mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
138 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
29d841a50b | ||
|
18e6e578dd | ||
|
d5e40e0271 | ||
|
6b85f10818 | ||
|
73ea029acd | ||
|
27c6e5ecff | ||
|
40f466b2c3 | ||
|
02667d3d1a | ||
|
9cb70817c7 | ||
|
e172f50d4f | ||
|
49f7185c8c | ||
|
638050a3de | ||
|
a0f369d100 | ||
|
f9239c860d | ||
|
41a08fed2b | ||
|
95d43712d3 | ||
|
4f76a8b0ce | ||
|
3fc2b81433 | ||
|
23a514bba8 | ||
|
51449c4913 | ||
|
18bb362578 | ||
|
6d9c2ec365 | ||
|
ad26a1968b | ||
|
52289acc62 | ||
|
b970c69844 | ||
|
c8e456d773 | ||
|
d00fbacec3 | ||
|
ccbec7715a | ||
|
1b7973ec13 | ||
|
1f63fd9344 | ||
|
d8d5118283 | ||
|
11226ff7d6 | ||
|
844d9c86a4 | ||
|
0aee97ccca | ||
|
fa06e5ee5a | ||
|
6794be4d1e | ||
|
4316396880 | ||
|
fd93bc7736 | ||
|
0cc88c17c7 | ||
|
6829f5308f | ||
|
1689913b43 | ||
|
33b7e8fad1 | ||
|
17eb55731e | ||
|
9dba57dc85 | ||
|
dddc49c235 | ||
|
faf9c4445f | ||
|
a646aaa125 | ||
|
80a0cb2bc4 | ||
|
675cabb7e3 | ||
|
f36cf088a1 | ||
|
df8d7075a8 | ||
|
314b194891 | ||
|
ac4fa0e035 | ||
|
0e7c10b26f | ||
|
64914e778d | ||
|
763a443680 | ||
|
27eb95cb36 | ||
|
732707ae2b | ||
|
ee81a8d88c | ||
|
b107848ccb | ||
|
ec270378d4 | ||
|
7c3b598eea | ||
|
aed0dab95d | ||
|
311249c5e0 | ||
|
88f3e01f72 | ||
|
8e06d18664 | ||
|
3fb4756c03 | ||
|
707ce62017 | ||
|
ddf485f354 | ||
|
c3f61af18a | ||
|
3eea8a714c | ||
|
49186dbfc1 | ||
|
87b9da11c6 | ||
|
77124056b8 | ||
|
8d794ed03f | ||
|
d4e05c99e3 | ||
|
111b91b1ed | ||
|
bed6b2d777 | ||
|
99a18be5fc | ||
|
ccf5454750 | ||
|
62e5f71cf9 | ||
|
f56834bb96 | ||
|
68819a1afc | ||
|
e4e2ff20a2 | ||
|
5c66c34414 | ||
|
1055673865 | ||
|
7a4b8abde7 | ||
|
1ad361593f | ||
|
ae850fdde7 | ||
|
c8b3bce8b9 | ||
|
c3d43a7d05 | ||
|
40f7aa47ad | ||
|
0e45aa7e58 | ||
|
ce3d76d662 | ||
|
c4209b9448 | ||
|
83cb3456fc | ||
|
e15c739e64 | ||
|
0df8c4b6a2 | ||
|
8fdea22379 | ||
|
260bb0af52 | ||
|
cf3baccc5e | ||
|
1bc9bdd64f | ||
|
b2dc4c6c1b | ||
|
130559d499 | ||
|
06899581db | ||
|
e9288874b1 | ||
|
b6243cdbe1 | ||
|
ed86e8de04 | ||
|
1b9ca4c9d9 | ||
|
dd69fd3934 | ||
|
3f47386cba | ||
|
83252617be | ||
|
a9f8506218 | ||
|
ea68e70ac7 | ||
|
e38a9df538 | ||
|
d3fe20c119 | ||
|
3feffaabe5 | ||
|
52b2c89d64 | ||
|
59100a0076 | ||
|
80fad96b1f | ||
|
eccfadb14e | ||
|
01f1f67de3 | ||
|
ef3c8a19fe | ||
|
732761a427 | ||
|
212e98302a | ||
|
ed366b15ca | ||
|
d7b895b92d | ||
|
0d6346180f | ||
|
433ddf5901 | ||
|
8e73882c9b | ||
|
e2f1f4a57a | ||
|
7205255d18 | ||
|
bc6e46bac8 | ||
|
374e931b2e | ||
|
f28e7cf47c | ||
|
8d5fbbe2bc | ||
|
2e6e8623f3 | ||
|
18462a632c |
305
.github/readme-zh_cn.md
vendored
Normal file
305
.github/readme-zh_cn.md
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||

|
||||
|
||||
移动设备界面友好,多种人工智能服务或模型支持(KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, WindowAI(Claude!)),类似 Galgame 的 老 婆 模 式,Horde SD,文本系统语音生成,世界信息(Lorebooks),可定制的界面,自动翻译,和比你所需要的更多的 Prompt。附带扩展服务,支持文本绘画生成与语音生成和基于向量数据库 ChromaDB 的聊天信息总结。
|
||||
|
||||
基于 TavernAI 1.2.8 的分叉版本
|
||||
|
||||
### 由 Cohee、RossAscends 和 SillyTavern 社区为您呈现
|
||||
|
||||
注意:我们创建了一个 [帮助文档](https://docs.sillytavern.app/) 网站来回答各类问题与帮助您开始使用。
|
||||
|
||||
### SillyTavern 或 TavernAI 是什么?
|
||||
|
||||
SillyTavern 是一个可以安装在电脑(和安卓手机)上的用户界面,让您可以与文本生成的人工智能互动,并与您或社区创建的角色聊天/玩角色扮演游戏。
|
||||
|
||||
SillyTavern 是 TavernAI 1.2.8 的一个分支,正在进行更积极地开发,并添加了许多重要功能。在这一点上,它可以被视为完全独立的程序。
|
||||
|
||||
### 分支
|
||||
|
||||
SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。
|
||||
|
||||
* release -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。
|
||||
* staging - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。
|
||||
|
||||
如果你不熟悉使用 Git 命令,或者不了解什么是分支,别担心!release 分支始终是您的首选。
|
||||
|
||||
### 除了 SillyTavern,我还需要什么?
|
||||
|
||||
SillyTavern 本身并无用处,因为它只是一个用户聊天界面。你必须接入一个能充当角色扮演的人工智能系统。支持的人工智能系统有多种:OpenAPI API (GPT)、KoboldAI(可在本地或 Google Colab 上运行)等。您可以在 [常见问题](https://docs.sillytavern.app/usage/faq/) 中阅读更多相关信息。
|
||||
|
||||
### 我需要一台性能强大的电脑来运行 SillyTavern 吗?
|
||||
|
||||
由于 SillyTavern 只是一个用户聊天界面,它对硬件性能的要求很低,可以在任何电脑上运行。需要强大性能的是人工智能系统。
|
||||
|
||||
### 移动设备支持
|
||||
|
||||
> 注意
|
||||
|
||||
> **此分叉可使用 Termux 在安卓手机上原生运行。请参考 ArroganceComplex#2659 编写的指南:**
|
||||
|
||||
<https://rentry.org/STAI-Termux>
|
||||
|
||||
Termux 不支持**.Webp 字符卡的导入/导出。请使用 JSON 或 PNG 格式**。
|
||||
|
||||
## 有问题或建议?
|
||||
|
||||
### 我们现在有了 Discord 社区
|
||||
|
||||
获取支持,或分享喜爱的角色和 Prompt:
|
||||
|
||||
### [加入 Discord 社区](https://discord.gg/RZdyAEUPvj)
|
||||
|
||||
***
|
||||
|
||||
直接与开发人员联系:
|
||||
|
||||
* Discord: cohee 或 rossascends
|
||||
* Reddit:/u/RossAscends 或 /u/sillylossy
|
||||
* [发布 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues)
|
||||
|
||||
## 此版本包括
|
||||
|
||||
* 经过大量修改的 TavernAI 1.2.8(超过 50% 的代码经过重写或优化)
|
||||
* 根据自定义规则自动重新生成消息
|
||||
* 群聊:多机器人房间,供角色与你或彼此交谈
|
||||
* 聊天书签/分支(复制当前状态下的对话)
|
||||
* 先进的 KoboldAI / TextGen 生成设置,包含大量社区预设
|
||||
* 支持世界信息(Lorebooks):创建丰富的传说
|
||||
* 支持 Window AI 浏览器扩展(运行 Claude、GPT 4 等模型):<https://windowai.io/>
|
||||
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API 连接
|
||||
* 连接 [AI Horde](https://horde.koboldai.net/)
|
||||
* Prompt 生成格式调整
|
||||
* Webp 角色卡支持(PNG 仍是内部格式)
|
||||
|
||||
## 扩展
|
||||
|
||||
SillyTavern 支持扩展服务,一些额外的人工智能模块可通过 [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras) 提供。
|
||||
|
||||
* 作者注释/角色偏见
|
||||
* 角色情绪识别
|
||||
* 聊天记录自动摘要
|
||||
* 在聊天窗口发送图片,并由人工智能解释图片内容
|
||||
* 文本图像生成(5 预设,以及 "自由模式")
|
||||
* 聊天信息的文字转语音(通过 ElevenLabs、Silero 或操作系统的语音生成)
|
||||
* ChromaDB 向量数据库,用于更智能的聊天 Prompt
|
||||
|
||||
扩展服务的完整功能介绍和使用教程,请参阅 [Docs](https://docs.sillytavern.app/extras/extensions/)。
|
||||
|
||||
## 界面/CSS/性能,由 RossAscends 调整并优化
|
||||
|
||||
* 针对 iOS 系统优化了界面,并支持将快捷方式保存到主屏幕,在全屏模式下打开。
|
||||
* 热键
|
||||
* 上 = 编辑聊天中的最后一条信息
|
||||
* Ctrl+P = 编辑聊天中最后一条用户信息
|
||||
* 左 = 向左滑动
|
||||
* 右 = 向右滑动(注意:当聊天窗口输入内容时,轻扫快捷键将被禁用)
|
||||
* Ctrl+左 = 查看本地存储的变量(在浏览器控制台窗口中)
|
||||
* 回车(选择聊天栏)= 向人工智能发送信息
|
||||
* Ctrl+Enter = 重新生成人工智能最后的回复
|
||||
|
||||
* 用户名更改和角色删除不再强制重新刷新页面。
|
||||
|
||||
* 增加在页面加载时自动连接 API 的选项。
|
||||
* 增加选项,在页面加载时自动加载最近的聊天信息。
|
||||
* 更好的 Tokens 计算器 - 适用于未保存的文字,并显示永久和临时 Tokens 数量
|
||||
|
||||
* 更好的聊天历史查询窗口
|
||||
* 聊天的文件名以"(角色卡名称)+(创建时间)"的可读格式保存
|
||||
* 聊天历史预览从 40 个字符增加到 300 个字符。
|
||||
* 聊天历史排序有多种选择(按名称、创建日期、聊天记录大小)。
|
||||
|
||||
* 默认情况下,左侧和右侧弹出的设置面板会在点击其他区域时自动关闭。
|
||||
* 点击导航面板上的 "锁按钮" 将保持弹出面板打开,并在不同聊天中记住此设置。
|
||||
* 导航面板的打开或关闭状态也会跨聊天保存。
|
||||
|
||||
* 自定义聊天界面:
|
||||
* 收到新消息时播放提示音
|
||||
* 切换圆形或长方形头像样式
|
||||
* 在台式电脑上拥有更宽的聊天窗口
|
||||
* 可选的半透明玻璃效果聊天窗口
|
||||
* 可定制 "主文本"、"引用文本 "和 "斜体文本 "的字体颜色。
|
||||
* 可定制聊天界面的背景颜色和透明模糊程度
|
||||
|
||||
## 安装
|
||||
|
||||
*注意:SillyTavern 用于本地安装,尚未在 Colab 或其他云服务上进行全面测试。
|
||||
|
||||
> **警告**
|
||||
|
||||
> 切勿安装到任何受 Windows 控制的系统文件夹(Program Files, System32, etc)中。
|
||||
|
||||
> 不要以管理员权限运行 start.bat
|
||||
|
||||
### Windows
|
||||
|
||||
通过 Git 安装(推荐使用,便于更新)
|
||||
|
||||
附有精美图片示例的简易指南:
|
||||
<https://docs.sillytavern.app/installation/windows/>
|
||||
|
||||
1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
|
||||
2. 安装 [GitHub 客户端](https://central.github.com/deployments/desktop/desktop/latest/win32)
|
||||
3. 打开 Windows 资源管理器 (`Win+E`)
|
||||
4. 浏览或创建一个不受 Windows 控制或监控的文件夹。(例如:C:\MySpecialFolder\)
|
||||
5. 点击顶部的 "地址栏",在该文件夹内打开命令提示符,输入 `cmd`,然后按回车。
|
||||
6. 弹出黑框(CMD 命令提示符)后,键入以下其中一项并按 Enter:
|
||||
|
||||
* 稳定分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
|
||||
* 开发分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
|
||||
|
||||
7. 等待 Git 克隆完成后,双击文件夹中的 `Start.bat` 将启动 NodeJS 并开始自动安装需要的软件包。
|
||||
8. 然后 SillyTavern 服务就会自动启动,同时在浏览器新标签页中自动打开。
|
||||
|
||||
通过压缩包下载安装(不推荐)
|
||||
|
||||
1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
|
||||
2. 从该 GitHub 仓库下载压缩包。(从 [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest) 获取 "Source code(zip)")。
|
||||
3. 将压缩包解压到您选择的文件夹中
|
||||
4. 双击或在命令行中运行 `Start.bat`。
|
||||
5. SillyTavern 服务自动为你准备好一切后,会在你的浏览器中打开一个新标签页。
|
||||
|
||||
### Linux
|
||||
|
||||
1.运行 `start.sh` 脚本。
|
||||
2.等待自动完成,然后开始享受
|
||||
|
||||
## API 密钥管理
|
||||
|
||||
SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
|
||||
|
||||
默认情况下,输入密钥并重新加载页面后,密钥会自动隐藏以保证安全。
|
||||
|
||||
如果要想通过点击 API 输入框旁边的按钮来查看密钥,请按照以下设置:
|
||||
|
||||
1. 打开 `config.conf` 文件,将里面的 `allowKeysExposure` 设置为 `true`。
|
||||
2. 然后重启 SillyTavern 服务。
|
||||
|
||||
## 远程访问
|
||||
|
||||
这通常是为那些想在手机上使用 SillyTavern 的人准备的,而他们的电脑和手机在同一个局域网中。
|
||||
|
||||
不过,SillyTavern 也可以被设置为允许从任何地方进行远程访问。
|
||||
|
||||
**重要提示:SillyTavern 是单用户程序,因此任何人登录后都能看到所有的角色卡和聊天内容,并能更改任何设置。
|
||||
|
||||
### 1.管理白名单 IP
|
||||
|
||||
* 在你的 SillyTavern 文件夹中新建一个文本文件,名为 `whitelist.txt`。
|
||||
* 用文本编辑器打开该文件,添加你希望允许连接的 IP 地址列表。
|
||||
* 接受单个 IP 地址和 IP 范围,示例:
|
||||
|
||||
```
|
||||
192.168.0.1
|
||||
192.168.0.20
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```
|
||||
192.168.0.*
|
||||
```
|
||||
|
||||
(上述 IP 范围将允许局域网中的任何设备连接)
|
||||
|
||||
也接受子网掩码设置(如 10.0.0.0/24)。
|
||||
|
||||
* 保存`whitelist.txt`文件。
|
||||
* 重启 SillyTavern 服务。
|
||||
|
||||
然后,文件中设置的 IP 就可以访问 SillyTavern 了。
|
||||
|
||||
*注意:"config.conf" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。
|
||||
|
||||
### 2.获取 SillyTavern 服务的 IP 地址
|
||||
|
||||
白名单设置完成后,您需要 SillyTavern 服务的 IP 地址。
|
||||
|
||||
如果 SillyTavern 服务设备在同一个局域网上,则使用安装 SillyTavern 服务的电脑的局域网 IP 地址:
|
||||
|
||||
* Windows:Windows 按钮 > 在搜索栏中输入 `cmd.exe` > 在打开的控制台中输入 `ipconfig`,回车 > 然后在输出中查找 `IPv4` 地址。
|
||||
|
||||
如果您(或其他人)想在互联网中访问你自己的 SillyTavern 服务,则需要运行 SillyTavern 服务的设备的互联网 IP 地址。
|
||||
|
||||
* 使用运行 SillyTavern 的设备,访问 [this page](https://whatismyipaddress.com/) 并查找 `IPv4`。这是您从互联网访问时要用到的。
|
||||
|
||||
### 3. 使用其他设备访问 SillyTavern 服务
|
||||
|
||||
无论你最终使用的是什么 IP 地址,都要将该 IP 地址和端口号输入其他设备网络浏览器。
|
||||
|
||||
同一局域网中的 SillyTavern 服务的典型默认地址如下:
|
||||
|
||||
`http://192.168.0.5:8000`
|
||||
|
||||
使用 http:// 而不是 https://
|
||||
|
||||
### 向所有 IP 开放您的 SillyTavern 服务
|
||||
|
||||
我们不建议这样做,但您可以打开 `config.conf` 并将里面的 `whitelist` 设置改为 `false`。
|
||||
|
||||
你必须删除(或重命名)SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。
|
||||
|
||||
这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。
|
||||
|
||||
用户名和密码在`config.conf`文件中设置。
|
||||
|
||||
重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。
|
||||
|
||||
### 还是无法访问?
|
||||
|
||||
* 为 `config.conf` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。
|
||||
* 在 "设置" > "网络和 Internet" > "以太网" 中启用 "专用网络" 配置。这对 Windows 11 非常重要,否则即使添加了上述防火墙规则也无法连接。
|
||||
|
||||
### 性能问题?
|
||||
|
||||
尝试在用户设置面板上关闭模糊效果(快速用户界面)模式。
|
||||
|
||||
## 我喜欢你的项目!我该如何贡献自己的力量?
|
||||
|
||||
### 应该
|
||||
|
||||
1. 发送 Fork 请求
|
||||
2. 使用规定的模板发送功能建议和问题报告
|
||||
3. 在提出任何问题之前,请先阅读 Readme 文件和文档
|
||||
|
||||
#### 不应该
|
||||
|
||||
1. 提供金钱捐赠
|
||||
2. 发送错误报告而不提供任何详细信息
|
||||
3. 提出已经回答过无数次的问题
|
||||
|
||||
## 我在哪里可以找到以前的聊天背景图片?
|
||||
|
||||
我们正在实行 100% 原创内容的政策,因此旧的背景图片已从该资源库中删除。
|
||||
|
||||
不过你可以在这里找到它们的存档:
|
||||
|
||||
<https://files.catbox.moe/1xevnc.zip>
|
||||
|
||||
## 屏幕截图
|
||||
|
||||
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">
|
||||
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">
|
||||
|
||||
## 许可证和贡献
|
||||
|
||||
** 发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。 **
|
||||
|
||||
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. **
|
||||
|
||||
* TAI Base by Humi: Unknown license
|
||||
* Cohee's modifications and derived code: AGPL v3
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (https://github.com/bdashore3)
|
||||
* Waifu mode inspired by the work of PepperTaco (https://github.com/peppertaco/Tavern/)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
* Thanks oobabooga for compiling presets for TextGen
|
||||
* KoboldAI Presets from KAI Lite: https://lite.koboldai.net/
|
||||
* Noto Sans font by Google (OFL license)
|
||||
* Icon theme by Font Awesome https://fontawesome.com (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
|
||||
* Linux startup script by AlpinDale
|
||||
* Thanks paniphons for providing a FAQ document
|
||||
* 10K Discord Users Celebratory Background by @kallmeflocc
|
||||
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
|
||||
* Korean translation by @doloroushyeonse
|
6
.github/readme.md
vendored
6
.github/readme.md
vendored
@@ -144,8 +144,8 @@ An easy-to-follow guide with pretty pictures:
|
||||
5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
|
||||
6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
|
||||
|
||||
* for Main Branch: `git clone https://github.com/SillyTavern/SillyTavern -b main`
|
||||
* for Dev Branch: `git clone https://github.com/SillyTavern/SillyTavern -b dev`
|
||||
* for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
|
||||
* for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
|
||||
|
||||
7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.
|
||||
8. The server will then start, and SillyTavern will pop up in your browser.
|
||||
@@ -293,7 +293,6 @@ GNU Affero General Public License for more details.**
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
|
||||
* BlipRanger's miscellaneous UI & extension modifications (<https://github.com/BlipRanger>)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
* Thanks oobabooga for compiling presets for TextGen
|
||||
@@ -306,3 +305,4 @@ GNU Affero General Public License for more details.**
|
||||
* 10K Discord Users Celebratory Background by @kallmeflocc
|
||||
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
|
||||
* Korean translation by @doloroushyeonse
|
||||
* k_euler_a support for Horde by <https://github.com/Teashrock>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
name: Build and Publish Release (Dev)
|
||||
name: Build and Publish Release (Release)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- release
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
@@ -30,8 +30,8 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/*
|
||||
tag_name: ci-dev
|
||||
name: Continuous Release (Dev)
|
||||
tag_name: ci-release
|
||||
name: Continuous Release (Release)
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@@ -1,9 +1,9 @@
|
||||
name: Build and Publish Release (Main)
|
||||
name: Build and Publish Release (Staging)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
@@ -30,8 +30,8 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/*
|
||||
tag_name: ci-main
|
||||
name: Continuous Release (Main)
|
||||
tag_name: ci-staging
|
||||
name: Continuous Release (Staging)
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -9,12 +9,14 @@ public/worlds/
|
||||
public/css/bg_load.css
|
||||
public/themes/
|
||||
public/OpenAI Settings/
|
||||
public/KoboldAI Settings/
|
||||
public/TextGen Settings/
|
||||
public/scripts/extensions/third-party/
|
||||
public/stats.json
|
||||
/uploads/
|
||||
*.jsonl
|
||||
/config.conf
|
||||
/docker/config.conf
|
||||
/docker/config
|
||||
.DS_Store
|
||||
public/settings.json
|
||||
/thumbnails
|
||||
@@ -24,4 +26,5 @@ secrets.json
|
||||
/dist
|
||||
/backups/
|
||||
public/movingUI/
|
||||
public/QuickReplies/
|
||||
content.log
|
||||
|
@@ -4,7 +4,7 @@ FROM node:19.1.0-alpine3.16
|
||||
ARG APP_HOME=/home/node/app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apk add gcompat tini
|
||||
RUN apk add gcompat tini git
|
||||
|
||||
# Ensure proper handling of kernel signals
|
||||
ENTRYPOINT [ "tini", "--" ]
|
||||
@@ -23,13 +23,17 @@ COPY . ./
|
||||
|
||||
# Copy default chats, characters and user avatars to <folder>.default folder
|
||||
RUN \
|
||||
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,settings.json" && \
|
||||
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds" && \
|
||||
\
|
||||
echo "*** Store default $RESOURCES in <folder>.default ***" && \
|
||||
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \
|
||||
\
|
||||
echo "*** Create symbolic links to config directory ***" && \
|
||||
for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done && \
|
||||
# rm "config.conf" "public/settings.json" "public/css/bg_load.css" && \
|
||||
ln -s "./config/config.conf" "config.conf" && \
|
||||
ln -s "../config/settings.json" "public/settings.json" && \
|
||||
ln -s "../../config/bg_load.css" "public/css/bg_load.css" && \
|
||||
mkdir "config"
|
||||
|
||||
# Cleanup unnecessary files
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 338 KiB |
@@ -9,5 +9,4 @@ services:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- "./config:/home/node/app/config"
|
||||
- "./config.conf:/home/node/app/config.conf"
|
||||
restart: unless-stopped
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Initialize missing user files
|
||||
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds,settings.json"
|
||||
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds"
|
||||
for R in $RESOURCES; do
|
||||
if [ ! -e "config/$R" ]; then
|
||||
echo "Resource not found, copying from defaults: $R"
|
||||
@@ -9,5 +9,20 @@ for R in $RESOURCES; do
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ! -e "config/config.conf" ]; then
|
||||
echo "Resource not found, copying from defaults: config.conf"
|
||||
cp -r "default/config.conf" "config/config.conf"
|
||||
fi
|
||||
|
||||
if [ ! -e "config/settings.json" ]; then
|
||||
echo "Resource not found, copying from defaults: settings.json"
|
||||
cp -r "default/settings.json" "config/settings.json"
|
||||
fi
|
||||
|
||||
if [ ! -e "config/bg_load.css" ]; then
|
||||
echo "Resource not found, copying from defaults: bg_load.css"
|
||||
cp -r "default/bg_load.css" "config/bg_load.css"
|
||||
fi
|
||||
|
||||
# Start the server
|
||||
exec node server.js
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.4",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
|
@@ -51,7 +51,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.4",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"max_length": 100,
|
||||
"genamt": 100,
|
||||
"max_length": 2048,
|
||||
"genamt": 200,
|
||||
"temp": 1,
|
||||
"top_k": 0,
|
||||
"top_p": 0.95,
|
||||
@@ -19,4 +19,4 @@
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
||||
}
|
||||
|
19
public/NovelAI Settings/Asper-Kayra.settings
Normal file
19
public/NovelAI Settings/Asper-Kayra.settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"order": [5, 0, 1, 3],
|
||||
"temperature": 1.23,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 200,
|
||||
"typical_p": 0.966,
|
||||
"tail_free_sampling": 0.982,
|
||||
"repetition_penalty": 1.74,
|
||||
"repetition_penalty_range": 4000,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0.02,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"max_context": 8192
|
||||
}
|
19
public/NovelAI Settings/Blended-Coffee-Kayra.settings
Normal file
19
public/NovelAI Settings/Blended-Coffee-Kayra.settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"order": [6, 0, 1, 2, 3],
|
||||
"temperature": 1,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 25,
|
||||
"top_p": 1,
|
||||
"tail_free_sampling": 0.925,
|
||||
"repetition_penalty": 1.6,
|
||||
"repetition_penalty_frequency": 0.001,
|
||||
"repetition_penalty_range": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "medium",
|
||||
"cfg_scale": 1.55,
|
||||
"max_context": 8192
|
||||
}
|
20
public/NovelAI Settings/Blook-Kayra.settings
Normal file
20
public/NovelAI Settings/Blook-Kayra.settings
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"order": [6, 2, 3, 1, 0],
|
||||
"temperature": 1,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 0,
|
||||
"top_p": 0.96,
|
||||
"tail_free_sampling": 0.96,
|
||||
"repetition_penalty": 2,
|
||||
"repetition_penalty_slope": 1,
|
||||
"repetition_penalty_frequency": 0.02,
|
||||
"repetition_penalty_range": 0,
|
||||
"repetition_penalty_presence": 0.3,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "very_aggressive",
|
||||
"cfg_scale": 1.3,
|
||||
"max_context": 8192
|
||||
}
|
21
public/NovelAI Settings/Carefree-Kayra.settings
Normal file
21
public/NovelAI Settings/Carefree-Kayra.settings
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"order": [2, 3, 0, 4, 1],
|
||||
"temperature": 1.35,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 12,
|
||||
"top_p": 0.85,
|
||||
"top_a": 0.1,
|
||||
"tail_free_sampling": 0.915,
|
||||
"repetition_penalty": 2.8,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_slope": 0.02,
|
||||
"repetition_penalty_frequency": 0.02,
|
||||
"repetition_penalty_presence": 0,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"max_context": 8192
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"order": [
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
0,
|
||||
2
|
||||
],
|
||||
"temperature": 1.05,
|
||||
"max_length": 90,
|
||||
"min_length": 1,
|
||||
"tail_free_sampling": 0.989,
|
||||
"repetition_penalty": 1.5,
|
||||
"repetition_penalty_range": 8192,
|
||||
"repetition_penalty_frequency": 0.03,
|
||||
"repetition_penalty_presence": 0.005,
|
||||
"top_a": 0.075,
|
||||
"top_k": 79,
|
||||
"top_p": 0.95
|
||||
}
|
21
public/NovelAI Settings/Edgewise-Clio.settings
Normal file
21
public/NovelAI Settings/Edgewise-Clio.settings
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"order": [4, 0, 5, 3, 2],
|
||||
"temperature": 1.09,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_p": 0.969,
|
||||
"top_a": 0.09,
|
||||
"typical_p": 0.99,
|
||||
"tail_free_sampling": 0.969,
|
||||
"repetition_penalty": 1.09,
|
||||
"repetition_penalty_range": 8192,
|
||||
"repetition_penalty_slope": 0.069,
|
||||
"repetition_penalty_frequency": 0.006,
|
||||
"repetition_penalty_presence": 0.009,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
}
|
@@ -5,6 +5,8 @@
|
||||
"min_length": 1,
|
||||
"top_k": 25,
|
||||
"top_p": 1,
|
||||
"top_a": 0,
|
||||
"typical_p": 1,
|
||||
"tail_free_sampling": 0.925,
|
||||
"repetition_penalty": 1.9,
|
||||
"repetition_penalty_range": 768,
|
||||
@@ -14,5 +16,7 @@
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
}
|
||||
}
|
||||
|
20
public/NovelAI Settings/Fresh-Coffee-Kayra.settings
Normal file
20
public/NovelAI Settings/Fresh-Coffee-Kayra.settings
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"order": [0, 1, 2, 3],
|
||||
"temperature": 1,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 25,
|
||||
"top_p": 1,
|
||||
"tail_free_sampling": 0.925,
|
||||
"repetition_penalty": 1.9,
|
||||
"repetition_penalty_range": 768,
|
||||
"repetition_penalty_slope": 1,
|
||||
"repetition_penalty_frequency": 0.0025,
|
||||
"repetition_penalty_presence": 0.001,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "off",
|
||||
"max_context": 8192
|
||||
}
|
19
public/NovelAI Settings/Green-Active-Writer-Kayra.settings
Normal file
19
public/NovelAI Settings/Green-Active-Writer-Kayra.settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"order": [6, 1, 0, 5, 3],
|
||||
"temperature": 1.25,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 70,
|
||||
"typical_p": 0.9,
|
||||
"tail_free_sampling": 0.925,
|
||||
"repetition_penalty": 2,
|
||||
"repetition_penalty_range": 1632,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"cfg_scale": 1.825,
|
||||
"max_context": 8192
|
||||
}
|
@@ -4,6 +4,8 @@
|
||||
"max_length": 40,
|
||||
"min_length": 1,
|
||||
"top_a": 0.022,
|
||||
"top_k": 0,
|
||||
"top_p": 1,
|
||||
"typical_p": 0.9,
|
||||
"tail_free_sampling": 0.956,
|
||||
"repetition_penalty": 1.25,
|
||||
@@ -14,5 +16,7 @@
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@
|
||||
"min_length": 1,
|
||||
"top_k": 25,
|
||||
"top_a": 0.3,
|
||||
"top_p": 1,
|
||||
"typical_p": 0.96,
|
||||
"tail_free_sampling": 0.895,
|
||||
"repetition_penalty": 1.0125,
|
||||
@@ -15,5 +16,7 @@
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
}
|
||||
}
|
||||
|
22
public/NovelAI Settings/Pilotfish-Kayra.settings
Normal file
22
public/NovelAI Settings/Pilotfish-Kayra.settings
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"order": [6, 0, 4, 1, 2, 5, 3],
|
||||
"temperature": 1.31,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 25,
|
||||
"top_p": 0.97,
|
||||
"top_a": 0.18,
|
||||
"typical_p": 0.98,
|
||||
"tail_free_sampling": 1,
|
||||
"repetition_penalty": 1.55,
|
||||
"repetition_penalty_frequency": 0.00075,
|
||||
"repetition_penalty_presence": 0.00085,
|
||||
"repetition_penalty_range": 8192,
|
||||
"repetition_penalty_slope": 1.8,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "medium",
|
||||
"cfg_scale": 1.35,
|
||||
"max_context": 8192
|
||||
}
|
17
public/NovelAI Settings/Stelenes-Kayra.settings
Normal file
17
public/NovelAI Settings/Stelenes-Kayra.settings
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"order": [3, 0, 5],
|
||||
"temperature": 2.5,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"typical_p": 0.966,
|
||||
"tail_free_sampling": 0.933,
|
||||
"repetition_penalty": 1,
|
||||
"repetition_penalty_range": 2048,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"max_context": 8192
|
||||
}
|
@@ -1,19 +1,22 @@
|
||||
{
|
||||
"order": [1, 3, 4, 0, 2],
|
||||
"temperature": 1.05,
|
||||
"max_length": 40,
|
||||
"order": [1, 5, 0, 2, 3, 4],
|
||||
"temperature": 1.5,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 79,
|
||||
"top_p": 0.95,
|
||||
"top_a": 0.075,
|
||||
"tail_free_sampling": 0.989,
|
||||
"repetition_penalty": 1.5,
|
||||
"top_k": 10,
|
||||
"top_p": 0.75,
|
||||
"top_a": 0.08,
|
||||
"typical_p": 0.975,
|
||||
"tail_free_sampling": 0.967,
|
||||
"repetition_penalty": 2.25,
|
||||
"repetition_penalty_range": 8192,
|
||||
"repetition_penalty_slope": 3.33,
|
||||
"repetition_penalty_frequency": 0.03,
|
||||
"repetition_penalty_slope": 0.09,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0.005,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
}
|
||||
}
|
||||
|
18
public/NovelAI Settings/Tesseract-Kayra.settings
Normal file
18
public/NovelAI Settings/Tesseract-Kayra.settings
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"order": [6, 0, 5],
|
||||
"temperature": 0.895,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"typical_p": 0.9,
|
||||
"repetition_penalty": 2,
|
||||
"repetition_penalty_slope": 3.2,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"repetition_penalty_range": 4048,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "aggressive",
|
||||
"cfg_scale": 1.3,
|
||||
"max_context": 8192
|
||||
}
|
@@ -5,6 +5,7 @@
|
||||
"min_length": 1,
|
||||
"top_k": 0,
|
||||
"top_p": 0.912,
|
||||
"top_a": 1,
|
||||
"typical_p": 0.912,
|
||||
"tail_free_sampling": 0.921,
|
||||
"repetition_penalty": 1.21,
|
||||
@@ -15,5 +16,7 @@
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"cfg_scale": 1,
|
||||
"phrase_rep_pen": "very_light",
|
||||
"max_context": 8192
|
||||
}
|
||||
}
|
||||
|
19
public/NovelAI Settings/Writers-Daemon-Kayra.settings
Normal file
19
public/NovelAI Settings/Writers-Daemon-Kayra.settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"order": [6, 1, 0, 5, 3, 2],
|
||||
"temperature": 1.5,
|
||||
"max_length": 300,
|
||||
"min_length": 1,
|
||||
"top_k": 70,
|
||||
"top_p": 0.95,
|
||||
"typical_p": 0.95,
|
||||
"tail_free_sampling": 0.95,
|
||||
"repetition_penalty": 1.6,
|
||||
"repetition_penalty_range": 2016,
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"use_cache": false,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"phrase_rep_pen": "very_aggressive",
|
||||
"max_context": 8192
|
||||
}
|
23
public/QuickReplies/Default.json
Normal file
23
public/QuickReplies/Default.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "Default",
|
||||
"quickReplyEnabled": true,
|
||||
"quickReplySlots": [
|
||||
{
|
||||
"mes": "/?",
|
||||
"label": "HELP",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"mes": "/newchat",
|
||||
"label": "New Chat",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"mes": "/bgcol",
|
||||
"label": "Match UI to Background",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"numberOfSlots": 3,
|
||||
"selectedPreset": "Default"
|
||||
}
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.02,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 4.5,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 2,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 10,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.01,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0.6,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.2,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 1.49,
|
||||
"eta_cutoff": 10.42,
|
||||
"rep_pen": 1.17,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.18,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.18,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.05,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.05,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -6,6 +6,7 @@
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.07,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.09,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 10.78,
|
||||
"rep_pen": 1.21,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -8,6 +8,7 @@
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.19,
|
||||
"rep_pen_range": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -26,6 +26,7 @@
|
||||
<script src="scripts/jquery-ui.min.js"></script>
|
||||
<script src="scripts/jquery.transit.min.js"></script>
|
||||
<script src="scripts/jquery-cookie-1.4.1.min.js"></script>
|
||||
<script src="scripts/jquery.ui.touch-punch.min.js"></script>
|
||||
<script src="scripts/showdown.min.js"></script>
|
||||
<script src="scripts/showdown-katex.min.js"></script>
|
||||
<script src="scripts/popper.js"></script>
|
||||
@@ -94,6 +95,7 @@
|
||||
<script type="module" src="scripts/context-template.js"></script>
|
||||
<script type="module" src="scripts/extensions.js"></script>
|
||||
<script type="module" src="scripts/authors-note.js"></script>
|
||||
<script type="module" src="scripts/preset-manager.js"></script>
|
||||
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
|
||||
|
||||
<title>SillyTavern</title>
|
||||
@@ -130,15 +132,24 @@
|
||||
<div class="scrollableInner">
|
||||
<div class="flex-container" id="ai_response_configuration">
|
||||
<div id="respective-presets-block" class="width100p">
|
||||
<input type="file" hidden data-preset-manager-file="" accept=".json, .settings">
|
||||
<div id="kobold_api-presets">
|
||||
<h3><span data-i18n="kobldpresets">Kobold Presets</span>
|
||||
<a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</h3>
|
||||
<select id="settings_perset">
|
||||
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
|
||||
</select>
|
||||
|
||||
<div class="preset_buttons">
|
||||
<select id="settings_perset" data-preset-manager-for="kobold">
|
||||
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
|
||||
</select>
|
||||
<i data-preset-manager-update="kobold" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="kobold" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-download"title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="kobold" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="novel_api-presets">
|
||||
<h3>
|
||||
@@ -147,7 +158,7 @@
|
||||
<span class="note-link-span">?</span>
|
||||
</a>
|
||||
</h3>
|
||||
<select id="settings_perset_novel">
|
||||
<select id="settings_perset_novel" data-preset-manager-for="novel">
|
||||
<option value="gui" data-i18n="default">Default</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -170,8 +181,15 @@
|
||||
<div id="textgenerationwebui_api-presets">
|
||||
<h3><span data-i18n="text gen webio(ooba)preset">Text Gen WebUI (ooba) presets</span>
|
||||
</h3>
|
||||
<select id="settings_preset_textgenerationwebui">
|
||||
</select>
|
||||
<div class="preset_buttons">
|
||||
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
|
||||
</select>
|
||||
<i data-preset-manager-update="textgenerationwebui" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
|
||||
<i data-preset-manager-new="textgenerationwebui" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
|
||||
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
|
||||
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-download"title="Export preset" data-i18n="[title]Export preset"></i>
|
||||
<i data-preset-manager-delete="textgenerationwebui" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
@@ -201,7 +219,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="max_context" name="volume" min="512" max="2048" step="1">
|
||||
<input type="range" id="max_context" name="volume" min="512" max="4096" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter" data-randomization-disabled="true">
|
||||
<div contenteditable="true" data-for="max_context" id="max_context_counter">
|
||||
@@ -217,10 +235,9 @@
|
||||
<span data-i18n="unlocked">Unlocked</span>
|
||||
</label>
|
||||
<div id="max_context_unlocked_warning">
|
||||
<b class="neutral_warning" data-i18n="ATTENTION!">ATTENTION!</b>
|
||||
<span data-i18n="only select modls support context sizes greater than 2048 tokens. proceed only is you know you're doing">
|
||||
Only select models support context sizes greater than 2048 tokens.
|
||||
Proceed only if you know what you're doing.
|
||||
Increase only if you know what you're doing.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,7 +253,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="temp" name="volume" min="0.1" max="2.0" step="0.01">
|
||||
<input type="range" id="temp" name="volume" min="0.0" max="2.0" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="temp" id="temp_counter">
|
||||
@@ -266,7 +283,7 @@
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="rep_pen_range" name="volume" min="0" max="2048" step="1">
|
||||
<input type="range" id="rep_pen_range" name="volume" min="0" max="4096" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="rep_pen_range" id="rep_pen_range_counter">
|
||||
@@ -441,6 +458,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="rep.pen range">
|
||||
Rep. Pen. Range
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="rep_pen_range_textgenerationwebui" name="volume" min="0" max="4096" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="rep_pen_range_textgenerationwebui" id="rep_pen_range_counter_textgenerationwebui">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Encoder Rep. Pen.">
|
||||
Encoder Rep. Pen.
|
||||
@@ -513,6 +545,19 @@
|
||||
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude">
|
||||
<div class="range-block-title justifyLeft" data-i18n="Proxy Password">
|
||||
Proxy Password
|
||||
</div>
|
||||
<div class="toggle-description justifyLeft">
|
||||
<span data-i18n="Will be used as a password for the proxy instead of API key.">
|
||||
Will be used as a password for the proxy instead of API key.<br>
|
||||
</span>
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<input id="openai_proxy_password" type="text" class="text_pole" placeholder="" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="openai,claude">
|
||||
<div class="range-block-title justifyLeft">
|
||||
<label for="legacy_streaming" class="checkbox_label">
|
||||
@@ -762,6 +807,111 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="novel_api-settings">
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Top P">
|
||||
Top P
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="top_p_novel" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="top_p_novel" id="top_p_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Top A">
|
||||
Top A
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="top_a_novel" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="top_a_novel" id="top_a_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Top K">
|
||||
Top K
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="top_k_novel" name="volume" min="0" max="300" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="top_k_novel" id="top_k_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Typical P">
|
||||
Typical P
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="typical_p_novel" id="typical_p_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="CFG Scale">
|
||||
CFG Scale
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="cfg_scale_novel" name="volume" min="1" max="3" step="0.05">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="cfg_scale_novel" id="cfg_scale_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Phrase Repetition Penalty">
|
||||
Phrase Repetition Penalty
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="phrase_rep_pen_novel" name="volume" min="0" max="5" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="phrase_rep_pen_novel" id="phrase_rep_pen_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block">
|
||||
<div class="range-block-title" data-i18n="Min Length">
|
||||
Min Length
|
||||
</div>
|
||||
<div class="range-block-range-and-counter">
|
||||
<div class="range-block-range">
|
||||
<input type="range" id="min_length_novel" name="volume" min="0" max="1024" step="1">
|
||||
</div>
|
||||
<div class="range-block-counter">
|
||||
<div contenteditable="true" data-for="min_length_novel" id="min_length_counter_novel">
|
||||
select
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="textgenerationwebui_api-settings">
|
||||
<div class="range-block">
|
||||
@@ -1169,6 +1319,14 @@
|
||||
<textarea id="jailbreak_prompt_textarea" class="text_pole textarea_compact" name="jailbreak_prompt" rows="6" placeholder=""></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="range-block" data-source="claude">
|
||||
<div class="range-block-title" data-i18n="Assistant Prefill">
|
||||
Assistant Prefill
|
||||
</div>
|
||||
<div class="wide100p">
|
||||
<input type="text" id="claude_assistant_prefill" class="text_pole" placeholder="Start Claude's answer with...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-drawer wide100p">
|
||||
<div class="inline-drawer-toggle inline-drawer-header margin-bot-10px">
|
||||
@@ -1372,6 +1530,7 @@
|
||||
<option value="euterpe-v2">Euterpe</option>
|
||||
<option value="krake-v2">Krake</option>
|
||||
<option value="clio-v1">Clio</option>
|
||||
<option value="kayra-v1">Kayra</option>
|
||||
</select>
|
||||
</form>
|
||||
<div id="online_status3">
|
||||
@@ -1423,7 +1582,9 @@
|
||||
<form id="openai_form" data-source="openai" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||
<h4><span data-i18n="OpenAI API key">OpenAI API key</span></h4>
|
||||
<div>
|
||||
<a id="openai_api_usage" href="javascript:void(0);"><span data-i18n="View API Usage Metrics">View API Usage Metrics</span></a>
|
||||
<a id="openai_api_usage" href="https://platform.openai.com/account/usage" target="_blank">
|
||||
<span data-i18n="View API Usage Metrics">View API Usage Metrics</span>
|
||||
</a>
|
||||
</div>
|
||||
<span>
|
||||
<ol>
|
||||
@@ -1441,6 +1602,11 @@
|
||||
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
|
||||
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_openai"></div>
|
||||
</div>
|
||||
<div id="ReverseProxyWarningMessage2" class="reverse_proxy_warning">
|
||||
<b data-i18n="Use Proxy password field instead. This input will be ignored.">
|
||||
Use "Proxy password" field instead. This input will be ignored.
|
||||
</b>
|
||||
</div>
|
||||
<div data-for="api_key_openai" class="neutral_warning">
|
||||
For privacy reasons, your API key will be hidden after you reload the page.
|
||||
</div>
|
||||
@@ -1705,7 +1871,7 @@
|
||||
<span data-i18n="Input Sequence">Input Sequence</span>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@@ -1713,7 +1879,7 @@
|
||||
<span data-i18n="Output Sequence">Output Sequence</span>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1723,7 +1889,7 @@
|
||||
<small data-i18n="System Sequence">System Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@@ -1731,7 +1897,7 @@
|
||||
<small data-i18n="Stop Sequence">Stop Sequence</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
@@ -1739,7 +1905,7 @@
|
||||
<small data-i18n="Separator">Separator</small>
|
||||
</label>
|
||||
<div>
|
||||
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
|
||||
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1758,8 +1924,8 @@
|
||||
<option value="1">GPT-3 (OpenAI)</option>
|
||||
<option value="2">GPT-3 (Alternative / Classic)</option>
|
||||
<option value="3">Sentencepiece (LLaMA)</option>
|
||||
<option value="4">NerdStash (NovelAI Krake)</option>
|
||||
<option value="5">NerdStash v2 (NovelAI Clio)</option>
|
||||
<option value="4">NerdStash (NovelAI Clio)</option>
|
||||
<option value="5">NerdStash v2 (NovelAI Kayra)</option>
|
||||
<option value="6">API (WebUI)</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1979,6 +2145,14 @@
|
||||
Match whole words
|
||||
</small>
|
||||
</label>
|
||||
<label title="Alert if your world info is greater than the allocated budget."
|
||||
data-i18n="[title]Alert if your world info is greater than the allocated budget."
|
||||
class="checkbox_label">
|
||||
<input id="world_info_overflow_alert" type="checkbox" />
|
||||
<small data-i18n="Alert On Overflow">
|
||||
Alert On Overflow
|
||||
</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2263,6 +2437,10 @@
|
||||
<input id="swipes-checkbox" type="checkbox" />
|
||||
<span data-i18n="Swipes">Swipes</span>
|
||||
</label>
|
||||
<label for="fuzzy_search_checkbox">
|
||||
<input id="fuzzy_search_checkbox" type="checkbox" />
|
||||
<span data-i18n="Advanced Character Search">Advanced Character Search</span>
|
||||
</label>
|
||||
<label for="prefer_character_prompt" title="If checked and the character card contains a prompt override (System Prompt), use that instead." data-i18n="[title]If checked and the character card contains a prompt override (System Prompt), use that instead." class="checkbox_label">
|
||||
<input id="prefer_character_prompt" type="checkbox" />
|
||||
<span data-i18n="Prefer Character Card Prompt">Prefer Char. Prompt</span>
|
||||
@@ -2323,7 +2501,7 @@
|
||||
<span data-i18n="Confirm message deletion">Confirm message deletion</span>
|
||||
</label>
|
||||
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
|
||||
<span data-i18n="Spoiler Free Mode">Spolier Free Mode</span>
|
||||
<span data-i18n="Spoiler Free Mode">Spoiler Free Mode</span>
|
||||
</label>
|
||||
|
||||
<div class="inline-drawer wide100p flexFlowColumn">
|
||||
@@ -2763,6 +2941,8 @@
|
||||
<option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
|
||||
<option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
|
||||
<option data-field="chat_size" data-order="asc" data-i18n="Least chats">Least chats</option>
|
||||
<option data-field="data_size" data-order="desc" data-i18n="Most tokens">Most tokens</option>
|
||||
<option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option>
|
||||
<option data-field="name" data-order="random" data-i18n="Random">Random</option>
|
||||
</select>
|
||||
</form>
|
||||
@@ -3283,7 +3463,8 @@
|
||||
<div class="flex-container wide100pLess70px character_select_container">
|
||||
<div class="wide100p character_name_block">
|
||||
<span class="ch_name"></span>
|
||||
<i class="ch_avatar_url"></i>
|
||||
<small class="character_version"></small>
|
||||
<small class="ch_avatar_url"></small>
|
||||
</div>
|
||||
<i class="ch_fav_icon fa-solid fa-star"></i>
|
||||
<input class="ch_fav" value="" hidden />
|
||||
@@ -3601,6 +3782,14 @@
|
||||
<!-- popups live outside sheld to avoid blur conflicts -->
|
||||
<div id="options" class="font-family-reset" style="display: none;">
|
||||
<div class="options-content">
|
||||
<a id="option_close_chat">
|
||||
<i class="fa-lg fa-solid fa-times"></i>
|
||||
<span data-i18n="Close chat">Close chat</span>
|
||||
</a>
|
||||
<a id="option_settings">
|
||||
<i class="fa-lg fa-solid fa-cog"></i>
|
||||
<span data-i18n="Toggle Panels">Toggle Panels</span>
|
||||
</a>
|
||||
<a id="option_toggle_AN">
|
||||
<i class="fa-lg fa-solid fa-note-sticky"></i>
|
||||
<span data-i18n="Author's Note">Author's Note</span>
|
||||
|
10
public/instruct/Llama2.json
Normal file
10
public/instruct/Llama2.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Llama 2",
|
||||
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
|
||||
"system_sequence": "[INST] <<SYS>>\n",
|
||||
"stop_sequence": "",
|
||||
"input_sequence": "[INST]",
|
||||
"output_sequence": "[/INST]",
|
||||
"separator_sequence": "\n",
|
||||
"wrap": false
|
||||
}
|
327
public/script.js
327
public/script.js
@@ -26,6 +26,7 @@ import {
|
||||
getWorldInfoPrompt,
|
||||
setWorldInfoSettings,
|
||||
world_info_recursive,
|
||||
world_info_overflow_alert,
|
||||
world_info_case_sensitive,
|
||||
world_info_match_whole_words,
|
||||
world_names,
|
||||
@@ -76,6 +77,8 @@ import {
|
||||
persona_description_positions,
|
||||
loadMovingUIState,
|
||||
getCustomStoppingStrings,
|
||||
fuzzySearchCharacters,
|
||||
MAX_CONTEXT_DEFAULT,
|
||||
} from "./scripts/power-user.js";
|
||||
|
||||
import {
|
||||
@@ -701,11 +704,11 @@ var is_use_scroll_holder = false;
|
||||
|
||||
//settings
|
||||
var settings;
|
||||
var koboldai_settings;
|
||||
var koboldai_setting_names;
|
||||
export let koboldai_settings;
|
||||
export let koboldai_setting_names;
|
||||
var preset_settings = "gui";
|
||||
var user_avatar = "you.png";
|
||||
var amount_gen = 80; //default max length of AI generated responses
|
||||
export var amount_gen = 80; //default max length of AI generated responses
|
||||
var max_context = 2048;
|
||||
|
||||
var is_pygmalion = false;
|
||||
@@ -719,8 +722,8 @@ let extension_prompts = {};
|
||||
var main_api;// = "kobold";
|
||||
//novel settings
|
||||
let novel_tier;
|
||||
let novelai_settings;
|
||||
let novelai_setting_names;
|
||||
export let novelai_settings;
|
||||
export let novelai_setting_names;
|
||||
let abortController;
|
||||
|
||||
//css
|
||||
@@ -735,6 +738,9 @@ let token;
|
||||
|
||||
var PromptArrayItemForRawPromptDisplay;
|
||||
|
||||
export let active_character = ""
|
||||
export let active_group = ""
|
||||
|
||||
export function getRequestHeaders() {
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
@@ -784,6 +790,14 @@ function checkOnlineStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
export function setActiveCharacter(character) {
|
||||
active_character = character;
|
||||
}
|
||||
|
||||
export function setActiveGroup(group) {
|
||||
active_group = group;
|
||||
}
|
||||
|
||||
async function getStatus() {
|
||||
if (is_get_status) {
|
||||
if (main_api == "koboldhorde") {
|
||||
@@ -895,6 +909,14 @@ async function printCharacters() {
|
||||
template.find('.ch_description').hide();
|
||||
}
|
||||
|
||||
const version = item.data?.character_version || '';
|
||||
if (version) {
|
||||
template.find('.character_version').text(version);
|
||||
}
|
||||
else {
|
||||
template.find('.character_version').hide();
|
||||
}
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(item.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
@@ -1224,6 +1246,9 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
|
||||
//console.log('mes after removed <tags>')
|
||||
//console.log(mes)
|
||||
} */
|
||||
|
||||
mes = DOMPurify.sanitize(mes);
|
||||
|
||||
return mes;
|
||||
}
|
||||
|
||||
@@ -1527,7 +1552,6 @@ function scrollChatToBottom() {
|
||||
function substituteParams(content, _name1, _name2, _original) {
|
||||
_name1 = _name1 ?? name1;
|
||||
_name2 = _name2 ?? name2;
|
||||
_original = _original || '';
|
||||
|
||||
if (!content) {
|
||||
return '';
|
||||
@@ -1536,7 +1560,9 @@ function substituteParams(content, _name1, _name2, _original) {
|
||||
// Replace {{original}} with the original message
|
||||
// Note: only replace the first instance of {{original}}
|
||||
// This will hopefully prevent the abuse
|
||||
content = content.replace(/{{original}}/i, _original);
|
||||
if (typeof _original === 'string') {
|
||||
content = content.replace(/{{original}}/i, _original);
|
||||
}
|
||||
content = content.replace(/{{input}}/gi, $('#send_textarea').val());
|
||||
content = content.replace(/{{user}}/gi, _name1);
|
||||
content = content.replace(/{{char}}/gi, _name2);
|
||||
@@ -1630,6 +1656,10 @@ function getStoppingStrings(isImpersonate, addSpace) {
|
||||
|
||||
result.push(userString);
|
||||
|
||||
if (!is_pygmalion && result.includes(youString)) {
|
||||
result.splice(result.indexOf(youString), 1);
|
||||
}
|
||||
|
||||
// Add other group members as the stopping strings
|
||||
if (selected_group) {
|
||||
const group = groups.find(x => x.id === selected_group);
|
||||
@@ -2109,7 +2139,6 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
|
||||
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||||
const isImpersonate = type == "impersonate";
|
||||
const isContinue = type == 'continue';
|
||||
|
||||
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
||||
// Name for the multigen prefix
|
||||
@@ -2198,6 +2227,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
||||
type = 'continue';
|
||||
}
|
||||
|
||||
const isContinue = type == 'continue';
|
||||
deactivateSendButtons();
|
||||
|
||||
let { messageBias, promptBias, isUserPromptBias } = getBiasStrings(textareaText, type);
|
||||
@@ -2953,7 +2983,7 @@ function getNextMessageId(type) {
|
||||
}
|
||||
|
||||
export function getBiasStrings(textareaText, type) {
|
||||
if (type == 'impersonate') {
|
||||
if (type == 'impersonate' || type == 'continue') {
|
||||
return { messageBias: '', promptBias: '', isUserPromptBias: false };
|
||||
}
|
||||
|
||||
@@ -3046,9 +3076,9 @@ function getMaxContextSize() {
|
||||
// Should be used with nerdstash tokenizer for best results
|
||||
this_max_context = Math.min(max_context, 2048);
|
||||
}
|
||||
if (nai_settings.model_novel == 'clio-v1') {
|
||||
// Clio has a max context of 8192
|
||||
// Should be used with nerdstash_v2 tokenizer for best results
|
||||
if (nai_settings.model_novel == 'clio-v1' || nai_settings.model_novel == 'kayra-v1') {
|
||||
// Clio and Kayra has a max context of 8192
|
||||
// Should be used with nerdstash / nerdstash_v2 tokenizer for best results
|
||||
this_max_context = Math.min(max_context, 8192);
|
||||
}
|
||||
}
|
||||
@@ -3150,6 +3180,17 @@ async function DupeChar() {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup(`
|
||||
<h3>Are you sure you want to duplicate this character?</h3>
|
||||
<span>If you just want to start a new chat with the same character, use "Start new chat" option in the bottom-left options menu.</span><br><br>`,
|
||||
'confirm',
|
||||
);
|
||||
|
||||
if (!confirm) {
|
||||
console.log('User cancelled duplication');
|
||||
return;
|
||||
}
|
||||
|
||||
const body = { avatar_url: characters[this_chid].avatar };
|
||||
const response = await fetch('/dupecharacter', {
|
||||
method: 'POST',
|
||||
@@ -4906,11 +4947,6 @@ async function getSettings(type) {
|
||||
novelai_setting_names = {};
|
||||
novelai_setting_names = arr_holder;
|
||||
|
||||
nai_settings.preset_settings_novel = settings.preset_settings_novel;
|
||||
$(
|
||||
`#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`
|
||||
).attr("selected", "true");
|
||||
|
||||
//Load AI model config settings
|
||||
|
||||
amount_gen = settings.amount_gen;
|
||||
@@ -4923,10 +4959,11 @@ async function getSettings(type) {
|
||||
showSwipeButtons();
|
||||
|
||||
// Kobold
|
||||
loadKoboldSettings(settings);
|
||||
loadKoboldSettings(settings.kai_settings ?? settings);
|
||||
|
||||
// Novel
|
||||
loadNovelSettings(settings);
|
||||
loadNovelSettings(settings.nai_settings ?? settings);
|
||||
$(`#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`).attr("selected", "true");
|
||||
|
||||
// TextGen
|
||||
loadTextGenSettings(data, settings);
|
||||
@@ -4984,6 +5021,10 @@ async function getSettings(type) {
|
||||
highlightSelectedAvatar();
|
||||
setPersonaDescription();
|
||||
|
||||
//Load the active character and group
|
||||
active_character = settings.active_character;
|
||||
active_group = settings.active_group;
|
||||
|
||||
//Load the API server URL from settings
|
||||
api_server = settings.api_server;
|
||||
$("#api_url_text").val(api_server);
|
||||
@@ -5024,6 +5065,8 @@ async function saveSettings(type) {
|
||||
data: JSON.stringify({
|
||||
firstRun: firstRun,
|
||||
username: name1,
|
||||
active_character: active_character,
|
||||
active_group: active_group,
|
||||
api_server: api_server,
|
||||
api_server_textgenerationwebui: api_server_textgenerationwebui,
|
||||
preset_settings: preset_settings,
|
||||
@@ -5035,6 +5078,7 @@ async function saveSettings(type) {
|
||||
world_info_depth: world_info_depth,
|
||||
world_info_budget: world_info_budget,
|
||||
world_info_recursive: world_info_recursive,
|
||||
world_info_overflow_alert: world_info_overflow_alert,
|
||||
world_info_case_sensitive: world_info_case_sensitive,
|
||||
world_info_match_whole_words: world_info_match_whole_words,
|
||||
world_info_character_strategy: world_info_character_strategy,
|
||||
@@ -5046,8 +5090,8 @@ async function saveSettings(type) {
|
||||
context_settings: context_settings,
|
||||
tags: tags,
|
||||
tag_map: tag_map,
|
||||
...nai_settings,
|
||||
...kai_settings,
|
||||
nai_settings: nai_settings,
|
||||
kai_settings: kai_settings,
|
||||
...oai_settings,
|
||||
}, null, 4),
|
||||
beforeSend: function () {
|
||||
@@ -5079,7 +5123,23 @@ async function saveSettings(type) {
|
||||
});
|
||||
}
|
||||
|
||||
export function setGenerationParamsFromPreset(preset) {
|
||||
if (preset.genamt !== undefined) {
|
||||
amount_gen = preset.genamt;
|
||||
$("#amount_gen").val(amount_gen);
|
||||
$("#amount_gen_counter").text(`${amount_gen}`);
|
||||
}
|
||||
|
||||
if (preset.max_length !== undefined) {
|
||||
max_context = preset.max_length;
|
||||
|
||||
const needsUnlock = max_context > MAX_CONTEXT_DEFAULT;
|
||||
$('#max_context_unlocked').prop('checked', needsUnlock).trigger('change');
|
||||
|
||||
$("#max_context").val(max_context);
|
||||
$("#max_context_counter").text(`${max_context}`);
|
||||
}
|
||||
}
|
||||
|
||||
function setCharacterBlockHeight() {
|
||||
const $children = $("#rm_print_characters_block").children();
|
||||
@@ -5590,11 +5650,19 @@ function onScenarioOverrideRemoveClick() {
|
||||
$(this).closest('.scenario_override').find('.chat_scenario').val('').trigger('input');
|
||||
}
|
||||
|
||||
function callPopup(text, type, inputValue = '', { okButton, rows } = {}) {
|
||||
function callPopup(text, type, inputValue = '', { okButton, rows, wide, large } = {}) {
|
||||
if (type) {
|
||||
popup_type = type;
|
||||
}
|
||||
|
||||
if (wide) {
|
||||
$("#dialogue_popup").addClass("wide_dialogue_popup");
|
||||
}
|
||||
|
||||
if (large) {
|
||||
$("#dialogue_popup").addClass("large_dialogue_popup");
|
||||
}
|
||||
|
||||
$("#dialogue_popup_cancel").css("display", "inline-block");
|
||||
switch (popup_type) {
|
||||
case "avatarToCrop":
|
||||
@@ -6703,6 +6771,11 @@ function connectAPISlash(_, text) {
|
||||
source: 'windowai',
|
||||
button: '#api_button_openai',
|
||||
},
|
||||
'openrouter': {
|
||||
selected: 'openai',
|
||||
source: 'openrouter',
|
||||
button: '#api_button_openai',
|
||||
},
|
||||
};
|
||||
|
||||
const apiConfig = apiMap[text];
|
||||
@@ -6711,11 +6784,11 @@ function connectAPISlash(_, text) {
|
||||
return;
|
||||
}
|
||||
|
||||
$("#main_api option[value='" + (apiConfig.selected || text) + "']").prop("selected", true);
|
||||
$(`#main_api option[value='${apiConfig.selected || text}']`).prop("selected", true);
|
||||
$("#main_api").trigger('change');
|
||||
|
||||
if (apiConfig.source) {
|
||||
$("#chat_completion_source option[value='" + apiConfig.source + "']").prop("selected", true);
|
||||
$(`#chat_completion_source option[value='${apiConfig.source}']`).prop("selected", true);
|
||||
$("#chat_completion_source").trigger('change');
|
||||
}
|
||||
|
||||
@@ -6839,6 +6912,73 @@ function doCharListDisplaySwitch() {
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to handle the deletion of a character, given a specific popup type and character ID.
|
||||
* If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function.
|
||||
* It fetches the delete character route, sending necessary parameters, and in case of success,
|
||||
* it proceeds to delete character from UI and saves settings.
|
||||
* In case of error during the fetch request, it logs the error details.
|
||||
*
|
||||
* @param {string} popup_type - The type of popup currently active.
|
||||
* @param {string} this_chid - The character ID to be deleted.
|
||||
*/
|
||||
export async function handleDeleteCharacter(popup_type, this_chid) {
|
||||
if (popup_type !== "del_ch") {
|
||||
return;
|
||||
}
|
||||
|
||||
const delete_chats = !!$("#del_char_checkbox").prop("checked");
|
||||
const avatar = characters[this_chid].avatar;
|
||||
const name = characters[this_chid].name;
|
||||
|
||||
const msg = { avatar_url: avatar, delete_chats: delete_chats };
|
||||
|
||||
const response = await fetch('/deletecharacter', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(msg),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await deleteCharacter(name, avatar);
|
||||
} else {
|
||||
console.error('Failed to delete character: ', response.status, response.statusText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function to delete a character from UI after character deletion API success.
|
||||
* It manages necessary UI changes such as closing advanced editing popup, unsetting
|
||||
* character ID, resetting characters array and chat metadata, deselecting character's tab
|
||||
* panel, removing character name from navigation tabs, clearing chat, removing character's
|
||||
* avatar from tag_map, fetching updated list of characters and updating the 'deleted
|
||||
* character' message.
|
||||
* It also ensures to save the settings after all the operations.
|
||||
*
|
||||
* @param {string} name - The name of the character to be deleted.
|
||||
* @param {string} avatar - The avatar URL of the character to be deleted.
|
||||
*/
|
||||
export async function deleteCharacter(name, avatar) {
|
||||
$("#character_cross").click();
|
||||
this_chid = "invalid-safety-id";
|
||||
characters.length = 0;
|
||||
name2 = systemUserName;
|
||||
chat = [...safetychat];
|
||||
chat_metadata = {};
|
||||
setRightTabSelectedClass();
|
||||
$(document.getElementById("rm_button_selected_ch")).children("h2").text("");
|
||||
clearChat();
|
||||
this_chid = undefined;
|
||||
delete tag_map[avatar];
|
||||
await getCharacters();
|
||||
select_rm_info("char_delete", name);
|
||||
printMessages();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
if (isMobile() === true) {
|
||||
@@ -6917,18 +7057,26 @@ $(document).ready(function () {
|
||||
$("#character_search_bar").on("input", function () {
|
||||
const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
|
||||
const searchValue = $(this).val().trim().toLowerCase();
|
||||
const fuzzySearchResults = power_user.fuzzy_search ? fuzzySearchCharacters(searchValue) : [];
|
||||
|
||||
function getIsValidSearch(_this) {
|
||||
const name = $(_this).find(".ch_name").text().toLowerCase();
|
||||
const chid = $(_this).attr("chid");
|
||||
|
||||
if (power_user.fuzzy_search) {
|
||||
return fuzzySearchResults.includes(parseInt(chid));
|
||||
}
|
||||
else {
|
||||
return name.includes(searchValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchValue) {
|
||||
$(selector).removeClass('hiddenBySearch');
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
} else {
|
||||
$(selector).each(function () {
|
||||
const isValidSearch = $(this)
|
||||
.find(".ch_name")
|
||||
.text()
|
||||
.toLowerCase()
|
||||
.includes(searchValue);
|
||||
|
||||
const isValidSearch = getIsValidSearch(this);
|
||||
$(this).toggleClass('hiddenBySearch', !isValidSearch);
|
||||
});
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
@@ -7114,7 +7262,7 @@ $(document).ready(function () {
|
||||
const data = { old_bg, new_bg };
|
||||
const response = await fetch('/renamebackground', {
|
||||
method: 'POST',
|
||||
headers:getRequestHeaders(),
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(data),
|
||||
cache: 'no-cache',
|
||||
});
|
||||
@@ -7210,55 +7358,7 @@ $(document).ready(function () {
|
||||
}, 200);
|
||||
}
|
||||
if (popup_type == "del_ch") {
|
||||
console.log(
|
||||
"Deleting character -- ChID: " +
|
||||
this_chid +
|
||||
" -- Name: " +
|
||||
characters[this_chid].name
|
||||
);
|
||||
const delete_chats = !!$("#del_char_checkbox").prop("checked");
|
||||
const avatar = characters[this_chid].avatar;
|
||||
const name = characters[this_chid].name;
|
||||
const msg = new FormData($("#form_create").get(0)); // ID form
|
||||
msg.append("delete_chats", delete_chats);
|
||||
jQuery.ajax({
|
||||
method: "POST",
|
||||
url: "/deletecharacter",
|
||||
beforeSend: function () {
|
||||
},
|
||||
data: msg,
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: async function (html) {
|
||||
//RossAscends: New handling of character deletion that avoids page refreshes and should have
|
||||
// fixed char corruption due to cache problems.
|
||||
//due to how it is handled with 'popup_type', i couldn't find a way to make my method completely
|
||||
// modular, so keeping it in TAI-main.js as a new default.
|
||||
//this allows for dynamic refresh of character list after deleting a character.
|
||||
// closes advanced editing popup
|
||||
$("#character_cross").click();
|
||||
// unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
|
||||
this_chid = "invalid-safety-id";
|
||||
// resets the characters array, forcing getcharacters to reset
|
||||
characters.length = 0;
|
||||
name2 = systemUserName; // replaces deleted charcter name with system user since she will be displayed next.
|
||||
chat = [...safetychat]; // sets up system user to tell user about having deleted a character
|
||||
chat_metadata = {}; // resets chat metadata
|
||||
setRightTabSelectedClass() // 'deselects' character's tab panel
|
||||
$(document.getElementById("rm_button_selected_ch"))
|
||||
.children("h2")
|
||||
.text(""); // removes character name from nav tabs
|
||||
clearChat(); // removes deleted char's chat
|
||||
this_chid = undefined; // prevents getCharacters from trying to load an invalid char.
|
||||
delete tag_map[avatar]; // removes deleted char's avatar from tag_map
|
||||
await getCharacters(); // gets the new list of characters (that doesn't include the deleted one)
|
||||
select_rm_info("char_delete", name); // also updates the 'deleted character' message
|
||||
printMessages(); // prints out system user's 'deleted character' message
|
||||
//console.log("#dialogue_popup_ok(del-char) >>>> saving");
|
||||
saveSettingsDebounced(); // saving settings to keep changes to variables
|
||||
},
|
||||
});
|
||||
handleDeleteCharacter(popup_type, this_chid, characters);
|
||||
}
|
||||
if (popup_type == "alternate_greeting" && menu_type !== "create") {
|
||||
createOrEditCharacter();
|
||||
@@ -7651,6 +7751,51 @@ $(document).ready(function () {
|
||||
else if (id == "option_delete_mes") {
|
||||
setTimeout(openMessageDelete, animation_duration);
|
||||
}
|
||||
|
||||
else if (id == "option_close_chat") {
|
||||
if (is_send_press == false) {
|
||||
clearChat();
|
||||
chat.length = 0;
|
||||
resetSelectedGroup();
|
||||
setCharacterId(undefined);
|
||||
setCharacterName('');
|
||||
setActiveCharacter(null);
|
||||
setActiveGroup(null);
|
||||
this_edit_mes_id = undefined;
|
||||
chat_metadata = {};
|
||||
selected_button = "characters";
|
||||
$("#rm_button_selected_ch").children("h2").text('');
|
||||
select_rm_characters();
|
||||
sendSystemMessage(system_message_types.WELCOME);
|
||||
} else {
|
||||
toastr.info("Please stop the message generation first.");
|
||||
}
|
||||
}
|
||||
|
||||
else if (id === "option_settings") {
|
||||
//var checkBox = document.getElementById("waifuMode");
|
||||
var topBar = document.getElementById("top-bar");
|
||||
var topSettingsHolder = document.getElementById("top-settings-holder");
|
||||
var divchat = document.getElementById("chat");
|
||||
|
||||
//if (checkBox.checked) {
|
||||
if (topBar.style.display === "none") {
|
||||
topBar.style.display = ""; // or "inline-block" if that's the original display value
|
||||
topSettingsHolder.style.display = ""; // or "inline-block" if that's the original display value
|
||||
|
||||
divchat.style.borderRadius = "";
|
||||
divchat.style.backgroundColor = "";
|
||||
|
||||
} else {
|
||||
|
||||
divchat.style.borderRadius = "10px"; // Adjust the value to control the roundness of the corners
|
||||
divchat.style.backgroundColor = ""; // Set the background color to your preference
|
||||
|
||||
topBar.style.display = "none";
|
||||
topSettingsHolder.style.display = "none";
|
||||
}
|
||||
//}
|
||||
}
|
||||
hideMenu();
|
||||
});
|
||||
|
||||
@@ -7708,13 +7853,7 @@ $(document).ready(function () {
|
||||
const preset = koboldai_settings[koboldai_setting_names[preset_settings]];
|
||||
loadKoboldSettings(preset);
|
||||
|
||||
amount_gen = preset.genamt;
|
||||
$("#amount_gen").val(amount_gen);
|
||||
$("#amount_gen_counter").text(`${amount_gen}`);
|
||||
|
||||
max_context = preset.max_length;
|
||||
$("#max_context").val(max_context);
|
||||
$("#max_context_counter").text(`${max_context}`);
|
||||
setGenerationParamsFromPreset(preset);
|
||||
|
||||
$("#range_block").find('input').prop("disabled", false);
|
||||
$("#kobold-advanced-config").find('input').prop("disabled", false);
|
||||
@@ -7907,7 +8046,7 @@ $(document).ready(function () {
|
||||
.append(
|
||||
`<textarea id='curEditTextarea' class='edit_textarea' style='max-width:auto;'></textarea>`
|
||||
);
|
||||
$('#curEditTextarea').val(text);
|
||||
$('#curEditTextarea').val(text);
|
||||
let edit_textarea = $(this)
|
||||
.closest(".mes_block")
|
||||
.find(".edit_textarea");
|
||||
@@ -8239,17 +8378,7 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
$("#dupe_button").click(async function () {
|
||||
|
||||
const body = { avatar_url: characters[this_chid].avatar };
|
||||
const response = await fetch('/dupecharacter', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (response.ok) {
|
||||
toastr.success("Character Duplicated");
|
||||
getCharacters();
|
||||
}
|
||||
await DupeChar();
|
||||
});
|
||||
|
||||
$(document).on("click", ".select_chat_block, .bookmark_link, .mes_bookmark", async function () {
|
||||
|
@@ -13,6 +13,10 @@ import {
|
||||
menu_type,
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
active_group,
|
||||
active_character,
|
||||
setActiveGroup,
|
||||
setActiveCharacter,
|
||||
} from "../script.js";
|
||||
|
||||
import {
|
||||
@@ -239,7 +243,6 @@ $("#rm_ch_create_block").on("input", function () { countTokensDebounced(); });
|
||||
$("#character_popup").on("input", function () { countTokensDebounced(); });
|
||||
//function:
|
||||
export function RA_CountCharTokens() {
|
||||
$("#result_info").html("");
|
||||
//console.log('RA_TC -- starting with this_chid = ' + this_chid);
|
||||
if (menu_type === "create") { //if new char
|
||||
function saveFormVariables() {
|
||||
@@ -331,11 +334,21 @@ export function RA_CountCharTokens() {
|
||||
characterStatsHandler(characters, this_chid);
|
||||
});
|
||||
}
|
||||
//Auto Load Last Charcter -- (fires when active_character is defined and auto_load_chat is true)
|
||||
/**
|
||||
* Auto load chat with the last active character or group.
|
||||
* Fires when active_character is defined and auto_load_chat is true.
|
||||
* The function first tries to find a character with a specific ID from the global settings.
|
||||
* If it doesn't exist, it tries to find a group with a specific grid from the global settings.
|
||||
* If the character list hadn't been loaded yet, it calls itself again after 100ms delay.
|
||||
* The character or group is selected (clicked) if it is found.
|
||||
*/
|
||||
async function RA_autoloadchat() {
|
||||
if (document.getElementById('CharID0') !== null) {
|
||||
var charToAutoLoad = document.getElementById('CharID' + LoadLocal('ActiveChar'));
|
||||
let groupToAutoLoad = document.querySelector(`.group_select[grid="${LoadLocal('ActiveGroup')}"]`);
|
||||
// active character is the name, we should look it up in the character list and get the id
|
||||
let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
|
||||
|
||||
var charToAutoLoad = document.getElementById('CharID' + active_character_id);
|
||||
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
|
||||
if (charToAutoLoad != null) {
|
||||
$(charToAutoLoad).click();
|
||||
}
|
||||
@@ -343,7 +356,7 @@ async function RA_autoloadchat() {
|
||||
$(groupToAutoLoad).click();
|
||||
}
|
||||
|
||||
// if the charcter list hadn't been loaded yet, try again.
|
||||
// if the character list hadn't been loaded yet, try again.
|
||||
} else { setTimeout(RA_autoloadchat, 100); }
|
||||
}
|
||||
|
||||
@@ -448,8 +461,8 @@ function RA_autoconnect(PrevApi) {
|
||||
}
|
||||
break;
|
||||
case 'openai':
|
||||
if ((secret_state[SECRET_KEYS.OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.OPENAI)
|
||||
|| (secret_state[SECRET_KEYS.CLAUDE] && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE)
|
||||
if (((secret_state[SECRET_KEYS.OPENAI] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.OPENAI)
|
||||
|| ((secret_state[SECRET_KEYS.CLAUDE] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE)
|
||||
|| (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|
||||
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|
||||
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
|
||||
@@ -904,16 +917,22 @@ $("document").ready(function () {
|
||||
$("#rm_button_characters").click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); });
|
||||
|
||||
// when a char is selected from the list, save them as the auto-load character for next page load
|
||||
|
||||
// when a char is selected from the list, save their name as the auto-load character for next page load
|
||||
$(document).on("click", ".character_select", function () {
|
||||
SaveLocal('ActiveChar', $(this).attr('chid'));
|
||||
SaveLocal('ActiveGroup', null);
|
||||
setActiveCharacter($(this).find('.avatar').attr('title'));
|
||||
setActiveGroup(null);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on("click", ".group_select", function () {
|
||||
SaveLocal('ActiveChar', null);
|
||||
SaveLocal('ActiveGroup', $(this).data('id'));
|
||||
setActiveCharacter(null);
|
||||
setActiveGroup($(this).data('id'));
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
|
||||
|
||||
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
|
||||
$('#send_textarea').on('input', function () {
|
||||
this.style.height = '40px';
|
||||
@@ -1029,11 +1048,10 @@ $("document").ready(function () {
|
||||
}
|
||||
|
||||
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
|
||||
console.debug('got ctrl+uparrow input');
|
||||
if (
|
||||
$("#send_textarea").val() === '' &&
|
||||
chatbarInFocus === true &&
|
||||
$(".swipe_right:last").css('display') === 'flex' &&
|
||||
($(".swipe_right:last").css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') &&
|
||||
$("#character_popup").css("display") === "none" &&
|
||||
$("#shadow_select_chat_popup").css("display") === "none"
|
||||
) {
|
||||
@@ -1041,7 +1059,7 @@ $("document").ready(function () {
|
||||
const lastIsUserMes = isUserMesList[isUserMesList.length - 1];
|
||||
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
|
||||
if (editMes !== null) {
|
||||
$(editMes).click();
|
||||
$(editMes).trigger('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1062,5 +1080,59 @@ $("document").ready(function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key == "Escape") { //closes various panels
|
||||
if ($("#curEditTextarea").is(":visible")) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($("#dialogue_popup").is(":visible")) {
|
||||
if ($("#dialogue_popup_cancel").is(":visible")) {
|
||||
$("#dialogue_popup_cancel").trigger('click');
|
||||
return
|
||||
} else {
|
||||
$("#dialogue_popup_ok").trigger('click')
|
||||
return
|
||||
}
|
||||
}
|
||||
if ($("#select_chat_popup").is(":visible")) {
|
||||
$("#select_chat_cross").trigger('click');
|
||||
return
|
||||
}
|
||||
if ($("#character_popup").is(":visible")) {
|
||||
$("#character_cross").trigger('click');
|
||||
return
|
||||
}
|
||||
|
||||
if ($(".drawer-content")
|
||||
.not('#WorldInfo')
|
||||
.not('#left-nav-panel')
|
||||
.not('#right-nav-panel')
|
||||
.is(":visible")) {
|
||||
let visibleDrawerContent = $(".drawer-content:visible")
|
||||
.not('#WorldInfo')
|
||||
.not('#left-nav-panel')
|
||||
.not('#right-nav-panel')
|
||||
$(visibleDrawerContent).parent().find('.drawer-icon').trigger('click');
|
||||
return
|
||||
}
|
||||
|
||||
if ($("#floatingPrompt").is(":visible")) {
|
||||
$("#ANClose").trigger('click');
|
||||
return
|
||||
}
|
||||
if ($("#WorldInfo").is(":visible")) {
|
||||
$("#WIDrawerIcon").trigger('click');
|
||||
return
|
||||
}
|
||||
if ($("#left-nav-panel").is(":visible")) {
|
||||
$("#leftNavDrawerIcon").trigger('click');
|
||||
return
|
||||
}
|
||||
if ($("#right-nav-panel").is(":visible")) {
|
||||
$("#rightNavDrawerIcon").trigger('click');
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -60,7 +60,10 @@ const extension_settings = {
|
||||
dice: {},
|
||||
regex: [],
|
||||
tts: {},
|
||||
sd: {},
|
||||
sd: {
|
||||
prompts: {},
|
||||
character_prompts: {},
|
||||
},
|
||||
chromadb: {},
|
||||
translate: {},
|
||||
objective: {},
|
||||
@@ -70,6 +73,7 @@ const extension_settings = {
|
||||
fluctuation: 0.1,
|
||||
enabled: false,
|
||||
},
|
||||
speech_recognition: {},
|
||||
};
|
||||
|
||||
let modules = [];
|
||||
|
@@ -551,10 +551,10 @@ async function onSelectInjectFile(e) {
|
||||
function doAutoAdjust(chat, maxContext) {
|
||||
console.debug('CHROMADB: Auto-adjusting sliders (messages: %o, maxContext: %o)', chat.length, maxContext);
|
||||
// Get mean message length
|
||||
const meanMessageLength = chat.reduce((acc, cur) => acc + cur.mes.length, 0) / chat.length;
|
||||
const meanMessageLength = chat.reduce((acc, cur) => acc + (cur?.mes?.length ?? 0), 0) / chat.length;
|
||||
|
||||
if (Number.isNaN(meanMessageLength)) {
|
||||
console.debug('CHROMADB: Mean message length is NaN, aborting auto-adjust');
|
||||
if (Number.isNaN(meanMessageLength) || meanMessageLength === 0) {
|
||||
console.debug('CHROMADB: Mean message length is zero or NaN, aborting auto-adjust');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -611,7 +611,7 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
console.debug("CHROMADB: Messages to store length vs keep context: %o vs %o", messagesToStore.length, extension_settings.chromadb.keep_context);
|
||||
await addMessages(currentChatId, messagesToStore);
|
||||
}
|
||||
|
||||
|
||||
const lastMessage = chat[chat.length - 1];
|
||||
|
||||
let queriedMessages;
|
||||
@@ -811,15 +811,15 @@ jQuery(async () => {
|
||||
<textarea id="chromadb_custom_msg" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_msg}" style="height: 61px; display: none;"></textarea>
|
||||
<label for="chromadb_custom_depth" hidden><small>How deep should the memory messages be injected?: (<span id="chromadb_custom_depth_value"></span>)</small></label>
|
||||
<input id="chromadb_custom_depth" type="range" min="${defaultSettings.chroma_depth_min}" max="${defaultSettings.chroma_depth_max}" step="${defaultSettings.chroma_depth_step}" value="${defaultSettings.chroma_depth}" hidden/>
|
||||
|
||||
|
||||
<label for="chromadb_hhaa_wrapperfmt" hidden><small>Custom wrapper format:</small></label>
|
||||
<textarea id="chromadb_hhaa_wrapperfmt" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_hhaa_wrapper}" style="height: 61px; display: none;"></textarea>
|
||||
<label for="chromadb_hhaa_memoryfmt" hidden><small>Custom memory format:</small></label>
|
||||
<textarea id="chromadb_hhaa_memoryfmt" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_hhaa_memory}" style="height: 61px; display: none;"></textarea>
|
||||
<label for="chromadb_hhaa_token_limit" hidden><small>Maximum tokens allowed for memories: (<span id="chromadb_hhaa_token_limit_value"></span>)</small></label>
|
||||
<input id="chromadb_hhaa_token_limit" type="range" min="0" max="2048" step="64" value="${defaultSettings.hhaa_token_limit}" hidden/>
|
||||
|
||||
|
||||
|
||||
|
||||
<span>Memory Recall Strategy</span>
|
||||
<select id="chromadb_recall_strategy">
|
||||
<option value="original">Recall only from this chat</option>
|
||||
@@ -834,7 +834,7 @@ jQuery(async () => {
|
||||
<input id="chromadb_keep_context" type="range" min="${defaultSettings.keep_context_min}" max="${defaultSettings.keep_context_max}" step="${defaultSettings.keep_context_step}" value="${defaultSettings.keep_context}" />
|
||||
<label for="chromadb_n_results"><small>Maximum number of ChromaDB 'memories' to inject: (<span id="chromadb_n_results_value"></span>) messages</small></label>
|
||||
<input id="chromadb_n_results" type="range" min="${defaultSettings.n_results_min}" max="${defaultSettings.n_results_max}" step="${defaultSettings.n_results_step}" value="${defaultSettings.n_results}" />
|
||||
|
||||
|
||||
<label for="chromadb_keep_context_proportion"><small>Keep (<span id="chromadb_keep_context_proportion_value"></span>%) of in-context chat messages; replace the rest with memories</small></label>
|
||||
<input id="chromadb_keep_context_proportion" type="range" min="${defaultSettings.keep_context_proportion_min}" max="${defaultSettings.keep_context_proportion_max}" step="${defaultSettings.keep_context_proportion_step}" value="${defaultSettings.keep_context_proportion}" />
|
||||
<label for="chromadb_split_length"><small>Max length for each 'memory' pulled from the current chat history: (<span id="chromadb_split_length_value"></span>) characters</small></label>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { getStringHash, debounce, waitUntilCondition } from "../../utils.js";
|
||||
import { getStringHash, debounce, waitUntilCondition, extractAllWords } from "../../utils.js";
|
||||
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
|
||||
export { MODULE_NAME };
|
||||
@@ -12,7 +12,21 @@ let lastMessageHash = null;
|
||||
let lastMessageId = null;
|
||||
let inApiCall = false;
|
||||
|
||||
const formatMemoryValue = (value) => value ? `Summary: ${value.trim()}` : '';
|
||||
const formatMemoryValue = function (value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
value = value.trim();
|
||||
|
||||
if (extension_settings.memory.template) {
|
||||
let result = extension_settings.memory.template.replace(/{{summary}}/i, value);
|
||||
return substituteParams(result);
|
||||
} else {
|
||||
return `Summary: ${value}`;
|
||||
}
|
||||
}
|
||||
|
||||
const saveChatDebounced = debounce(() => getContext().saveChat(), 2000);
|
||||
|
||||
const summary_sources = {
|
||||
@@ -21,6 +35,7 @@ const summary_sources = {
|
||||
};
|
||||
|
||||
const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';
|
||||
const defaultTemplate = '[Summary: {{summary}}]';
|
||||
|
||||
const defaultSettings = {
|
||||
minLongMemory: 16,
|
||||
@@ -46,6 +61,9 @@ const defaultSettings = {
|
||||
memoryFrozen: false,
|
||||
source: summary_sources.extras,
|
||||
prompt: defaultPrompt,
|
||||
template: defaultTemplate,
|
||||
position: extension_prompt_types.AFTER_SCENARIO,
|
||||
depth: 2,
|
||||
promptWords: 200,
|
||||
promptMinWords: 25,
|
||||
promptMaxWords: 1000,
|
||||
@@ -54,6 +72,10 @@ const defaultSettings = {
|
||||
promptMinInterval: 1,
|
||||
promptMaxInterval: 100,
|
||||
promptIntervalStep: 1,
|
||||
promptForceWords: 0,
|
||||
promptForceWordsStep: 100,
|
||||
promptMinForceWords: 0,
|
||||
promptMaxForceWords: 10000,
|
||||
};
|
||||
|
||||
function loadSettings() {
|
||||
@@ -61,20 +83,10 @@ function loadSettings() {
|
||||
Object.assign(extension_settings.memory, defaultSettings);
|
||||
}
|
||||
|
||||
if (extension_settings.memory.source === undefined) {
|
||||
extension_settings.memory.source = defaultSettings.source;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.prompt === undefined) {
|
||||
extension_settings.memory.prompt = defaultSettings.prompt;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.promptWords === undefined) {
|
||||
extension_settings.memory.promptWords = defaultSettings.promptWords;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.promptInterval === undefined) {
|
||||
extension_settings.memory.promptInterval = defaultSettings.promptInterval;
|
||||
for (const key of Object.keys(defaultSettings)) {
|
||||
if (extension_settings.memory[key] === undefined) {
|
||||
extension_settings.memory[key] = defaultSettings[key];
|
||||
}
|
||||
}
|
||||
|
||||
$('#summary_source').val(extension_settings.memory.source).trigger('change');
|
||||
@@ -87,6 +99,10 @@ function loadSettings() {
|
||||
$('#memory_prompt').val(extension_settings.memory.prompt).trigger('input');
|
||||
$('#memory_prompt_words').val(extension_settings.memory.promptWords).trigger('input');
|
||||
$('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input');
|
||||
$('#memory_template').val(extension_settings.memory.template).trigger('input');
|
||||
$('#memory_depth').val(extension_settings.memory.depth).trigger('input');
|
||||
$(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input');
|
||||
$('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input');
|
||||
}
|
||||
|
||||
function onSummarySourceChange(event) {
|
||||
@@ -170,6 +186,31 @@ function onMemoryPromptInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryTemplateInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.template = value;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryDepthInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.depth = Number(value);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPositionChange(e) {
|
||||
const value = e.target.value;
|
||||
extension_settings.memory.position = value;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPromptWordsForceInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.promptForceWords = Number(value);
|
||||
$('#memory_prompt_words_force_value').text(extension_settings.memory.promptForceWords);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function saveLastValues() {
|
||||
const context = getContext();
|
||||
lastGroupId = context.groupId;
|
||||
@@ -310,19 +351,30 @@ async function summarizeChatMain(context, force) {
|
||||
}
|
||||
|
||||
let messagesSinceLastSummary = 0;
|
||||
let wordsSinceLastSummary = 0;
|
||||
let conditionSatisfied = false;
|
||||
for (let i = context.chat.length - 1; i >= 0; i--) {
|
||||
if (context.chat[i].extra && context.chat[i].extra.memory) {
|
||||
break;
|
||||
}
|
||||
messagesSinceLastSummary++;
|
||||
wordsSinceLastSummary += extractAllWords(context.chat[i].mes).length;
|
||||
}
|
||||
|
||||
if (messagesSinceLastSummary < extension_settings.memory.promptInterval && !force) {
|
||||
console.debug(`Not enough messages since last summary (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}`);
|
||||
if (messagesSinceLastSummary >= extension_settings.memory.promptInterval) {
|
||||
conditionSatisfied = true;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.promptForceWords && wordsSinceLastSummary >= extension_settings.memory.promptForceWords) {
|
||||
conditionSatisfied = true;
|
||||
}
|
||||
|
||||
if (!conditionSatisfied && !force) {
|
||||
console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary);
|
||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
|
||||
const prompt = substituteParams(extension_settings.memory.prompt)
|
||||
.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
|
||||
@@ -458,9 +510,11 @@ function onMemoryContentInput() {
|
||||
|
||||
function setMemoryContext(value, saveToMessage) {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO);
|
||||
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth);
|
||||
$('#memory_contents').val(value);
|
||||
console.log('Memory set to: ' + value);
|
||||
console.log('Summary set to: ' + value);
|
||||
console.debug('Position: ' + extension_settings.memory.position);
|
||||
console.debug('Depth: ' + extension_settings.memory.depth);
|
||||
|
||||
if (saveToMessage && context.chat.length) {
|
||||
const idx = context.chat.length - 2;
|
||||
@@ -496,6 +550,21 @@ jQuery(function () {
|
||||
<input id="memory_restore" class="menu_button" type="button" value="Restore previous state" />
|
||||
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Pause summarization</label>
|
||||
</div>
|
||||
<div class="memory_template">
|
||||
<label for="memory_template">Injection template:</label>
|
||||
<textarea id="memory_template" class="text_pole textarea_compact" rows="1" placeholder="Use {{summary}} macro to specify the position of summarized text."></textarea>
|
||||
</div>
|
||||
<label for="memory_position">Injection position:</label>
|
||||
<div class="radio_group">
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="0" />
|
||||
After scenario
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="memory_position" value="1" />
|
||||
In-chat @ Depth <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
</label>
|
||||
</div>
|
||||
<div data-source="main" class="memory_contents_controls">
|
||||
</div>
|
||||
<div data-source="main">
|
||||
@@ -511,6 +580,9 @@ jQuery(function () {
|
||||
<input id="memory_prompt_words" type="range" value="${defaultSettings.promptWords}" min="${defaultSettings.promptMinWords}" max="${defaultSettings.promptMaxWords}" step="${defaultSettings.promptWordsStep}" />
|
||||
<label for="memory_prompt_interval">Update interval (<span id="memory_prompt_interval_value"></span> messages)</label>
|
||||
<input id="memory_prompt_interval" type="range" value="${defaultSettings.promptInterval}" min="${defaultSettings.promptMinInterval}" max="${defaultSettings.promptMaxInterval}" step="${defaultSettings.promptIntervalStep}" />
|
||||
<label for="memory_prompt_words_force">Force update after (<span id="memory_prompt_words_force_value"></span> words)</label>
|
||||
<small>Set to 0 to disable</small>
|
||||
<input id="memory_prompt_words_force" type="range" value="${defaultSettings.promptForceWords}" min="${defaultSettings.promptMinForceWords}" max="${defaultSettings.promptMaxForceWords}" step="${defaultSettings.promptForceWordsStep}" />
|
||||
</div>
|
||||
<div data-source="extras">
|
||||
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
|
||||
@@ -542,6 +614,10 @@ jQuery(function () {
|
||||
$('#memory_prompt_interval').on('input', onMemoryPromptIntervalInput);
|
||||
$('#memory_prompt').on('input', onMemoryPromptInput);
|
||||
$('#memory_force_summarize').on('click', forceSummarizeChat);
|
||||
$('#memory_template').on('input', onMemoryTemplateInput);
|
||||
$('#memory_depth').on('input', onMemoryDepthInput);
|
||||
$('input[name="memory_position"]').on('change', onMemoryPositionChange);
|
||||
$('#memory_prompt_words_force').on('input', onMemoryPromptWordsForceInput);
|
||||
}
|
||||
|
||||
addExtensionControls();
|
||||
|
@@ -1,34 +1,26 @@
|
||||
#memory_settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#memory_settings textarea {
|
||||
font-size: calc(var(--mainFontSize) * 0.9);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
#memory_settings input[type="range"] {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#memory_settings label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label[for="memory_frozen"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
label[for="memory_frozen"] input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.memory_contents_controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#memory_settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#memory_settings textarea {
|
||||
font-size: calc(var(--mainFontSize) * 0.9);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
label[for="memory_frozen"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
label[for="memory_frozen"] input {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.memory_contents_controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { chat_metadata } from "../../../script.js";
|
||||
import { chat_metadata, callPopup, saveSettingsDebounced, getCurrentChatId } from "../../../script.js";
|
||||
import { getContext, extension_settings, saveMetadataDebounced } from "../../extensions.js";
|
||||
import {
|
||||
substituteParams,
|
||||
@@ -11,92 +11,92 @@ import { registerSlashCommand } from "../../slash-commands.js";
|
||||
const MODULE_NAME = "Objective"
|
||||
|
||||
|
||||
let globalObjective = ""
|
||||
let taskTree = null
|
||||
let globalTasks = []
|
||||
let currentChatId = ""
|
||||
let currentObjective = null
|
||||
let currentTask = null
|
||||
let checkCounter = 0
|
||||
|
||||
|
||||
const objectivePrompts = {
|
||||
const defaultPrompts = {
|
||||
"createTask": `Pause your roleplay and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.
|
||||
|
||||
The objective that you must make a numbered task list for is: [{{objective}}].
|
||||
The tasks created should take into account the character traits of {{char}}. These tasks may or may not involve {{user}} directly. Be sure to include the objective as the final task.
|
||||
The objective that you must make a numbered task list for is: [{{objective}}].
|
||||
The tasks created should take into account the character traits of {{char}}. These tasks may or may not involve {{user}} directly. Be sure to include the objective as the final task.
|
||||
|
||||
Given an example objective of 'Make me a four course dinner', here is an example output:
|
||||
1. Determine what the courses will be
|
||||
2. Find recipes for each course
|
||||
3. Go shopping for supplies with {{user}}
|
||||
4. Cook the food
|
||||
5. Get {{user}} to set the table
|
||||
6. Serve the food
|
||||
7. Enjoy eating the meal with {{user}}
|
||||
Given an example objective of 'Make me a four course dinner', here is an example output:
|
||||
1. Determine what the courses will be
|
||||
2. Find recipes for each course
|
||||
3. Go shopping for supplies with {{user}}
|
||||
4. Cook the food
|
||||
5. Get {{user}} to set the table
|
||||
6. Serve the food
|
||||
7. Enjoy eating the meal with {{user}}
|
||||
`,
|
||||
"checkTaskCompleted": `Pause your roleplay. Determine if this task is completed: [{{task}}].
|
||||
To do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.
|
||||
Example output:
|
||||
true
|
||||
`
|
||||
To do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.
|
||||
Example output:
|
||||
true
|
||||
`,
|
||||
'currentTask':`Your current task is [{{task}}]. Balance existing roleplay with completing this task.`
|
||||
}
|
||||
|
||||
const extensionPrompt = "Your current task is [{{task}}]. Balance existing roleplay with completing this task."
|
||||
|
||||
let objectivePrompts = defaultPrompts
|
||||
|
||||
//###############################//
|
||||
//# Task Management #//
|
||||
//###############################//
|
||||
|
||||
// Accepts optional index. Defaults to adding to end of list.
|
||||
function addTask(description, index = null) {
|
||||
index = index != null ? index: index = globalTasks.length
|
||||
globalTasks.splice(index, 0, new ObjectiveTask(
|
||||
{description: description}
|
||||
))
|
||||
saveState()
|
||||
}
|
||||
|
||||
// Return the task and index or throw an error
|
||||
function getTaskById(taskId){
|
||||
if (taskId == null) {
|
||||
throw `Null task id`
|
||||
}
|
||||
const index = globalTasks.findIndex((task) => task.id === taskId);
|
||||
if (index !== -1) {
|
||||
return { task: globalTasks[index], index: index };
|
||||
} else {
|
||||
throw `Cannot find task with ${taskId}`
|
||||
|
||||
}
|
||||
return getTaskByIdRecurse(taskId, taskTree)
|
||||
}
|
||||
|
||||
function deleteTask(taskId){
|
||||
const { task, index } = getTaskById(taskId)
|
||||
function getTaskByIdRecurse(taskId, task) {
|
||||
if (task.id == taskId){
|
||||
return task
|
||||
}
|
||||
for (const childTask of task.children) {
|
||||
const foundTask = getTaskByIdRecurse(taskId, childTask);
|
||||
if (foundTask != null) {
|
||||
return foundTask;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
globalTasks.splice(index, 1)
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
function substituteParamsPrompts(content) {
|
||||
content = content.replace(/{{objective}}/gi, currentObjective.description)
|
||||
content = content.replace(/{{task}}/gi, currentTask.description)
|
||||
content = substituteParams(content)
|
||||
return content
|
||||
}
|
||||
|
||||
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
|
||||
async function generateTasks() {
|
||||
const prompt = substituteParams(objectivePrompts["createTask"].replace(/{{objective}}/gi, globalObjective));
|
||||
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.createTask);
|
||||
console.log(`Generating tasks for objective with prompt`)
|
||||
toastr.info('Generating tasks for objective', 'Please wait...');
|
||||
const taskResponse = await generateQuietPrompt(prompt)
|
||||
|
||||
// Clear all existing global tasks when generating
|
||||
globalTasks = []
|
||||
|
||||
// Clear all existing objective tasks when generating
|
||||
currentObjective.children = []
|
||||
const numberedListPattern = /^\d+\./
|
||||
|
||||
// Create tasks from generated task list
|
||||
for (const task of taskResponse.split('\n').map(x => x.trim())) {
|
||||
if (task.match(numberedListPattern) != null) {
|
||||
addTask(task.replace(numberedListPattern,"").trim())
|
||||
currentObjective.addTask(task.replace(numberedListPattern,"").trim())
|
||||
}
|
||||
}
|
||||
updateUiTaskList()
|
||||
console.info(`Response for Objective: '${globalObjective}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
|
||||
updateUiTaskList();
|
||||
setCurrentTask();
|
||||
console.info(`Response for Objective: '${taskTree.description}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
|
||||
toastr.success(`Generated ${globalTasks.length} tasks`, 'Done!');
|
||||
}
|
||||
|
||||
@@ -107,13 +107,14 @@ async function checkTaskCompleted() {
|
||||
return
|
||||
}
|
||||
checkCounter = $('#objective-check-frequency').val()
|
||||
toastr.info("Checking for task completion.")
|
||||
|
||||
const prompt = substituteParams(objectivePrompts["checkTaskCompleted"].replace(/{{task}}/gi, currentTask.description));
|
||||
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted);
|
||||
const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase()
|
||||
|
||||
// Check response if task complete
|
||||
if (taskResponse.includes("true")) {
|
||||
console.info(`Character determined task '${JSON.stringify(currentTask.toSaveState())} is completed.`)
|
||||
console.info(`Character determined task '${currentTask.description} is completed.`)
|
||||
currentTask.completeTask()
|
||||
} else if (!(taskResponse.includes("false"))) {
|
||||
console.warn(`checkTaskCompleted response did not contain true or false. taskResponse: ${taskResponse}`)
|
||||
@@ -122,27 +123,57 @@ async function checkTaskCompleted() {
|
||||
}
|
||||
}
|
||||
|
||||
function getNextIncompleteTaskRecurse(task){
|
||||
if (task.completed === false // Return task if incomplete
|
||||
&& task.children.length === 0 // Ensure task has no children, it's subtasks will determine completeness
|
||||
&& task.parentId // Must have parent id. Only root task will be missing this and we dont want that
|
||||
){
|
||||
return task
|
||||
}
|
||||
for (const childTask of task.children) {
|
||||
if (childTask.completed === true){ // Don't recurse into completed tasks
|
||||
continue
|
||||
}
|
||||
const foundTask = getNextIncompleteTaskRecurse(childTask);
|
||||
if (foundTask != null) {
|
||||
return foundTask;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set a task in extensionPrompt context. Defaults to first incomplete
|
||||
function setCurrentTask(taskId = null) {
|
||||
const context = getContext();
|
||||
|
||||
// TODO: Should probably null this rather than set empty object
|
||||
currentTask = {};
|
||||
|
||||
// Set current task to either the next incomplete task, or the index
|
||||
// Find the task, either next incomplete, or by provided taskId
|
||||
if (taskId === null) {
|
||||
currentTask = globalTasks.find(task => !task.completed) || {};
|
||||
currentTask = getNextIncompleteTaskRecurse(taskTree) || {};
|
||||
} else {
|
||||
const { _, index } = getTaskById(taskId)
|
||||
currentTask = globalTasks[index];
|
||||
currentTask = getTaskById(taskId);
|
||||
}
|
||||
|
||||
// Get the task description and add to extension prompt
|
||||
// Don't just check for a current task, check if it has data
|
||||
const description = currentTask.description || null;
|
||||
|
||||
// Now update the extension prompt
|
||||
|
||||
if (description) {
|
||||
const extensionPromptText = extensionPrompt.replace(/{{task}}/gi, description);
|
||||
const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask);
|
||||
|
||||
// Remove highlights
|
||||
$('.objective-task').css({'border-color':'','border-width':''})
|
||||
// Highlight current task
|
||||
let highlightTask = currentTask
|
||||
while (highlightTask.parentId !== ""){
|
||||
if (highlightTask.descriptionSpan){
|
||||
highlightTask.descriptionSpan.css({'border-color':'yellow','border-width':'2px'});
|
||||
}
|
||||
const parent = getTaskById(highlightTask.parentId)
|
||||
highlightTask = parent
|
||||
}
|
||||
|
||||
// Update the extension prompt
|
||||
context.setExtensionPrompt(MODULE_NAME, extensionPromptText, 1, $('#objective-chat-depth').val());
|
||||
console.info(`Current task in context.extensionPrompts.Objective is ${JSON.stringify(context.extensionPrompts.Objective)}`);
|
||||
} else {
|
||||
@@ -153,22 +184,26 @@ function setCurrentTask(taskId = null) {
|
||||
saveState();
|
||||
}
|
||||
|
||||
let taskIdCounter = 0
|
||||
function getNextTaskId(){
|
||||
// Make sure id does not exist
|
||||
while (globalTasks.find(task => task.id == taskIdCounter) != undefined) {
|
||||
taskIdCounter += 1
|
||||
function getHighestTaskIdRecurse(task) {
|
||||
let nextId = task.id;
|
||||
|
||||
for (const childTask of task.children) {
|
||||
const childId = getHighestTaskIdRecurse(childTask);
|
||||
if (childId > nextId) {
|
||||
nextId = childId;
|
||||
}
|
||||
}
|
||||
const nextId = taskIdCounter
|
||||
console.log(`TaskID assigned: ${nextId}`)
|
||||
taskIdCounter += 1
|
||||
return nextId
|
||||
return nextId;
|
||||
}
|
||||
|
||||
//###############################//
|
||||
//# Task Class #//
|
||||
//###############################//
|
||||
class ObjectiveTask {
|
||||
id
|
||||
description
|
||||
completed
|
||||
parent
|
||||
parentId
|
||||
children
|
||||
|
||||
// UI Elements
|
||||
@@ -178,25 +213,67 @@ class ObjectiveTask {
|
||||
deleteTaskButton
|
||||
addTaskButton
|
||||
|
||||
constructor ({id=undefined, description, completed=false, parent=null}) {
|
||||
constructor ({id=undefined, description, completed=false, parentId=""}) {
|
||||
this.description = description
|
||||
this.parent = parent
|
||||
this.parentId = parentId
|
||||
this.children = []
|
||||
this.completed = completed
|
||||
|
||||
// Generate a new ID if none specified
|
||||
if (id==undefined){
|
||||
this.id = getNextTaskId()
|
||||
this.id = getHighestTaskIdRecurse(taskTree) + 1
|
||||
} else {
|
||||
this.id=id
|
||||
}
|
||||
}
|
||||
|
||||
// Accepts optional index. Defaults to adding to end of list.
|
||||
addTask(description, index = null) {
|
||||
index = index != null ? index: index = this.children.length
|
||||
this.children.splice(index, 0, new ObjectiveTask(
|
||||
{description: description, parentId: this.id}
|
||||
))
|
||||
saveState()
|
||||
}
|
||||
|
||||
getIndex(){
|
||||
if (this.parentId !== null) {
|
||||
const parent = getTaskById(this.parentId)
|
||||
const index = parent.children.findIndex(task => task.id === this.id)
|
||||
if (index === -1){
|
||||
throw `getIndex failed: Task '${this.description}' not found in parent task '${parent.description}'`
|
||||
}
|
||||
return index
|
||||
} else {
|
||||
throw `getIndex failed: Task '${this.description}' has no parent`
|
||||
}
|
||||
}
|
||||
|
||||
// Used to set parent to complete when all child tasks are completed
|
||||
checkParentComplete() {
|
||||
let all_completed = true;
|
||||
if (this.parentId !== ""){
|
||||
const parent = getTaskById(this.parentId);
|
||||
for (const child of parent.children){
|
||||
if (!child.completed){
|
||||
all_completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_completed){
|
||||
parent.completed = true;
|
||||
console.info(`Parent task '${parent.description}' completed after all child tasks complated.`)
|
||||
} else {
|
||||
parent.completed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the current task, setting next task to next incomplete task
|
||||
completeTask() {
|
||||
this.completed = true
|
||||
console.info(`Task successfully completed: ${JSON.stringify(this.description)}`)
|
||||
this.checkParentComplete()
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
}
|
||||
@@ -206,9 +283,10 @@ class ObjectiveTask {
|
||||
const template = `
|
||||
<div id="objective-task-label-${this.id}" class="flex1 checkbox_label">
|
||||
<input id="objective-task-complete-${this.id}" type="checkbox">
|
||||
<span class="text_pole" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span>
|
||||
<span class="text_pole objective-task" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span>
|
||||
<div id="objective-task-delete-${this.id}" class="objective-task-button fa-solid fa-xmark fa-2x" title="Delete Task"></div>
|
||||
<div id="objective-task-add-${this.id}" class="objective-task-button fa-solid fa-plus fa-2x" title="Add Task"></div>
|
||||
<div id="objective-task-add-branch-${this.id}" class="objective-task-button fa-solid fa-code-fork fa-2x" title="Branch Task"></div>
|
||||
</div><br>
|
||||
`;
|
||||
|
||||
@@ -219,6 +297,15 @@ class ObjectiveTask {
|
||||
this.descriptionSpan = $(`#objective-task-description-${this.id}`);
|
||||
this.addButton = $(`#objective-task-add-${this.id}`);
|
||||
this.deleteButton = $(`#objective-task-delete-${this.id}`);
|
||||
this.taskHtml = $(`#objective-task-label-${this.id}`);
|
||||
this.branchButton = $(`#objective-task-add-branch-${this.id}`)
|
||||
|
||||
// Handle sub-task forking style
|
||||
if (this.children.length > 0){
|
||||
this.branchButton.css({'color':'#33cc33'})
|
||||
} else {
|
||||
this.branchButton.css({'color':''})
|
||||
}
|
||||
|
||||
// Add event listeners and set properties
|
||||
$(`#objective-task-complete-${this.id}`).prop('checked', this.completed);
|
||||
@@ -227,52 +314,201 @@ class ObjectiveTask {
|
||||
$(`#objective-task-description-${this.id}`).on('focusout', () => (this.onDescriptionFocusout()));
|
||||
$(`#objective-task-delete-${this.id}`).on('click', () => (this.onDeleteClick()));
|
||||
$(`#objective-task-add-${this.id}`).on('click', () => (this.onAddClick()));
|
||||
this.branchButton.on('click', () => (this.onBranchClick()))
|
||||
}
|
||||
|
||||
onBranchClick() {
|
||||
currentObjective = this
|
||||
updateUiTaskList();
|
||||
setCurrentTask();
|
||||
}
|
||||
|
||||
onCompleteClick(){
|
||||
this.completed = this.completedCheckbox.prop('checked')
|
||||
this.checkParentComplete()
|
||||
setCurrentTask();
|
||||
}
|
||||
|
||||
onDescriptionUpdate(){
|
||||
this.description = this.descriptionSpan.text();
|
||||
}
|
||||
|
||||
onDescriptionFocusout(){
|
||||
setCurrentTask();
|
||||
}
|
||||
|
||||
onDeleteClick(){
|
||||
deleteTask(this.id);
|
||||
const index = this.getIndex()
|
||||
const parent = getTaskById(this.parentId)
|
||||
parent.children.splice(index, 1)
|
||||
updateUiTaskList()
|
||||
setCurrentTask()
|
||||
}
|
||||
|
||||
onAddClick(){
|
||||
const {_, index} = getTaskById(this.id)
|
||||
addTask("", index + 1);
|
||||
setCurrentTask();
|
||||
const index = this.getIndex()
|
||||
const parent = getTaskById(this.parentId)
|
||||
parent.addTask("", index + 1);
|
||||
updateUiTaskList();
|
||||
setCurrentTask();
|
||||
}
|
||||
|
||||
toSaveState() {
|
||||
toSaveStateRecurse() {
|
||||
let children = []
|
||||
if (this.children.length > 0){
|
||||
for (const child of this.children){
|
||||
children.push(child.toSaveStateRecurse())
|
||||
}
|
||||
}
|
||||
return {
|
||||
"id":this.id,
|
||||
"description":this.description,
|
||||
"completed":this.completed,
|
||||
"parent": this.parent,
|
||||
"parentId": this.parentId,
|
||||
"children": children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//###############################//
|
||||
//# Custom Prompts #//
|
||||
//###############################//
|
||||
|
||||
function onEditPromptClick() {
|
||||
let popupText = ''
|
||||
popupText += `
|
||||
<div class="objective_prompt_modal">
|
||||
<small>Edit prompts used by Objective for this session. You can use {{objective}} or {{task}} plus any other standard template variables. Save template to persist changes.</small>
|
||||
<br>
|
||||
<div>
|
||||
<label for="objective-prompt-generate">Generation Prompt</label>
|
||||
<textarea id="objective-prompt-generate" type="text" class="text_pole textarea_compact" rows="8"></textarea>
|
||||
<label for="objective-prompt-check">Completion Check Prompt</label>
|
||||
<textarea id="objective-prompt-check" type="text" class="text_pole textarea_compact" rows="8"></textarea>
|
||||
<label for="objective-prompt-extension-prompt">Injected Prompt</label>
|
||||
<textarea id="objective-prompt-extension-prompt" type="text" class="text_pole textarea_compact" rows="8"></textarea>
|
||||
</div>
|
||||
<div class="objective_prompt_block">
|
||||
<label for="objective-custom-prompt-select">Custom Prompt Select</label>
|
||||
<select id="objective-custom-prompt-select"><select>
|
||||
</div>
|
||||
<div class="objective_prompt_block">
|
||||
<input id="objective-custom-prompt-new" class="menu_button" type="submit" value="New Prompt" />
|
||||
<input id="objective-custom-prompt-save" class="menu_button" type="submit" value="Save Prompt" />
|
||||
<input id="objective-custom-prompt-delete" class="menu_button" type="submit" value="Delete Prompt" />
|
||||
</div>
|
||||
</div>`
|
||||
callPopup(popupText, 'text')
|
||||
populateCustomPrompts()
|
||||
|
||||
// Set current values
|
||||
$('#objective-prompt-generate').val(objectivePrompts.createTask)
|
||||
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
|
||||
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
|
||||
|
||||
// Handle value updates
|
||||
$('#objective-prompt-generate').on('input', () => {
|
||||
objectivePrompts.createTask = $('#objective-prompt-generate').val()
|
||||
})
|
||||
$('#objective-prompt-check').on('input', () => {
|
||||
objectivePrompts.checkTaskCompleted = $('#objective-prompt-check').val()
|
||||
})
|
||||
$('#objective-prompt-extension-prompt').on('input', () => {
|
||||
objectivePrompts.currentTask = $('#objective-prompt-extension-prompt').val()
|
||||
})
|
||||
|
||||
// Handle new
|
||||
$('#objective-custom-prompt-new').on('click', () => {
|
||||
newCustomPrompt()
|
||||
})
|
||||
|
||||
// Handle save
|
||||
$('#objective-custom-prompt-save').on('click', () => {
|
||||
saveCustomPrompt()
|
||||
})
|
||||
|
||||
// Handle delete
|
||||
$('#objective-custom-prompt-delete').on('click', () => {
|
||||
deleteCustomPrompt()
|
||||
})
|
||||
|
||||
// Handle load
|
||||
$('#objective-custom-prompt-select').on('change', loadCustomPrompt)
|
||||
}
|
||||
async function newCustomPrompt() {
|
||||
const customPromptName = await callPopup('<h3>Custom Prompt name:</h3>', 'input');
|
||||
|
||||
if (customPromptName == "") {
|
||||
toastr.warning("Please set custom prompt name to save.")
|
||||
return
|
||||
}
|
||||
if (customPromptName == "default"){
|
||||
toastr.error("Cannot save over default prompt")
|
||||
return
|
||||
}
|
||||
extension_settings.objective.customPrompts[customPromptName] = {}
|
||||
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
|
||||
saveSettingsDebounced()
|
||||
populateCustomPrompts()
|
||||
}
|
||||
|
||||
function saveCustomPrompt() {
|
||||
const customPromptName = $("#objective-custom-prompt-select").find(':selected').val()
|
||||
if (customPromptName == "default"){
|
||||
toastr.error("Cannot save over default prompt")
|
||||
return
|
||||
}
|
||||
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
|
||||
saveSettingsDebounced()
|
||||
populateCustomPrompts()
|
||||
}
|
||||
|
||||
function deleteCustomPrompt(){
|
||||
const customPromptName = $("#objective-custom-prompt-select").find(':selected').val()
|
||||
|
||||
if (customPromptName == "default"){
|
||||
toastr.error("Cannot delete default prompt")
|
||||
return
|
||||
}
|
||||
delete extension_settings.objective.customPrompts[customPromptName]
|
||||
saveSettingsDebounced()
|
||||
populateCustomPrompts()
|
||||
loadCustomPrompt()
|
||||
}
|
||||
|
||||
function loadCustomPrompt(){
|
||||
const optionSelected = $("#objective-custom-prompt-select").find(':selected').val()
|
||||
Object.assign(objectivePrompts, extension_settings.objective.customPrompts[optionSelected])
|
||||
|
||||
$('#objective-prompt-generate').val(objectivePrompts.createTask)
|
||||
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
|
||||
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
|
||||
}
|
||||
|
||||
function populateCustomPrompts(){
|
||||
// Populate saved prompts
|
||||
$('#objective-custom-prompt-select').empty()
|
||||
for (const customPromptName in extension_settings.objective.customPrompts){
|
||||
const option = document.createElement('option');
|
||||
option.innerText = customPromptName;
|
||||
option.value = customPromptName;
|
||||
option.selected = customPromptName
|
||||
$('#objective-custom-prompt-select').append(option)
|
||||
}
|
||||
}
|
||||
|
||||
//###############################//
|
||||
//# UI AND Settings #//
|
||||
//###############################//
|
||||
|
||||
|
||||
const defaultSettings = {
|
||||
objective: "",
|
||||
tasks: [],
|
||||
currentObjectiveId: null,
|
||||
taskTree: null,
|
||||
chatDepth: 2,
|
||||
checkFrequency: 3,
|
||||
hideTasks: false
|
||||
hideTasks: false,
|
||||
prompts: defaultPrompts,
|
||||
}
|
||||
|
||||
// Convenient single call. Not much at the moment.
|
||||
@@ -288,15 +524,13 @@ function saveState() {
|
||||
currentChatId = context.chatId
|
||||
}
|
||||
|
||||
// Convert globalTasks for saving
|
||||
const tasks = globalTasks.map(task => {return task.toSaveState()})
|
||||
|
||||
chat_metadata['objective'] = {
|
||||
objective: globalObjective,
|
||||
tasks: tasks,
|
||||
currentObjectiveId: currentObjective.id,
|
||||
taskTree: taskTree.toSaveStateRecurse(),
|
||||
checkFrequency: $('#objective-check-frequency').val(),
|
||||
chatDepth: $('#objective-chat-depth').val(),
|
||||
hideTasks: $('#objective-hide-tasks').prop('checked'),
|
||||
prompts: objectivePrompts,
|
||||
}
|
||||
|
||||
saveMetadataDebounced();
|
||||
@@ -305,12 +539,12 @@ function saveState() {
|
||||
// Dump core state
|
||||
function debugObjectiveExtension() {
|
||||
console.log(JSON.stringify({
|
||||
"currentTask": currentTask.toSaveState(),
|
||||
"currentChatId": currentChatId,
|
||||
"checkCounter": checkCounter,
|
||||
"globalObjective": globalObjective,
|
||||
"globalTasks": globalTasks.map(v => {return v.toSaveState()}),
|
||||
"extension_settings": chat_metadata['objective'],
|
||||
"currentTask": currentTask,
|
||||
"currentObjective": currentObjective,
|
||||
"taskTree": taskTree.toSaveStateRecurse(),
|
||||
"chat_metadata": chat_metadata['objective'],
|
||||
"extension_settings": extension_settings['objective'],
|
||||
"prompts": objectivePrompts
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
@@ -320,9 +554,20 @@ window.debugObjectiveExtension = debugObjectiveExtension
|
||||
// Populate UI task list
|
||||
function updateUiTaskList() {
|
||||
$('#objective-tasks').empty()
|
||||
// Show tasks if there are any
|
||||
if (globalTasks.length > 0){
|
||||
for (const task of globalTasks) {
|
||||
|
||||
// Show button to navigate back to parent objective if parent exists
|
||||
if (currentObjective){
|
||||
if (currentObjective.parentId !== "") {
|
||||
$('#objective-parent').show()
|
||||
} else {
|
||||
$('#objective-parent').hide()
|
||||
}
|
||||
}
|
||||
|
||||
$('#objective-text').val(currentObjective.description)
|
||||
if (currentObjective.children.length > 0){
|
||||
// Show tasks if there are any to show
|
||||
for (const task of currentObjective.children) {
|
||||
task.addUiElement()
|
||||
}
|
||||
} else {
|
||||
@@ -331,17 +576,21 @@ function updateUiTaskList() {
|
||||
<input id="objective-task-add-first" type="button" class="menu_button" value="Add Task">
|
||||
`)
|
||||
$("#objective-task-add-first").on('click', () => {
|
||||
addTask("")
|
||||
currentObjective.addTask("")
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onParentClick() {
|
||||
currentObjective = getTaskById(currentObjective.parentId)
|
||||
updateUiTaskList()
|
||||
setCurrentTask()
|
||||
}
|
||||
|
||||
// Trigger creation of new tasks with given objective.
|
||||
async function onGenerateObjectiveClick() {
|
||||
globalObjective = $('#objective-text').val()
|
||||
await generateTasks()
|
||||
saveState()
|
||||
}
|
||||
@@ -352,6 +601,13 @@ function onChatDepthInput() {
|
||||
setCurrentTask() // Ensure extension prompt is updated
|
||||
}
|
||||
|
||||
function onObjectiveTextFocusOut(){
|
||||
if (currentObjective){
|
||||
currentObjective.description = $('#objective-text').val()
|
||||
saveState()
|
||||
}
|
||||
}
|
||||
|
||||
// Update how often we check for task completion
|
||||
function onCheckFrequencyInput() {
|
||||
checkCounter = $("#objective-check-frequency").val()
|
||||
@@ -364,10 +620,33 @@ function onHideTasksInput() {
|
||||
saveState()
|
||||
}
|
||||
|
||||
function loadTaskChildrenRecurse(savedTask) {
|
||||
let tempTaskTree = new ObjectiveTask({
|
||||
id: savedTask.id,
|
||||
description: savedTask.description,
|
||||
completed: savedTask.completed,
|
||||
parentId: savedTask.parentId,
|
||||
})
|
||||
for (const task of savedTask.children){
|
||||
const childTask = loadTaskChildrenRecurse(task)
|
||||
tempTaskTree.children.push(childTask)
|
||||
}
|
||||
return tempTaskTree
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
// Load/Init settings for chatId
|
||||
currentChatId = getContext().chatId
|
||||
|
||||
// Reset Objectives and Tasks in memory
|
||||
taskTree = null;
|
||||
currentObjective = null;
|
||||
|
||||
// Init extension settings
|
||||
if (Object.keys(extension_settings.objective).length === 0) {
|
||||
Object.assign(extension_settings.objective, { 'customPrompts': {'default':defaultPrompts}})
|
||||
}
|
||||
|
||||
// Bail on home screen
|
||||
if (currentChatId == undefined) {
|
||||
return
|
||||
@@ -375,6 +654,7 @@ function loadSettings() {
|
||||
|
||||
// Migrate existing settings
|
||||
if (currentChatId in extension_settings.objective) {
|
||||
// TODO: Remove this soon
|
||||
chat_metadata['objective'] = extension_settings.objective[currentChatId];
|
||||
delete extension_settings.objective[currentChatId];
|
||||
}
|
||||
@@ -383,26 +663,52 @@ function loadSettings() {
|
||||
Object.assign(chat_metadata, { objective: defaultSettings });
|
||||
}
|
||||
|
||||
// Update globals
|
||||
globalObjective = chat_metadata['objective'].objective
|
||||
globalTasks = chat_metadata['objective'].tasks.map(task => {
|
||||
return new ObjectiveTask({
|
||||
id: task.id,
|
||||
description: task.description,
|
||||
completed: task.completed,
|
||||
parent: task.parent,
|
||||
})
|
||||
});
|
||||
// Migrate legacy flat objective to new objectiveTree and currentObjective
|
||||
if ('objective' in chat_metadata.objective) {
|
||||
|
||||
// Create root objective from legacy objective
|
||||
taskTree = new ObjectiveTask({id:0, description: chat_metadata.objective.objective});
|
||||
currentObjective = taskTree;
|
||||
|
||||
// Populate root objective tree from legacy tasks
|
||||
if ('tasks' in chat_metadata.objective) {
|
||||
let idIncrement = 0;
|
||||
taskTree.children = chat_metadata.objective.tasks.map(task => {
|
||||
idIncrement += 1;
|
||||
return new ObjectiveTask({
|
||||
id: idIncrement,
|
||||
description: task.description,
|
||||
completed: task.completed,
|
||||
parentId: taskTree.id,
|
||||
})
|
||||
});
|
||||
}
|
||||
saveState();
|
||||
delete chat_metadata.objective.objective;
|
||||
delete chat_metadata.objective.tasks;
|
||||
} else {
|
||||
// Load Objectives and Tasks (Normal path)
|
||||
if (chat_metadata.objective.taskTree){
|
||||
taskTree = loadTaskChildrenRecurse(chat_metadata.objective.taskTree)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure there's a root task
|
||||
if (!taskTree) {
|
||||
taskTree = new ObjectiveTask({id:0,description:$('#objective-text').val()})
|
||||
}
|
||||
|
||||
currentObjective = taskTree
|
||||
checkCounter = chat_metadata['objective'].checkFrequency
|
||||
|
||||
// Update UI elements
|
||||
$('#objective-counter').text(checkCounter)
|
||||
$("#objective-text").text(globalObjective)
|
||||
$("#objective-text").text(taskTree.description)
|
||||
updateUiTaskList()
|
||||
$('#objective-chat-depth').val(chat_metadata['objective'].chatDepth)
|
||||
$('#objective-check-frequency').val(chat_metadata['objective'].checkFrequency)
|
||||
$('#objective-hide-tasks').prop('checked', chat_metadata['objective'].hideTasks)
|
||||
onHideTasksInput()
|
||||
$('#objective-tasks').prop('hidden', $('#objective-hide-tasks').prop('checked'))
|
||||
setCurrentTask()
|
||||
}
|
||||
|
||||
@@ -420,35 +726,44 @@ jQuery(() => {
|
||||
<div class="objective-settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Objective</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
|
||||
<textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
|
||||
<div class="objective_block flex-container">
|
||||
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
|
||||
<label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
|
||||
<b>Objective</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
|
||||
<div id="objective-tasks"> </div>
|
||||
<div class="objective_block margin-bot-10px">
|
||||
<div class="objective_block objective_block_control flex1 flexFlowColumn">
|
||||
<label for="objective-chat-depth">Position in Chat</label>
|
||||
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
<div class="inline-drawer-content">
|
||||
<label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
|
||||
<textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
|
||||
<div class="objective_block flex-container">
|
||||
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
|
||||
<label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="objective_block objective_block_control flex1">
|
||||
|
||||
<label for="objective-check-frequency">Task Check Frequency</label>
|
||||
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
<small>(0 = disabled)</small>
|
||||
<div id="objective-parent" class="objective_block flex-container">
|
||||
<i class="objective-task-button fa-solid fa-circle-left fa-2x" title="Go to Parent"></i>
|
||||
<small>Go to parent task</small>
|
||||
</div>
|
||||
|
||||
<div id="objective-tasks"> </div>
|
||||
<div class="objective_block margin-bot-10px">
|
||||
<div class="objective_block objective_block_control flex1 flexFlowColumn">
|
||||
<label for="objective-chat-depth">Position in Chat</label>
|
||||
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
</div>
|
||||
<br>
|
||||
<div class="objective_block objective_block_control flex1">
|
||||
|
||||
<label for="objective-check-frequency">Task Check Frequency</label>
|
||||
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
<small>(0 = disabled)</small>
|
||||
</div>
|
||||
</div>
|
||||
<span> Messages until next AI task completion check <span id="objective-counter">0</span></span>
|
||||
<div class="objective_block flex-container">
|
||||
<input id="objective_prompt_edit" class="menu_button" type="submit" value="Edit Prompts" />
|
||||
</div>
|
||||
<hr class="sysHR">
|
||||
</div>
|
||||
<span> Messages until next AI task completion check <span id="objective-counter">0</span></span>
|
||||
<hr class="sysHR">
|
||||
</div>
|
||||
</div>`;
|
||||
</div>
|
||||
`;
|
||||
|
||||
addManualTaskCheckUi()
|
||||
$('#extensions_settings').append(settingsHtml);
|
||||
@@ -456,6 +771,10 @@ jQuery(() => {
|
||||
$('#objective-chat-depth').on('input', onChatDepthInput)
|
||||
$("#objective-check-frequency").on('input', onCheckFrequencyInput)
|
||||
$('#objective-hide-tasks').on('click', onHideTasksInput)
|
||||
$('#objective_prompt_edit').on('click', onEditPromptClick)
|
||||
$('#objective-parent').hide()
|
||||
$('#objective-parent').on('click',onParentClick)
|
||||
$('#objective-text').on('focusout',onObjectiveTextFocusOut)
|
||||
loadSettings()
|
||||
|
||||
eventSource.on(event_types.CHAT_CHANGED, () => {
|
||||
@@ -463,7 +782,7 @@ jQuery(() => {
|
||||
});
|
||||
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, () => {
|
||||
if (currentChatId == undefined) {
|
||||
if (currentChatId == undefined || currentTask == undefined) {
|
||||
return
|
||||
}
|
||||
if ($("#objective-check-frequency").val() > 0) {
|
||||
|
@@ -10,6 +10,13 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.objective_prompt_block {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
column-gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.objective_block_control {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
@@ -1,19 +1,48 @@
|
||||
import { saveSettingsDebounced } from "../../../script.js";
|
||||
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
|
||||
import { getContext, extension_settings } from "../../extensions.js";
|
||||
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||
|
||||
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'quick-reply';
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
let presets = [];
|
||||
let selected_preset = '';
|
||||
|
||||
const defaultSettings = {
|
||||
quickReplyEnabled: false,
|
||||
quickReplyEnabled: true,
|
||||
numberOfSlots: 5,
|
||||
quickReplySlots: [],
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
//method from worldinfo
|
||||
async function updateQuickReplyPresetList() {
|
||||
var result = await fetch("/getsettings", {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
var data = await result.json();
|
||||
presets = data.quickReplyPresets?.length ? data.quickReplyPresets : [];
|
||||
console.log(presets)
|
||||
$("#quickReplyPresets").find('option[value!=""]').remove();
|
||||
|
||||
|
||||
if (presets !== undefined) {
|
||||
presets.forEach((item, i) => {
|
||||
$("#quickReplyPresets").append(`<option value='${item.name}'${selected_preset.includes(item.name) ? ' selected' : ''}>${item.name}</option>`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSettings(type) {
|
||||
if (type === 'init') {
|
||||
await updateQuickReplyPresetList()
|
||||
}
|
||||
if (Object.keys(extension_settings.quickReply).length === 0) {
|
||||
Object.assign(extension_settings.quickReply, defaultSettings);
|
||||
}
|
||||
@@ -111,6 +140,51 @@ async function moduleWorker() {
|
||||
if (extension_settings.quickReply.quickReplyEnabled === true) {
|
||||
$('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection');
|
||||
}
|
||||
if (extension_settings.quickReply.selectedPreset) {
|
||||
selected_preset = extension_settings.quickReply.selectedPreset;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveQuickReplyPreset() {
|
||||
const name = await callPopup('Enter a name for the Quick Reply Preset:', 'input');
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const quickReplyPreset = {
|
||||
name: name,
|
||||
quickReplyEnabled: extension_settings.quickReply.quickReplyEnabled,
|
||||
quickReplySlots: extension_settings.quickReply.quickReplySlots,
|
||||
numberOfSlots: extension_settings.quickReply.numberOfSlots,
|
||||
selectedPreset: name
|
||||
}
|
||||
|
||||
const response = await fetch('/savequickreply', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(quickReplyPreset)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const quickReplyPresetIndex = presets.findIndex(x => x.name == name);
|
||||
|
||||
if (quickReplyPresetIndex == -1) {
|
||||
presets.push(quickReplyPreset);
|
||||
const option = document.createElement('option');
|
||||
option.selected = true;
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
$('#quickReplyPresets').append(option);
|
||||
}
|
||||
else {
|
||||
presets[quickReplyPresetIndex] = quickReplyPreset;
|
||||
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
} else {
|
||||
toastr.warning('Failed to save Quick Reply Preset.')
|
||||
}
|
||||
}
|
||||
|
||||
async function onQuickReplyNumberOfSlotsInput() {
|
||||
@@ -178,6 +252,27 @@ function generateQuickReplyElements() {
|
||||
});
|
||||
}
|
||||
|
||||
async function applyQuickReplyPreset(name) {
|
||||
const quickReplyPreset = presets.find(x => x.name == name);
|
||||
|
||||
if (!quickReplyPreset) {
|
||||
console.log(`error, QR preset '${name}' not found`)
|
||||
return;
|
||||
}
|
||||
|
||||
extension_settings.quickReply = quickReplyPreset;
|
||||
extension_settings.quickReply.selectedPreset = name;
|
||||
saveSettingsDebounced()
|
||||
loadSettings('init')
|
||||
addQuickReplyBar();
|
||||
moduleWorker();
|
||||
|
||||
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
|
||||
|
||||
console.debug('QR Preset applied: ' + name);
|
||||
//loadMovingUIState()
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
|
||||
moduleWorker();
|
||||
@@ -190,11 +285,18 @@ jQuery(async () => {
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label marginBot10">
|
||||
<input id="quickReplyEnabled" type="checkbox" />
|
||||
Enable Quick Replies
|
||||
</label>
|
||||
<label for="quickReplyNumberOfSlots">Number of slots:</label>
|
||||
<div class="flex-container ">
|
||||
<label class="checkbox_label marginBot10 wide100p flexnowrap">
|
||||
<input id="quickReplyEnabled" type="checkbox" />
|
||||
Enable Quick Replies
|
||||
</label>
|
||||
<div class="flex-container flexnowrap wide100p">
|
||||
<select id="quickReplyPresets" name="quickreply-preset">
|
||||
</select>
|
||||
<i id="quickReplyPresetSaveButton" class="fa-solid fa-save"></i>
|
||||
</div>
|
||||
<label for="quickReplyNumberOfSlots">Number of slots:</label>
|
||||
</div>
|
||||
<div class="flex-container flexGap5 flexnowrap">
|
||||
<input id="quickReplyNumberOfSlots" class="text_pole" type="number" min="1" max="100" value="" />
|
||||
<div class="menu_button menu_button_icon" id="quickReplyNumberOfSlotsApply">
|
||||
@@ -212,8 +314,17 @@ jQuery(async () => {
|
||||
|
||||
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
|
||||
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
|
||||
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
|
||||
|
||||
await loadSettings();
|
||||
$("#quickReplyPresets").on('change', async function () {
|
||||
const quickReplyPresetSelected = $(this).find(':selected').val();
|
||||
extension_settings.quickReplyPreset = quickReplyPresetSelected;
|
||||
applyQuickReplyPreset(quickReplyPresetSelected);
|
||||
saveSettingsDebounced();
|
||||
|
||||
});
|
||||
|
||||
await loadSettings('init');
|
||||
addQuickReplyBar();
|
||||
});
|
||||
|
||||
|
@@ -5,13 +5,14 @@ import { regex_placement } from "./engine.js";
|
||||
|
||||
async function saveRegexScript(regexScript, existingScriptIndex) {
|
||||
// If not editing
|
||||
if (existingScriptIndex === -1) {
|
||||
// Is the script name undefined?
|
||||
if (!regexScript.scriptName) {
|
||||
toastr.error(`Could not save regex script: The script name was undefined or empty!`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Is the script name undefined or empty?
|
||||
if (!regexScript.scriptName) {
|
||||
toastr.error(`Could not save regex script: The script name was undefined or empty!`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingScriptIndex === -1) {
|
||||
// Does the script name already exist?
|
||||
if (extension_settings.regex.find((e) => e.scriptName === regexScript.scriptName)) {
|
||||
toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`);
|
||||
@@ -29,14 +30,12 @@ async function saveRegexScript(regexScript, existingScriptIndex) {
|
||||
|
||||
// Is a find regex present?
|
||||
if (regexScript.findRegex.length === 0) {
|
||||
toastr.error(`Could not save regex script: A find regex is required!`);
|
||||
return;
|
||||
toastr.warning(`This regex script will not work, but was saved anyway: A find regex isn't present.`);
|
||||
}
|
||||
|
||||
// Is there someplace to place results?
|
||||
if (regexScript.placement.length === 0) {
|
||||
toastr.error(`Could not save regex script: One placement checkbox must be selected!`);
|
||||
return;
|
||||
toastr.warning(`This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!`);
|
||||
}
|
||||
|
||||
if (existingScriptIndex !== -1) {
|
||||
@@ -140,7 +139,7 @@ async function onRegexEditorOpenClick(existingId) {
|
||||
.prop("checked", true);
|
||||
|
||||
editorHtml
|
||||
.find(`input[name="replace_position"][value="0"]`)
|
||||
.find(`input[name="replace_position"][value="1"]`)
|
||||
.prop("checked", true);
|
||||
}
|
||||
|
||||
|
233
public/scripts/extensions/speech-recognition/browser.js
Normal file
233
public/scripts/extensions/speech-recognition/browser.js
Normal file
@@ -0,0 +1,233 @@
|
||||
// Borrowed from Agnai (AGPLv3)
|
||||
// https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
|
||||
// First version by Cohee#1207
|
||||
// Adapted by Tony-sama
|
||||
|
||||
export { BrowserSttProvider }
|
||||
|
||||
const DEBUG_PREFIX = "<Speech Recognition module (Browser)> "
|
||||
|
||||
class BrowserSttProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings = {
|
||||
language: ""
|
||||
}
|
||||
|
||||
defaultSettings = {
|
||||
language: "en-US",
|
||||
}
|
||||
|
||||
processTranscriptFunction = null;
|
||||
|
||||
get settingsHtml() {
|
||||
let html = ' \
|
||||
<span>Language</span> </br> \
|
||||
<select id="speech_recognition_browser_provider_language"> \
|
||||
<option value="ar-SA">ar-SA: Arabic (Saudi Arabia)</option> \
|
||||
<option value="bn-BD">bn-BD: Bangla (Bangladesh)</option> \
|
||||
<option value="bn-IN">bn-IN: Bangla (India)</option> \
|
||||
<option value="cs-CZ">cs-CZ: Czech (Czech Republic)</option> \
|
||||
<option value="da-DK">da-DK: Danish (Denmark)</option> \
|
||||
<option value="de-AT">de-AT: German (Austria)</option> \
|
||||
<option value="de-CH">de-CH: German (Switzerland)</option> \
|
||||
<option value="de-DE">de-DE: German (Germany)</option> \
|
||||
<option value="el-GR">el-GR: Greek (Greece)</option> \
|
||||
<option value="en-AU">en-AU: English (Australia)</option> \
|
||||
<option value="en-CA">en-CA: English (Canada)</option> \
|
||||
<option value="en-GB">en-GB: English (United Kingdom)</option> \
|
||||
<option value="en-IE">en-IE: English (Ireland)</option> \
|
||||
<option value="en-IN">en-IN: English (India)</option> \
|
||||
<option value="en-NZ">en-NZ: English (New Zealand)</option> \
|
||||
<option value="en-US">en-US: English (United States)</option> \
|
||||
<option value="en-ZA">en-ZA: English (South Africa)</option> \
|
||||
<option value="es-AR">es-AR: Spanish (Argentina)</option> \
|
||||
<option value="es-CL">es-CL: Spanish (Chile)</option> \
|
||||
<option value="es-CO">es-CO: Spanish (Columbia)</option> \
|
||||
<option value="es-ES">es-ES: Spanish (Spain)</option> \
|
||||
<option value="es-MX">es-MX: Spanish (Mexico)</option> \
|
||||
<option value="es-US">es-US: Spanish (United States)</option> \
|
||||
<option value="fi-FI">fi-FI: Finnish (Finland)</option> \
|
||||
<option value="fr-BE">fr-BE: French (Belgium)</option> \
|
||||
<option value="fr-CA">fr-CA: French (Canada)</option> \
|
||||
<option value="fr-CH">fr-CH: French (Switzerland)</option> \
|
||||
<option value="fr-FR">fr-FR: French (France)</option> \
|
||||
<option value="he-IL">he-IL: Hebrew (Israel)</option> \
|
||||
<option value="hi-IN">hi-IN: Hindi (India)</option> \
|
||||
<option value="hu-HU">hu-HU: Hungarian (Hungary)</option> \
|
||||
<option value="id-ID">id-ID: Indonesian (Indonesia)</option> \
|
||||
<option value="it-CH">it-CH: Italian (Switzerland)</option> \
|
||||
<option value="it-IT">it-IT: Italian (Italy)</option> \
|
||||
<option value="ja-JP">ja-JP: Japanese (Japan)</option> \
|
||||
<option value="ko-KR">ko-KR: Korean (Republic of Korea)</option> \
|
||||
<option value="nl-BE">nl-BE: Dutch (Belgium)</option> \
|
||||
<option value="nl-NL">nl-NL: Dutch (The Netherlands)</option> \
|
||||
<option value="no-NO">no-NO: Norwegian (Norway)</option> \
|
||||
<option value="pl-PL">pl-PL: Polish (Poland)</option> \
|
||||
<option value="pt-BR">pt-BR: Portugese (Brazil)</option> \
|
||||
<option value="pt-PT">pt-PT: Portugese (Portugal)</option> \
|
||||
<option value="ro-RO">ro-RO: Romanian (Romania)</option> \
|
||||
<option value="ru-RU">ru-RU: Russian (Russian Federation)</option> \
|
||||
<option value="sk-SK">sk-SK: Slovak (Slovakia)</option> \
|
||||
<option value="sv-SE">sv-SE: Swedish (Sweden)</option> \
|
||||
<option value="ta-IN">ta-IN: Tamil (India)</option> \
|
||||
<option value="ta-LK">ta-LK: Tamil (Sri Lanka)</option> \
|
||||
<option value="th-TH">th-TH: Thai (Thailand)</option> \
|
||||
<option value="tr-TR">tr-TR: Turkish (Turkey)</option> \
|
||||
<option value="zh-CN">zh-CN: Chinese (China)</option> \
|
||||
<option value="zh-HK">zh-HK: Chinese (Hond Kong)</option> \
|
||||
<option value="zh-TW">zh-TW: Chinese (Taiwan)</option> \
|
||||
</select> \
|
||||
'
|
||||
return html
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
// Used when provider settings are updated from UI
|
||||
this.settings.language = $("#speech_recognition_browser_provider_language").val();
|
||||
console.debug(DEBUG_PREFIX+"Change language to",this.settings.language);
|
||||
this.loadSettings(this.settings);
|
||||
}
|
||||
|
||||
static capitalizeInterim(interimTranscript) {
|
||||
let capitalizeIndex = -1;
|
||||
if (interimTranscript.length > 2 && interimTranscript[0] === ' ') capitalizeIndex = 1;
|
||||
else if (interimTranscript.length > 1) capitalizeIndex = 0;
|
||||
if (capitalizeIndex > -1) {
|
||||
const spacing = capitalizeIndex > 0 ? ' '.repeat(capitalizeIndex - 1) : '';
|
||||
const capitalized = interimTranscript[capitalizeIndex].toLocaleUpperCase();
|
||||
const rest = interimTranscript.substring(capitalizeIndex + 1);
|
||||
interimTranscript = spacing + capitalized + rest;
|
||||
}
|
||||
return interimTranscript;
|
||||
}
|
||||
|
||||
static composeValues(previous, interim) {
|
||||
let spacing = '';
|
||||
if (previous.endsWith('.')) spacing = ' ';
|
||||
return previous + spacing + interim;
|
||||
}
|
||||
|
||||
loadSettings(settings) {
|
||||
const processTranscript = this.processTranscriptFunction;
|
||||
|
||||
// Populate Provider UI given input settings
|
||||
if (Object.keys(settings).length == 0) {
|
||||
console.debug(DEBUG_PREFIX+"Using default browser STT settings")
|
||||
}
|
||||
|
||||
// Initialise as defaultSettings
|
||||
this.settings = this.defaultSettings;
|
||||
|
||||
for (const key in settings){
|
||||
if (key in this.settings){
|
||||
this.settings[key] = settings[key]
|
||||
} else {
|
||||
throw `Invalid setting passed to Speech recogniton extension (browser): ${key}`
|
||||
}
|
||||
}
|
||||
|
||||
$("#speech_recognition_browser_provider_language").val(this.settings.language);
|
||||
|
||||
const speechRecognitionSettings = $.extend({
|
||||
grammar: '' // Custom grammar
|
||||
}, options);
|
||||
|
||||
const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
const speechRecognitionList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
|
||||
|
||||
if (!speechRecognition) {
|
||||
console.warn(DEBUG_PREFIX+'Speech recognition is not supported in this browser.');
|
||||
$("#microphone_button").hide();
|
||||
toastr.error("Speech recognition is not supported in this browser, use another browser or another provider of SillyTavern-extras Speech recognition extension.", "Speech recognition activation Failed (Browser)", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const recognition = new speechRecognition();
|
||||
|
||||
if (speechRecognitionSettings.grammar && speechRecognitionList) {
|
||||
speechRecognitionList.addFromString(speechRecognitionSettings.grammar, 1);
|
||||
recognition.grammars = speechRecognitionList;
|
||||
}
|
||||
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
recognition.lang = this.settings.language;
|
||||
|
||||
const textarea = $('#send_textarea');
|
||||
const button = $('#microphone_button');
|
||||
|
||||
let listening = false;
|
||||
button.off('click').on("click", function () {
|
||||
if (listening) {
|
||||
recognition.stop();
|
||||
} else {
|
||||
recognition.start();
|
||||
}
|
||||
listening = !listening;
|
||||
});
|
||||
|
||||
let initialText = '';
|
||||
|
||||
recognition.onresult = function (speechEvent) {
|
||||
let finalTranscript = '';
|
||||
let interimTranscript = ''
|
||||
|
||||
for (let i = speechEvent.resultIndex; i < speechEvent.results.length; ++i) {
|
||||
const transcript = speechEvent.results[i][0].transcript;
|
||||
|
||||
if (speechEvent.results[i].isFinal) {
|
||||
let interim = BrowserSttProvider.capitalizeInterim(transcript);
|
||||
if (interim != '') {
|
||||
let final = finalTranscript;
|
||||
final = BrowserSttProvider.composeValues(final, interim);
|
||||
if (final.slice(-1) != '.' & final.slice(-1) != '?') final += '.';
|
||||
finalTranscript = final;
|
||||
recognition.abort();
|
||||
listening = false;
|
||||
}
|
||||
interimTranscript = ' ';
|
||||
} else {
|
||||
interimTranscript += transcript;
|
||||
}
|
||||
}
|
||||
|
||||
interimTranscript = BrowserSttProvider.capitalizeInterim(interimTranscript);
|
||||
|
||||
textarea.val(initialText + finalTranscript + interimTranscript);
|
||||
};
|
||||
|
||||
recognition.onerror = function (event) {
|
||||
console.error('Error occurred in recognition:', event.error);
|
||||
//if ($('#speech_recognition_debug').is(':checked'))
|
||||
// toastr.error('Error occurred in recognition:'+ event.error, 'STT Generation error (Browser)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
};
|
||||
|
||||
recognition.onend = function () {
|
||||
listening = false;
|
||||
button.toggleClass('fa-microphone fa-microphone-slash');
|
||||
const newText = textarea.val().substring(initialText.length);
|
||||
textarea.val(textarea.val().substring(0,initialText.length));
|
||||
processTranscript(newText);
|
||||
|
||||
};
|
||||
|
||||
recognition.onstart = function () {
|
||||
initialText = textarea.val();
|
||||
button.toggleClass('fa-microphone fa-microphone-slash');
|
||||
|
||||
if ($("#speech_recognition_message_mode").val() == "replace") {
|
||||
textarea.val("");
|
||||
initialText = ""
|
||||
}
|
||||
};
|
||||
|
||||
$("#microphone_button").show();
|
||||
|
||||
console.debug(DEBUG_PREFIX+"Browser STT settings loaded")
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,110 +1,351 @@
|
||||
// Borrowed from Agnai (AGPLv3)
|
||||
// https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
|
||||
function capitalizeInterim(interimTranscript) {
|
||||
let capitalizeIndex = -1;
|
||||
if (interimTranscript.length > 2 && interimTranscript[0] === ' ') capitalizeIndex = 1;
|
||||
else if (interimTranscript.length > 1) capitalizeIndex = 0;
|
||||
if (capitalizeIndex > -1) {
|
||||
const spacing = capitalizeIndex > 0 ? ' '.repeat(capitalizeIndex - 1) : '';
|
||||
const capitalized = interimTranscript[capitalizeIndex].toLocaleUpperCase();
|
||||
const rest = interimTranscript.substring(capitalizeIndex + 1);
|
||||
interimTranscript = spacing + capitalized + rest;
|
||||
/*
|
||||
TODO:
|
||||
- try pseudo streaming audio by just sending chunk every X seconds and asking VOSK if it is full text.
|
||||
*/
|
||||
|
||||
import { saveSettingsDebounced } from "../../../script.js";
|
||||
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
|
||||
import { VoskSttProvider } from './vosk.js'
|
||||
import { WhisperSttProvider } from './whisper.js'
|
||||
import { BrowserSttProvider } from './browser.js'
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'Speech Recognition';
|
||||
const DEBUG_PREFIX = "<Speech Recognition module> "
|
||||
|
||||
let sttProviders = {
|
||||
None: null,
|
||||
Browser: BrowserSttProvider,
|
||||
Whisper: WhisperSttProvider,
|
||||
Vosk: VoskSttProvider,
|
||||
}
|
||||
|
||||
let sttProvider = null
|
||||
let sttProviderName = "None"
|
||||
|
||||
let audioRecording = false
|
||||
const constraints = { audio: { sampleSize: 16, channelCount: 1, sampleRate: 16000 } };
|
||||
let audioChunks = [];
|
||||
|
||||
async function processTranscript(transcript) {
|
||||
try {
|
||||
const transcriptOriginal = transcript;
|
||||
let transcriptFormatted = transcriptOriginal.trim();
|
||||
|
||||
if (transcriptFormatted.length > 0)
|
||||
{
|
||||
console.debug(DEBUG_PREFIX+"recorded transcript: \""+transcriptFormatted+"\"");
|
||||
const messageMode = extension_settings.speech_recognition.messageMode;
|
||||
console.debug(DEBUG_PREFIX+"mode: "+messageMode);
|
||||
|
||||
let transcriptLower = transcriptFormatted.toLowerCase()
|
||||
// remove punctuation
|
||||
let transcriptRaw = transcriptLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
|
||||
|
||||
// Check message mapping
|
||||
if (extension_settings.speech_recognition.messageMappingEnabled) {
|
||||
console.debug(DEBUG_PREFIX+"Start searching message mapping into:",transcriptRaw)
|
||||
for (const key in extension_settings.speech_recognition.messageMapping) {
|
||||
console.debug(DEBUG_PREFIX+"message mapping searching: ", key,"=>",extension_settings.speech_recognition.messageMapping[key]);
|
||||
if (transcriptRaw.includes(key)) {
|
||||
var message = extension_settings.speech_recognition.messageMapping[key];
|
||||
console.debug(DEBUG_PREFIX+"message mapping found: ", key,"=>",extension_settings.speech_recognition.messageMapping[key]);
|
||||
$("#send_textarea").val(message);
|
||||
|
||||
if (messageMode == "auto_send") await getContext().generate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX+"no message mapping found, processing transcript as normal message");
|
||||
|
||||
switch (messageMode) {
|
||||
case "auto_send":
|
||||
$('#send_textarea').val("") // clear message area to avoid double message
|
||||
|
||||
console.debug(DEBUG_PREFIX+"Sending message")
|
||||
const context = getContext();
|
||||
const messageText = transcriptFormatted;
|
||||
const message = {
|
||||
name: context.name1,
|
||||
is_user: true,
|
||||
is_name: true,
|
||||
send_date: Date.now(),
|
||||
mes: messageText,
|
||||
};
|
||||
context.chat.push(message);
|
||||
context.addOneMessage(message);
|
||||
|
||||
await context.generate();
|
||||
|
||||
$('#debug_output').text("<SST-module DEBUG>: message sent: \""+ transcriptFormatted +"\"");
|
||||
break;
|
||||
|
||||
case "replace":
|
||||
console.debug(DEBUG_PREFIX+"Replacing message")
|
||||
$('#send_textarea').val(transcriptFormatted);
|
||||
break;
|
||||
|
||||
case "append":
|
||||
console.debug(DEBUG_PREFIX+"Appending message")
|
||||
$('#send_textarea').val($('#send_textarea').val()+" "+transcriptFormatted);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.debug(DEBUG_PREFIX+"Not supported stt message mode: "+messageMode)
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
console.debug(DEBUG_PREFIX+"Empty transcript, do nothing");
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(error);
|
||||
}
|
||||
return interimTranscript;
|
||||
}
|
||||
|
||||
function composeValues(previous, interim) {
|
||||
let spacing = '';
|
||||
if (previous.endsWith('.')) spacing = ' ';
|
||||
return previous + spacing + interim;
|
||||
function loadNavigatorAudioRecording() {
|
||||
if (navigator.mediaDevices.getUserMedia) {
|
||||
console.debug(DEBUG_PREFIX+' getUserMedia supported by browser.');
|
||||
|
||||
let onSuccess = function(stream) {
|
||||
const mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
$("#microphone_button").off('click').on("click", function() {
|
||||
if (!audioRecording) {
|
||||
mediaRecorder.start();
|
||||
console.debug(mediaRecorder.state);
|
||||
console.debug("recorder started");
|
||||
audioRecording = true;
|
||||
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
|
||||
}
|
||||
else {
|
||||
mediaRecorder.stop();
|
||||
console.debug(mediaRecorder.state);
|
||||
console.debug("recorder stopped");
|
||||
audioRecording = false;
|
||||
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
|
||||
}
|
||||
});
|
||||
|
||||
mediaRecorder.onstop = async function() {
|
||||
console.debug(DEBUG_PREFIX+"data available after MediaRecorder.stop() called: ", audioChunks.length, " chunks");
|
||||
const audioBlob = new Blob(audioChunks, { type: "audio/wav; codecs=0" });
|
||||
audioChunks = [];
|
||||
|
||||
const transcript = await sttProvider.processAudio(audioBlob);
|
||||
|
||||
// TODO: lock and release recording while processing?
|
||||
console.debug(DEBUG_PREFIX+"received transcript:", transcript);
|
||||
processTranscript(transcript);
|
||||
}
|
||||
|
||||
mediaRecorder.ondataavailable = function(e) {
|
||||
audioChunks.push(e.data);
|
||||
}
|
||||
}
|
||||
|
||||
let onError = function(err) {
|
||||
console.debug(DEBUG_PREFIX+"The following error occured: " + err);
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
|
||||
|
||||
} else {
|
||||
console.debug(DEBUG_PREFIX+"getUserMedia not supported on your browser!");
|
||||
toastr.error("getUserMedia not supported", DEBUG_PREFIX+"not supported for your browser.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
}
|
||||
}
|
||||
|
||||
(function ($) {
|
||||
$.fn.speechRecognitionPlugin = function (options) {
|
||||
const settings = $.extend({
|
||||
grammar: '' // Custom grammar
|
||||
}, options);
|
||||
//##############//
|
||||
// STT Provider //
|
||||
//##############//
|
||||
|
||||
const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
const speechRecognitionList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
|
||||
function loadSttProvider(provider) {
|
||||
//Clear the current config and add new config
|
||||
$("#speech_recognition_provider_settings").html("");
|
||||
|
||||
if (!speechRecognition) {
|
||||
console.warn('Speech recognition is not supported in this browser.');
|
||||
return;
|
||||
// Init provider references
|
||||
extension_settings.speech_recognition.currentProvider = provider;
|
||||
sttProviderName = provider;
|
||||
|
||||
if (!(sttProviderName in extension_settings.speech_recognition)) {
|
||||
console.warn(`Provider ${sttProviderName} not in Extension Settings, initiatilizing provider in settings`);
|
||||
extension_settings.speech_recognition[sttProviderName] = {};
|
||||
}
|
||||
|
||||
$('#speech_recognition_provider').val(sttProviderName);
|
||||
|
||||
if (sttProviderName == "None") {
|
||||
$("#microphone_button").hide();
|
||||
$("#speech_recognition_message_mode_div").hide();
|
||||
$("#speech_recognition_message_mapping_div").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$("#speech_recognition_message_mode_div").show();
|
||||
$("#speech_recognition_message_mapping_div").show();
|
||||
|
||||
sttProvider = new sttProviders[sttProviderName]
|
||||
|
||||
// Init provider settings
|
||||
$('#speech_recognition_provider_settings').append(sttProvider.settingsHtml);
|
||||
|
||||
// Use microphone button as push to talk
|
||||
if (sttProviderName == "Browser") {
|
||||
sttProvider.processTranscriptFunction = processTranscript;
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
}
|
||||
else {
|
||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
||||
loadNavigatorAudioRecording();
|
||||
|
||||
$("#microphone_button").show();
|
||||
}
|
||||
}
|
||||
|
||||
function onSttProviderChange() {
|
||||
const sttProviderSelection = $('#speech_recognition_provider').val();
|
||||
loadSttProvider(sttProviderSelection);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onSttProviderSettingsInput() {
|
||||
sttProvider.onSettingsChange();
|
||||
|
||||
// Persist changes to SillyTavern stt extension settings
|
||||
extension_settings.speech_recognition[sttProviderName] = sttProvider.settings;
|
||||
saveSettingsDebounced();
|
||||
console.info(`Saved settings ${sttProviderName} ${JSON.stringify(sttProvider.settings)}`);
|
||||
}
|
||||
|
||||
//#############################//
|
||||
// Extension UI and Settings //
|
||||
//#############################//
|
||||
|
||||
const defaultSettings = {
|
||||
currentProvider: "None",
|
||||
messageMode: "append",
|
||||
messageMappingText: "",
|
||||
messageMapping: [],
|
||||
messageMappingEnabled: false
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
if (Object.keys(extension_settings.speech_recognition).length === 0) {
|
||||
Object.assign(extension_settings.speech_recognition, defaultSettings)
|
||||
}
|
||||
$('#speech_recognition_enabled').prop('checked',extension_settings.speech_recognition.enabled);
|
||||
$('#speech_recognition_message_mode').val(extension_settings.speech_recognition.messageMode);
|
||||
|
||||
if (extension_settings.speech_recognition.messageMappingText.length > 0) {
|
||||
$('#speech_recognition_message_mapping').val(extension_settings.speech_recognition.messageMappingText);
|
||||
}
|
||||
|
||||
$('#speech_recognition_message_mapping_enabled').prop('checked',extension_settings.speech_recognition.messageMappingEnabled);
|
||||
}
|
||||
|
||||
async function onMessageModeChange() {
|
||||
extension_settings.speech_recognition.messageMode = $('#speech_recognition_message_mode').val();
|
||||
|
||||
if(sttProviderName != "Browser" & extension_settings.speech_recognition.messageMode == "auto_send") {
|
||||
$("#speech_recognition_wait_response_div").show()
|
||||
}
|
||||
else {
|
||||
$("#speech_recognition_wait_response_div").hide()
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function onMessageMappingChange() {
|
||||
let array = $('#speech_recognition_message_mapping').val().split(",");
|
||||
array = array.map(element => {return element.trim();});
|
||||
array = array.filter((str) => str !== '');
|
||||
extension_settings.speech_recognition.messageMapping = {};
|
||||
for (const text of array) {
|
||||
if (text.includes("=")) {
|
||||
const pair = text.toLowerCase().split("=")
|
||||
extension_settings.speech_recognition.messageMapping[pair[0].trim()] = pair[1].trim()
|
||||
console.debug(DEBUG_PREFIX+"Added mapping", pair[0],"=>", extension_settings.speech_recognition.messageMapping[pair[0]]);
|
||||
}
|
||||
|
||||
const recognition = new speechRecognition();
|
||||
|
||||
if (settings.grammar && speechRecognitionList) {
|
||||
speechRecognitionList.addFromString(settings.grammar, 1);
|
||||
recognition.grammars = speechRecognitionList;
|
||||
else {
|
||||
console.debug(DEBUG_PREFIX+"Wrong syntax for message mapping, no '=' found in:", text);
|
||||
}
|
||||
}
|
||||
|
||||
$("#speech_recognition_message_mapping_status").text("Message mapping updated to: "+JSON.stringify(extension_settings.speech_recognition.messageMapping))
|
||||
console.debug(DEBUG_PREFIX+"Updated message mapping", extension_settings.speech_recognition.messageMapping);
|
||||
extension_settings.speech_recognition.messageMappingText = $('#speech_recognition_message_mapping').val()
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = true;
|
||||
// TODO: This should be configurable.
|
||||
recognition.lang = 'en-US'; // Set the language to English (US).
|
||||
async function onMessageMappingEnabledClick() {
|
||||
extension_settings.speech_recognition.messageMappingEnabled = $('#speech_recognition_message_mapping_enabled').is(':checked');
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
const $textarea = this;
|
||||
const $button = $('<div class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
|
||||
$(document).ready(function () {
|
||||
function addExtensionControls() {
|
||||
const settingsHtml = `
|
||||
<div id="speech_recognition_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Speech Recognition</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div>
|
||||
<span>Select Speech-to-text Provider</span> </br>
|
||||
<select id="speech_recognition_provider">
|
||||
</select>
|
||||
</div>
|
||||
<div id="speech_recognition_message_mode_div">
|
||||
<span>Message Mode</span> </br>
|
||||
<select id="speech_recognition_message_mode">
|
||||
<option value="append">Append</option>
|
||||
<option value="replace">Replace</option>
|
||||
<option value="auto_send">Auto send</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="speech_recognition_message_mapping_div">
|
||||
<span>Message Mapping</span>
|
||||
<textarea id="speech_recognition_message_mapping" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated phrases mapping, example:\ncommand delete = /del 2,\nslash delete = /del 2,\nsystem roll = /roll 2d6,\nhey continue = /continue"></textarea>
|
||||
<span id="speech_recognition_message_mapping_status"></span>
|
||||
<label class="checkbox_label" for="speech_recognition_message_mapping_enabled">
|
||||
<input type="checkbox" id="speech_recognition_message_mapping_enabled" name="speech_recognition_message_mapping_enabled">
|
||||
<small>Enable messages mapping</small>
|
||||
</label>
|
||||
</div>
|
||||
<form id="speech_recognition_provider_settings" class="inline-drawer-content">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$('#extensions_settings').append(settingsHtml);
|
||||
$('#speech_recognition_provider_settings').on('input', onSttProviderSettingsInput);
|
||||
for (const provider in sttProviders) {
|
||||
$('#speech_recognition_provider').append($("<option />").val(provider).text(provider));
|
||||
console.debug(DEBUG_PREFIX+"added option "+provider);
|
||||
}
|
||||
$('#speech_recognition_provider').on('change', onSttProviderChange);
|
||||
$('#speech_recognition_message_mode').on('change', onMessageModeChange);
|
||||
$('#speech_recognition_message_mapping').on('change', onMessageMappingChange);
|
||||
$('#speech_recognition_message_mapping_enabled').on('click', onMessageMappingEnabledClick);
|
||||
|
||||
const $button = $('<div id="microphone_button" class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
|
||||
$('#send_but_sheld').prepend($button);
|
||||
|
||||
let listening = false;
|
||||
$button.on('click', function () {
|
||||
if (listening) {
|
||||
recognition.stop();
|
||||
} else {
|
||||
recognition.start();
|
||||
}
|
||||
listening = !listening;
|
||||
});
|
||||
}
|
||||
addExtensionControls(); // No init dependencies
|
||||
loadSettings(); // Depends on Extension Controls and loadTtsProvider
|
||||
loadSttProvider(extension_settings.speech_recognition.currentProvider); // No dependencies
|
||||
|
||||
let initialText = '';
|
||||
|
||||
recognition.onresult = function (speechEvent) {
|
||||
let finalTranscript = '';
|
||||
let interimTranscript = ''
|
||||
|
||||
for (let i = speechEvent.resultIndex; i < speechEvent.results.length; ++i) {
|
||||
const transcript = speechEvent.results[i][0].transcript;
|
||||
|
||||
if (speechEvent.results[i].isFinal) {
|
||||
let interim = capitalizeInterim(transcript);
|
||||
if (interim != '') {
|
||||
let final = finalTranscript;
|
||||
final = composeValues(final, interim) + '.';
|
||||
finalTranscript = final;
|
||||
recognition.abort();
|
||||
listening = false;
|
||||
}
|
||||
interimTranscript = ' ';
|
||||
} else {
|
||||
interimTranscript += transcript;
|
||||
}
|
||||
}
|
||||
|
||||
interimTranscript = capitalizeInterim(interimTranscript);
|
||||
|
||||
$textarea.val(initialText + finalTranscript + interimTranscript);
|
||||
};
|
||||
|
||||
recognition.onerror = function (event) {
|
||||
console.error('Error occurred in recognition:', event.error);
|
||||
};
|
||||
|
||||
recognition.onend = function () {
|
||||
listening = false;
|
||||
$button.toggleClass('fa-microphone fa-microphone-slash');
|
||||
};
|
||||
|
||||
recognition.onstart = function () {
|
||||
initialText = $textarea.val();
|
||||
$button.toggleClass('fa-microphone fa-microphone-slash');
|
||||
};
|
||||
};
|
||||
}(jQuery));
|
||||
|
||||
jQuery(() => {
|
||||
const $textarea = $('#send_textarea');
|
||||
$textarea.speechRecognitionPlugin();
|
||||
});
|
||||
//const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
//setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
|
||||
//moduleWorker();
|
||||
})
|
||||
|
@@ -2,10 +2,13 @@
|
||||
"display_name": "Speech Recognition",
|
||||
"loading_order": 13,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"optional": [
|
||||
"vosk-speech-recognition",
|
||||
"whisper-speech-recognition"
|
||||
],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee#1207",
|
||||
"version": "1.0.0",
|
||||
"author": "Cohee#1207 and Keij#6799",
|
||||
"version": "1.1.0",
|
||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
||||
}
|
||||
|
65
public/scripts/extensions/speech-recognition/vosk.js
Normal file
65
public/scripts/extensions/speech-recognition/vosk.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { getApiUrl, doExtrasFetch } from "../../extensions.js";
|
||||
export { VoskSttProvider }
|
||||
|
||||
const DEBUG_PREFIX = "<Speech Recognition module (Vosk)> "
|
||||
|
||||
class VoskSttProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings
|
||||
|
||||
defaultSettings = {
|
||||
}
|
||||
|
||||
get settingsHtml() {
|
||||
let html = ""
|
||||
return html
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
// Used when provider settings are updated from UI
|
||||
}
|
||||
|
||||
loadSettings(settings) {
|
||||
// Populate Provider UI given input settings
|
||||
if (Object.keys(settings).length == 0) {
|
||||
console.debug(DEBUG_PREFIX+"Using default vosk STT extension settings")
|
||||
}
|
||||
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings
|
||||
|
||||
for (const key in settings){
|
||||
if (key in this.settings){
|
||||
this.settings[key] = settings[key]
|
||||
} else {
|
||||
throw `Invalid setting passed to STT extension: ${key}`
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX+"Vosk STT settings loaded")
|
||||
}
|
||||
|
||||
async processAudio(audioblob) {
|
||||
var requestData = new FormData();
|
||||
requestData.append('AudioFile', audioblob, 'record.wav');
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/speech-recognition/vosk/process-audio';
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
body: requestData,
|
||||
});
|
||||
|
||||
if (!apiResult.ok) {
|
||||
toastr.error(apiResult.statusText, 'STT Generation Failed (Vosk)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
||||
}
|
||||
|
||||
const result = await apiResult.json();
|
||||
return result.transcript;
|
||||
}
|
||||
}
|
67
public/scripts/extensions/speech-recognition/whisper.js
Normal file
67
public/scripts/extensions/speech-recognition/whisper.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { getApiUrl, doExtrasFetch } from "../../extensions.js";
|
||||
export { WhisperSttProvider }
|
||||
|
||||
const DEBUG_PREFIX = "<Speech Recognition module (Vosk)> "
|
||||
|
||||
class WhisperSttProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings
|
||||
|
||||
defaultSettings = {
|
||||
//model_path: "",
|
||||
}
|
||||
|
||||
get settingsHtml() {
|
||||
let html = ""
|
||||
return html
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
// Used when provider settings are updated from UI
|
||||
}
|
||||
|
||||
loadSettings(settings) {
|
||||
// Populate Provider UI given input settings
|
||||
if (Object.keys(settings).length == 0) {
|
||||
console.debug(DEBUG_PREFIX+"Using default Whisper STT extension settings")
|
||||
}
|
||||
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings
|
||||
|
||||
for (const key in settings){
|
||||
if (key in this.settings){
|
||||
this.settings[key] = settings[key]
|
||||
} else {
|
||||
throw `Invalid setting passed to STT extension: ${key}`
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(DEBUG_PREFIX+"Whisper STT settings loaded")
|
||||
}
|
||||
|
||||
async processAudio(audioblob) {
|
||||
var requestData = new FormData();
|
||||
requestData.append('AudioFile', audioblob, 'record.wav');
|
||||
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/speech-recognition/whisper/process-audio';
|
||||
|
||||
const apiResult = await doExtrasFetch(url, {
|
||||
method: 'POST',
|
||||
body: requestData,
|
||||
});
|
||||
|
||||
if (!apiResult.ok) {
|
||||
toastr.error(apiResult.statusText, 'STT Generation Failed (Whisper)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
||||
}
|
||||
|
||||
const result = await apiResult.json();
|
||||
return result.transcript;
|
||||
}
|
||||
|
||||
}
|
@@ -8,10 +8,13 @@ import {
|
||||
getRequestHeaders,
|
||||
event_types,
|
||||
eventSource,
|
||||
appendImageToMessage
|
||||
appendImageToMessage,
|
||||
generateQuietPrompt,
|
||||
this_chid,
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment } from "../../utils.js";
|
||||
import { selected_group } from "../../group-chats.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
@@ -39,6 +42,15 @@ const generationMode = {
|
||||
FREE: 6,
|
||||
}
|
||||
|
||||
const modeLabels = {
|
||||
[generationMode.CHARACTER]: 'Character ("Yourself")',
|
||||
[generationMode.FACE]: 'Portrait ("Your Face")',
|
||||
[generationMode.USER]: 'User ("Me")',
|
||||
[generationMode.SCENARIO]: 'Scenario ("The Whole Story")',
|
||||
[generationMode.NOW]: 'Last Message',
|
||||
[generationMode.RAW_LAST]: 'Raw Last Message',
|
||||
}
|
||||
|
||||
const triggerWords = {
|
||||
[generationMode.CHARACTER]: ['you'],
|
||||
[generationMode.USER]: ['me'],
|
||||
@@ -48,7 +60,7 @@ const triggerWords = {
|
||||
[generationMode.FACE]: ['face'],
|
||||
}
|
||||
|
||||
const quietPrompts = {
|
||||
const promptTemplates = {
|
||||
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
|
||||
[generationMode.CHARACTER]: "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,']",
|
||||
//face-specific prompt
|
||||
@@ -134,6 +146,8 @@ const defaultSettings = {
|
||||
|
||||
// Refine mode
|
||||
refine_mode: false,
|
||||
|
||||
prompts: promptTemplates,
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
@@ -141,6 +155,14 @@ async function loadSettings() {
|
||||
Object.assign(extension_settings.sd, defaultSettings);
|
||||
}
|
||||
|
||||
if (extension_settings.sd.prompts === undefined) {
|
||||
extension_settings.sd.prompts = promptTemplates;
|
||||
}
|
||||
|
||||
if (extension_settings.sd.character_prompts === undefined) {
|
||||
extension_settings.sd.character_prompts = {};
|
||||
}
|
||||
|
||||
$('#sd_scale').val(extension_settings.sd.scale).trigger('input');
|
||||
$('#sd_steps').val(extension_settings.sd.steps).trigger('input');
|
||||
$('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input');
|
||||
@@ -154,9 +176,104 @@ async function loadSettings() {
|
||||
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
|
||||
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
|
||||
|
||||
addPromptTemplates();
|
||||
|
||||
await Promise.all([loadSamplers(), loadModels()]);
|
||||
}
|
||||
|
||||
function addPromptTemplates() {
|
||||
$('#sd_prompt_templates').empty();
|
||||
|
||||
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts)) {
|
||||
const label = $('<label></label>')
|
||||
.text(modeLabels[name])
|
||||
.attr('for', `sd_prompt_${name}`);
|
||||
const textarea = $('<textarea></textarea>')
|
||||
.addClass('textarea_compact text_pole')
|
||||
.attr('id', `sd_prompt_${name}`)
|
||||
.attr('rows', 6)
|
||||
.val(prompt).on('input', () => {
|
||||
extension_settings.sd.prompts[name] = textarea.val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
const button = $('<button></button>')
|
||||
.addClass('menu_button fa-solid fa-undo')
|
||||
.attr('title', 'Restore default')
|
||||
.on('click', () => {
|
||||
textarea.val(promptTemplates[name]);
|
||||
extension_settings.sd.prompts[name] = promptTemplates[name];
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
const container = $('<div></div>')
|
||||
.addClass('title_restorable')
|
||||
.append(label)
|
||||
.append(button)
|
||||
$('#sd_prompt_templates').append(container);
|
||||
$('#sd_prompt_templates').append(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
async function refinePrompt(prompt) {
|
||||
if (extension_settings.sd.refine_mode) {
|
||||
const refinedPrompt = await callPopup('<h3>Review and edit the prompt:</h3>Press "Cancel" to abort the image generation.', 'input', prompt, { rows: 5, okButton: 'Generate' });
|
||||
|
||||
if (refinedPrompt) {
|
||||
return refinedPrompt;
|
||||
} else {
|
||||
throw new Error('Generation aborted by user.');
|
||||
}
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
function onChatChanged() {
|
||||
if (this_chid === undefined || selected_group) {
|
||||
$('#sd_character_prompt_block').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$('#sd_character_prompt_block').show();
|
||||
const key = getCharaFilename(this_chid);
|
||||
$('#sd_character_prompt').val(key ? (extension_settings.sd.character_prompts[key] || '') : '');
|
||||
}
|
||||
|
||||
function onCharacterPromptInput() {
|
||||
const key = getCharaFilename(this_chid);
|
||||
extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val();
|
||||
resetScrollHeight($(this));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function getCharacterPrefix() {
|
||||
if (selected_group) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const key = getCharaFilename(this_chid);
|
||||
|
||||
if (key) {
|
||||
return extension_settings.sd.character_prompts[key] || '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function combinePrefixes(str1, str2) {
|
||||
if (!str2) {
|
||||
return str1;
|
||||
}
|
||||
|
||||
// Remove leading/trailing white spaces and commas from the strings
|
||||
str1 = str1.trim().replace(/^,|,$/g, '');
|
||||
str2 = str2.trim().replace(/^,|,$/g, '');
|
||||
|
||||
// Combine the strings with a comma between them
|
||||
var result = `${str1}, ${str2},`;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function onRefineModeInput() {
|
||||
extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@@ -383,7 +500,7 @@ function getQuietPrompt(mode, trigger) {
|
||||
return trigger;
|
||||
}
|
||||
|
||||
return substituteParams(stringFormat(quietPrompts[mode], trigger));
|
||||
return substituteParams(stringFormat(extension_settings.sd.prompts[mode], trigger));
|
||||
}
|
||||
|
||||
function processReply(str) {
|
||||
@@ -438,7 +555,8 @@ async function generatePicture(_, trigger, message, callback) {
|
||||
|
||||
const prevSDHeight = extension_settings.sd.height;
|
||||
if (generationType == generationMode.FACE) {
|
||||
extension_settings.sd.height = extension_settings.sd.width * 1.5;
|
||||
// Round to nearest multiple of 64
|
||||
extension_settings.sd.height = Math.round(extension_settings.sd.height * 1.5 / 64) * 64;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -475,31 +593,16 @@ async function getPrompt(generationType, message, trigger, quiet_prompt) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (generationType !== generationMode.FREE) {
|
||||
prompt = await refinePrompt(prompt);
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
async function generatePrompt(quiet_prompt) {
|
||||
let reply = processReply(await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await getContext().generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
}
|
||||
}));
|
||||
|
||||
if (extension_settings.sd.refine_mode) {
|
||||
const refinedPrompt = await callPopup('<h3>Review and edit the generated prompt:</h3>Press "Cancel" to abort the image generation.', 'input', reply, { rows: 5, okButton: 'Generate' });
|
||||
|
||||
if (refinedPrompt) {
|
||||
reply = refinedPrompt;
|
||||
} else {
|
||||
throw new Error('Generation aborted by user.');
|
||||
}
|
||||
}
|
||||
|
||||
return reply;
|
||||
const reply = await generateQuietPrompt(quiet_prompt);
|
||||
return processReply(reply);
|
||||
}
|
||||
|
||||
async function sendGenerationRequest(prompt, callback) {
|
||||
@@ -524,7 +627,7 @@ async function generateExtrasImage(prompt, callback) {
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: extension_settings.sd.prompt_prefix,
|
||||
prompt_prefix: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()),
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
@@ -552,7 +655,7 @@ async function generateHordeImage(prompt, callback) {
|
||||
scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
prompt_prefix: extension_settings.sd.prompt_prefix,
|
||||
prompt_prefix: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()),
|
||||
negative_prompt: extension_settings.sd.negative_prompt,
|
||||
model: extension_settings.sd.model,
|
||||
nsfw: extension_settings.sd.horde_nsfw,
|
||||
@@ -690,7 +793,9 @@ async function sdMessageButton(e) {
|
||||
try {
|
||||
setBusyIcon(true);
|
||||
if (hasSavedImage) {
|
||||
const prompt = message?.extra?.title;
|
||||
const prompt = await refinePrompt(message.extra.title);
|
||||
message.extra.title = prompt;
|
||||
|
||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||
await sendGenerationRequest(prompt, saveGeneratedImage);
|
||||
}
|
||||
@@ -763,60 +868,74 @@ jQuery(async () => {
|
||||
<div class="sd_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Stable Diffusion</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
<b>Stable Diffusion</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<br>
|
||||
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
|
||||
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
|
||||
<input id="sd_refine_mode" type="checkbox" />
|
||||
Edit prompts before generation
|
||||
</label>
|
||||
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde" type="checkbox" />
|
||||
Use Stable Horde
|
||||
</label>
|
||||
<label style="margin-left:1em;" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
Allow NSFW images from Horde
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
|
||||
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
|
||||
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
|
||||
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
|
||||
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
|
||||
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
|
||||
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
|
||||
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
|
||||
<div class="flex-container marginTop10 margin-bot-10px">
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_restore_faces" type="checkbox" />
|
||||
Restore Faces
|
||||
</label>
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_enable_hr" type="checkbox" />
|
||||
Hires. Fix
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_model">Stable Diffusion model</label>
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<div class="flex-container flexGap5 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
Karras (only for Horde, not all samplers supported)
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_prompt_prefix">Common prompt prefix</label>
|
||||
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
<div id="sd_character_prompt_block">
|
||||
<label for="sd_character_prompt">Character-specific prompt prefix</label>
|
||||
<small>Won't be used in groups.</small>
|
||||
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prefix. Example: female, green eyes, brown hair, pink shirt"></textarea>
|
||||
</div>
|
||||
<label for="sd_negative_prompt">Negative prompt</label>
|
||||
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
|
||||
<br>
|
||||
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
|
||||
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
|
||||
<input id="sd_refine_mode" type="checkbox" />
|
||||
Edit prompts before generation
|
||||
</label>
|
||||
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde" type="checkbox" />
|
||||
Use Stable Horde
|
||||
</label>
|
||||
<label style="margin-left:1em;" class="checkbox_label">
|
||||
<input id="sd_horde_nsfw" type="checkbox" />
|
||||
Allow NSFW images from Horde
|
||||
</label>
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>SD Prompt Templates</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
|
||||
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
|
||||
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
|
||||
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
|
||||
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
|
||||
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
|
||||
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
|
||||
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
|
||||
<div class="flex-container marginTop10 margin-bot-10px">
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_restore_faces" type="checkbox" />
|
||||
Restore Faces
|
||||
</label>
|
||||
<label class="flex1 checkbox_label">
|
||||
<input id="sd_enable_hr" type="checkbox" />
|
||||
Hires. Fix
|
||||
</label>
|
||||
<div id="sd_prompt_templates" class="inline-drawer-content">
|
||||
</div>
|
||||
<label for="sd_model">Stable Diffusion model</label>
|
||||
<select id="sd_model"></select>
|
||||
<label for="sd_sampler">Sampling method</label>
|
||||
<select id="sd_sampler"></select>
|
||||
<div class="flex-container flexGap5 margin-bot-10px">
|
||||
<label class="checkbox_label">
|
||||
<input id="sd_horde_karras" type="checkbox" />
|
||||
Karras (only for Horde, not all samplers supported)
|
||||
</label>
|
||||
</div>
|
||||
<label for="sd_prompt_prefix">Generated prompt prefix</label>
|
||||
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="2"></textarea>
|
||||
<label for="sd_negative_prompt">Negative prompt</label>
|
||||
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="2"></textarea>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@@ -835,16 +954,21 @@ jQuery(async () => {
|
||||
$('#sd_restore_faces').on('input', onRestoreFacesInput);
|
||||
$('#sd_enable_hr').on('input', onHighResFixInput);
|
||||
$('#sd_refine_mode').on('input', onRefineModeInput);
|
||||
$('#sd_character_prompt').on('input', onCharacterPromptInput);
|
||||
$('#sd_character_prompt_block').hide();
|
||||
|
||||
$('.sd_settings .inline-drawer-toggle').on('click', function () {
|
||||
initScrollHeight($("#sd_prompt_prefix"));
|
||||
initScrollHeight($("#sd_negative_prompt"));
|
||||
initScrollHeight($("#sd_character_prompt"));
|
||||
})
|
||||
|
||||
eventSource.on(event_types.EXTRAS_CONNECTED, async () => {
|
||||
await Promise.all([loadSamplers(), loadModels()]);
|
||||
});
|
||||
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||
|
||||
await loadSettings();
|
||||
$('body').addClass('sd');
|
||||
});
|
||||
|
@@ -24,6 +24,6 @@
|
||||
}
|
||||
|
||||
#sd_dropdown {
|
||||
z-index: 3000;
|
||||
z-index: 30000;
|
||||
backdrop-filter: blur(--SmartThemeBlurStrength);
|
||||
}
|
||||
}
|
||||
|
403
public/scripts/extensions/tts/coquitts.js
Normal file
403
public/scripts/extensions/tts/coquitts.js
Normal file
@@ -0,0 +1,403 @@
|
||||
import { eventSource, event_types } from "../../../script.js"
|
||||
import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js"
|
||||
|
||||
export { CoquiTtsProvider }
|
||||
|
||||
function throwIfModuleMissing() {
|
||||
if (!modules.includes('coqui-tts')) {
|
||||
toastr.error(`Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`)
|
||||
throw new Error(`Coqui TTS module not loaded.`)
|
||||
}
|
||||
}
|
||||
|
||||
class CoquiTtsProvider {
|
||||
//########//
|
||||
// Config //
|
||||
//########//
|
||||
|
||||
settings
|
||||
voices = []
|
||||
separator = ' .. '
|
||||
|
||||
defaultSettings = {
|
||||
voiceMap: {}
|
||||
}
|
||||
|
||||
|
||||
get settingsHtml() {
|
||||
let html = `
|
||||
<div class="flex wide100p flexGap10 alignitemscenter">
|
||||
<div style="flex: 80%;">
|
||||
<label for="coqui_model">Model:</label>
|
||||
<select id="coqui_model">
|
||||
<option value="none">Select Model</option>
|
||||
<!-- Add more model options here -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex justifyCenter" style="flex: 20%;">
|
||||
<button id="coqui_preview" class="menu_button menu_button_icon wide100p" type="button">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex wide100p flexGap10">
|
||||
<div class="flex1">
|
||||
<label for="coqui_speaker">Speaker:</label>
|
||||
<select id="coqui_speaker">
|
||||
<!-- Add more speaker options here -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex1">
|
||||
<label for="coqui_language">Language:</label>
|
||||
<select id="coqui_language">
|
||||
<!-- Add more language options here -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
return html
|
||||
}
|
||||
|
||||
onSettingsChange() {
|
||||
}
|
||||
|
||||
loadSettings(settings) {
|
||||
// Pupulate Provider UI given input settings
|
||||
if (Object.keys(settings).length == 0) {
|
||||
console.info("Using default TTS Provider settings")
|
||||
}
|
||||
|
||||
const modelSelect = document.getElementById('coqui_model');
|
||||
const previewButton = document.getElementById('coqui_preview');
|
||||
previewButton.addEventListener('click', () => {
|
||||
const selectedModel = modelSelect.value;
|
||||
this.sampleTtsVoice(selectedModel);
|
||||
});//add event listener to button
|
||||
|
||||
previewButton.disabled = true;
|
||||
previewButton.innerText = "Select Model";
|
||||
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings
|
||||
|
||||
for (const key in settings) {
|
||||
if (key in this.settings) {
|
||||
this.settings[key] = settings[key]
|
||||
} else {
|
||||
throw `Invalid setting passed to TTS Provider: ${key}`
|
||||
}
|
||||
}
|
||||
|
||||
const textexample = document.getElementById('tts_voice_map');
|
||||
textexample.placeholder = 'Enter comma separated map of charName:ttsName[speakerID][langID]. Example: \nAqua:tts_models--en--ljspeech--glow-tts\model_file.pth,\nDarkness:tts_models--multilingual--multi-dataset--your_tts\model_file.pth[2][3]';
|
||||
|
||||
//Load models function
|
||||
eventSource.on(event_types.EXTRAS_CONNECTED, () => {
|
||||
this.getModels();
|
||||
});
|
||||
this.onttsCoquiHideButtons();
|
||||
console.info("Settings loaded")
|
||||
}
|
||||
|
||||
async onttsCoquiHideButtons() {
|
||||
// Get references to the select element and the two input elements
|
||||
const ttsProviderSelect = document.getElementById('tts_provider');
|
||||
const ttsVoicesInput = document.getElementById('tts_voices');
|
||||
const ttsPreviewInput = document.getElementById('tts_preview');
|
||||
|
||||
ttsProviderSelect.addEventListener('click', () => {
|
||||
this.getModels();
|
||||
});
|
||||
|
||||
// Add an event listener to the 'change' event of the tts_provider select element
|
||||
ttsProviderSelect.addEventListener('change', () => {
|
||||
// Check if the selected value is 'Coqui'
|
||||
if (ttsProviderSelect.value === 'Coqui') {
|
||||
ttsVoicesInput.style.display = 'none'; // Hide the tts_voices input
|
||||
ttsPreviewInput.style.display = ''; // Show the tts_preview input
|
||||
} else {
|
||||
ttsVoicesInput.style.display = ''; // Show the tts_voices input
|
||||
ttsPreviewInput.style.display = 'none'; // Hide the tts_preview input
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
return
|
||||
}
|
||||
|
||||
async getLang() {
|
||||
try {
|
||||
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/multlang`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const voiceData = await response.json();
|
||||
|
||||
const languageSelect = document.getElementById('coqui_language');
|
||||
languageSelect.innerHTML = ''; // Clear existing options
|
||||
|
||||
if (Object.keys(voiceData).length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.value = 'none';
|
||||
option.textContent = 'None';
|
||||
languageSelect.appendChild(option);
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(voiceData)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = key;
|
||||
option.textContent = key + ": " + value;
|
||||
languageSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//console.error('Error fetching voice data:', error);
|
||||
|
||||
// Remove all options except "None"
|
||||
const languageSelect = document.getElementById('coqui_language');
|
||||
languageSelect.innerHTML = '';
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = 'none';
|
||||
option.textContent = 'None';
|
||||
languageSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getSpeakers() {
|
||||
try {
|
||||
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/multspeaker`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const voiceData = await response.json();
|
||||
|
||||
const speakerSelect = document.getElementById('coqui_speaker');
|
||||
speakerSelect.innerHTML = ''; // Clear existing options
|
||||
|
||||
if (Object.keys(voiceData).length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.value = 'none';
|
||||
option.textContent = 'None';
|
||||
speakerSelect.appendChild(option);
|
||||
} else {
|
||||
for (const [index, name] of Object.entries(voiceData)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = index;
|
||||
option.textContent = index + ": " + name;
|
||||
speakerSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//console.error('Error fetching voice data:', error);
|
||||
|
||||
// Remove all options except "None"
|
||||
const speakerSelect = document.getElementById('coqui_speaker');
|
||||
speakerSelect.innerHTML = '';
|
||||
|
||||
const option = document.createElement('option');
|
||||
option.value = 'none';
|
||||
option.textContent = 'None';
|
||||
speakerSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
async getModels() {
|
||||
try {
|
||||
throwIfModuleMissing();
|
||||
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/list`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const voiceIds = await response.json();
|
||||
|
||||
const modelSelect = document.getElementById('coqui_model');
|
||||
if (voiceIds.length === 0) {
|
||||
const option = document.createElement('option');
|
||||
option.value = 'none';
|
||||
option.textContent = 'Select Model';
|
||||
modelSelect.appendChild(option);
|
||||
} else {
|
||||
voiceIds.forEach(voiceId => {
|
||||
const option = document.createElement('option');
|
||||
option.value = voiceId;
|
||||
option.textContent = voiceId;
|
||||
modelSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Update provider endpoint on model selection change
|
||||
modelSelect.addEventListener('change', () => {
|
||||
const selectedModel = modelSelect.value;
|
||||
this.LoadModel(selectedModel);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching voice IDs:', error);
|
||||
|
||||
// Add "None" option when the request fails or the response is empty
|
||||
const modelSelect = document.getElementById('coqui_model');
|
||||
const option = document.createElement('option');
|
||||
option.value = 'none';
|
||||
option.textContent = 'None';
|
||||
modelSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
async LoadModel(selectedModel) {
|
||||
const previewButton = document.getElementById('coqui_preview');
|
||||
previewButton.disabled = true;
|
||||
previewButton.innerText = "Loading";
|
||||
try {
|
||||
throwIfModuleMissing();
|
||||
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/load?_model=${selectedModel}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
this.getSpeakers();
|
||||
this.getLang();
|
||||
|
||||
const previewButton = document.getElementById('coqui_preview');
|
||||
previewButton.disabled = false;
|
||||
previewButton.innerText = "Play";
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating provider endpoint:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async getVoice(voiceName) {
|
||||
//tts_models--multilingual--multi-dataset--your_tts\model_file.pth[2][1]
|
||||
//tts_models--en--ljspeech--glow-tts\model_file.pth
|
||||
|
||||
let _voiceNameOrg = voiceName; // Store the original voiceName in a variable _voiceNameOrg
|
||||
voiceName = voiceName.replace(/(\[\d+\])+$/, ''); // For example, converts 'model[2][1]' to 'model'
|
||||
|
||||
this.voices = []; //reset for follow up runs
|
||||
|
||||
if (this.voices.length === 0) { this.voices = await this.fetchCheckMap(); }
|
||||
|
||||
// Search for a voice object in the 'this.voices' array where the 'name' property matches the provided 'voiceName'
|
||||
|
||||
//const match = this.voices.find((CoquiVoice) => CoquiVoice.name === voiceName);
|
||||
const match = this.voices.find((CoquiVoice) => CoquiVoice.name === voiceName);
|
||||
|
||||
// If no match is found, throw an error indicating that the TTS Voice name was not found
|
||||
if (!match) {
|
||||
throw new Error(`TTS Voice name ${voiceName} not found`);
|
||||
} else {
|
||||
match.name = _voiceNameOrg;
|
||||
match.voice_id = _voiceNameOrg;
|
||||
}
|
||||
// Return the matched voice object (with the 'name' property updated if a match was found)
|
||||
return match;
|
||||
}
|
||||
|
||||
async fetchCheckMap() {
|
||||
const endpoint = `${getApiUrl()}/api/coqui-tts/checkmap`;
|
||||
const response = await doExtrasFetch(endpoint);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
|
||||
}
|
||||
const voiceData = await response.json();
|
||||
const voices = voiceData.map((voice) => ({
|
||||
id: voice.name,
|
||||
name: voice.id, // this is the issue!!!
|
||||
voice_id: voice.id, // this is the issue!!!
|
||||
//preview_url: false,
|
||||
lang: voice.lang,
|
||||
}));
|
||||
return voices;
|
||||
}
|
||||
|
||||
async fetchTtsVoiceIds() {
|
||||
throwIfModuleMissing();
|
||||
const endpoint = `${getApiUrl()}/api/coqui-tts/speaker_id`;
|
||||
const response = await doExtrasFetch(endpoint);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
|
||||
}
|
||||
const voiceData = await response.json();
|
||||
const voices = voiceData.map((voice) => ({
|
||||
id: voice.name,
|
||||
name: voice.id, //add filename here
|
||||
voice_id: voice.id,
|
||||
//preview_url: false,
|
||||
//preview_url: `${getApiUrl()}/api/coqui-tts/download?model=${voice.id}`,
|
||||
//http://localhost:5100/api/coqui-tts/speaker_id/tts_models/en/ljspeech/speedy-speech
|
||||
lang: voice.lang,
|
||||
}));
|
||||
return voices;
|
||||
}
|
||||
|
||||
sampleTtsVoice(voiceId) {
|
||||
// Get the selected values of speaker and language
|
||||
const speakerSelect = document.getElementById('coqui_speaker');
|
||||
const languageSelect = document.getElementById('coqui_language');
|
||||
const selectedSpeaker = speakerSelect.value;
|
||||
const selectedLanguage = languageSelect.value;
|
||||
|
||||
// Construct the URL with the selected values
|
||||
const url = `${getApiUrl()}/api/coqui-tts/tts?text=The%20Quick%20Brown%20Fox%20Jumps%20Over%20the%20Lazy%20Dog.&speaker_id=${voiceId}&style_wav=&language_id=${selectedLanguage}&mspker=${selectedSpeaker}`;
|
||||
|
||||
doExtrasFetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const audioUrl = URL.createObjectURL(blob);
|
||||
// Play the audio
|
||||
const audio = new Audio(audioUrl);
|
||||
audio.play();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error performing TTS request:', error);
|
||||
});
|
||||
}
|
||||
|
||||
previewTtsVoice(voiceId) { //button on avail voices
|
||||
throwIfModuleMissing();
|
||||
const url = `${getApiUrl()}/api/coqui-tts/download?model=${voiceId}`;
|
||||
|
||||
doExtrasFetch(url)
|
||||
.then(response => response.text()) // Expecting a text response
|
||||
.then(responseText => {
|
||||
const isResponseTrue = responseText.trim().toLowerCase() === 'true';
|
||||
|
||||
if (isResponseTrue) {
|
||||
console.log("Downloading Model") //if true
|
||||
} else {
|
||||
console.error('Already Installed'); //if false
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error performing download:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async generateTts(text, voiceId) {
|
||||
const response = await this.fetchTtsGeneration(text, voiceId)
|
||||
return response
|
||||
}
|
||||
|
||||
async fetchTtsGeneration(inputText, voiceId) {
|
||||
throwIfModuleMissing();
|
||||
console.info(`Generating new TTS for voice_id ${voiceId}`);
|
||||
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/tts?text=${encodeURIComponent(inputText)}&speaker_id=${voiceId}`);
|
||||
if (!response.ok) {
|
||||
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
if (!response.ok) {
|
||||
toastr.error(response.statusText, 'TTS Generation Failed');
|
||||
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async fetchTtsFromHistory(history_item_id) {
|
||||
return Promise.resolve(history_item_id);
|
||||
}
|
||||
|
||||
}
|
@@ -4,6 +4,7 @@ import { escapeRegex, getStringHash } from '../../utils.js'
|
||||
import { EdgeTtsProvider } from './edge.js'
|
||||
import { ElevenLabsTtsProvider } from './elevenlabs.js'
|
||||
import { SileroTtsProvider } from './silerotts.js'
|
||||
import { CoquiTtsProvider } from './coquitts.js'
|
||||
import { SystemTtsProvider } from './system.js'
|
||||
import { NovelTtsProvider } from './novel.js'
|
||||
import { isMobile } from '../../RossAscends-mods.js'
|
||||
@@ -64,6 +65,7 @@ let ttsProviders = {
|
||||
ElevenLabs: ElevenLabsTtsProvider,
|
||||
Silero: SileroTtsProvider,
|
||||
System: SystemTtsProvider,
|
||||
Coqui: CoquiTtsProvider,
|
||||
Edge: EdgeTtsProvider,
|
||||
Novel: NovelTtsProvider,
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
delay,
|
||||
isDataURL,
|
||||
createThumbnail,
|
||||
extractAllWords,
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js";
|
||||
import { sortCharactersList, sortGroupMembers, loadMovingUIState } from './power-user.js';
|
||||
@@ -782,19 +783,6 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
|
||||
return memberIds;
|
||||
}
|
||||
|
||||
function extractAllWords(value) {
|
||||
const words = [];
|
||||
|
||||
if (!value) {
|
||||
return words;
|
||||
}
|
||||
|
||||
const matches = value.matchAll(/\b\w+\b/gim);
|
||||
for (let match of matches) {
|
||||
words.push(match[0].toLowerCase());
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
async function deleteGroup(id) {
|
||||
|
11
public/scripts/jquery.ui.touch-punch.min.js
vendored
Normal file
11
public/scripts/jquery.ui.touch-punch.min.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
|
@@ -11,13 +11,18 @@ export {
|
||||
};
|
||||
|
||||
const nai_settings = {
|
||||
temp_novel: 0.5,
|
||||
rep_pen_novel: 1,
|
||||
rep_pen_size_novel: 100,
|
||||
rep_pen_slope_novel: 0,
|
||||
rep_pen_freq_novel: 0,
|
||||
rep_pen_presence_novel: 0,
|
||||
tail_free_sampling_novel: 0.68,
|
||||
temperature: 0.5,
|
||||
repetition_penalty: 1,
|
||||
repetition_penalty_range: 100,
|
||||
repetition_penalty_slope: 0,
|
||||
repetition_penalty_frequency: 0,
|
||||
repetition_penalty_presence: 0,
|
||||
tail_free_sampling: 0.68,
|
||||
top_k: 0,
|
||||
top_p: 1,
|
||||
top_a: 1,
|
||||
typical_p: 1,
|
||||
min_length: 0,
|
||||
model_novel: "euterpe-v2",
|
||||
preset_settings_novel: "Classic-Euterpe",
|
||||
streaming_novel: false,
|
||||
@@ -45,13 +50,20 @@ function loadNovelPreset(preset) {
|
||||
$("#max_context_counter").text(`${preset.max_context}`);
|
||||
$("#rep_pen_size_novel").attr('max', preset.max_context);
|
||||
|
||||
nai_settings.temp_novel = preset.temperature;
|
||||
nai_settings.rep_pen_novel = preset.repetition_penalty;
|
||||
nai_settings.rep_pen_size_novel = preset.repetition_penalty_range;
|
||||
nai_settings.rep_pen_slope_novel = preset.repetition_penalty_slope;
|
||||
nai_settings.rep_pen_freq_novel = preset.repetition_penalty_frequency;
|
||||
nai_settings.rep_pen_presence_novel = preset.repetition_penalty_presence;
|
||||
nai_settings.tail_free_sampling_novel = preset.tail_free_sampling;
|
||||
nai_settings.temperature = preset.temperature;
|
||||
nai_settings.repetition_penalty = preset.repetition_penalty;
|
||||
nai_settings.repetition_penalty_range = preset.repetition_penalty_range;
|
||||
nai_settings.repetition_penalty_slope = preset.repetition_penalty_slope;
|
||||
nai_settings.repetition_penalty_frequency = preset.repetition_penalty_frequency;
|
||||
nai_settings.repetition_penalty_presence = preset.repetition_penalty_presence;
|
||||
nai_settings.tail_free_sampling = preset.tail_free_sampling;
|
||||
nai_settings.top_k = preset.top_k;
|
||||
nai_settings.top_p = preset.top_p;
|
||||
nai_settings.top_a = preset.top_a;
|
||||
nai_settings.typical_p = preset.typical_p;
|
||||
nai_settings.min_length = preset.min_length;
|
||||
nai_settings.cfg_scale = preset.cfg_scale;
|
||||
nai_settings.phrase_rep_pen = preset.phrase_rep_pen;
|
||||
loadNovelSettingsUi(nai_settings);
|
||||
}
|
||||
|
||||
@@ -59,33 +71,90 @@ function loadNovelSettings(settings) {
|
||||
//load the rest of the Novel settings without any checks
|
||||
nai_settings.model_novel = settings.model_novel;
|
||||
$(`#model_novel_select option[value=${nai_settings.model_novel}]`).attr("selected", true);
|
||||
$('#model_novel_select').val(nai_settings.model_novel);
|
||||
|
||||
nai_settings.temp_novel = settings.temp_novel;
|
||||
nai_settings.rep_pen_novel = settings.rep_pen_novel;
|
||||
nai_settings.rep_pen_size_novel = settings.rep_pen_size_novel;
|
||||
nai_settings.rep_pen_slope_novel = settings.rep_pen_slope_novel;
|
||||
nai_settings.rep_pen_freq_novel = settings.rep_pen_freq_novel;
|
||||
nai_settings.rep_pen_presence_novel = settings.rep_pen_presence_novel;
|
||||
nai_settings.tail_free_sampling_novel = settings.tail_free_sampling_novel;
|
||||
nai_settings.preset_settings_novel = settings.preset_settings_novel;
|
||||
nai_settings.temperature = settings.temperature;
|
||||
nai_settings.repetition_penalty = settings.repetition_penalty;
|
||||
nai_settings.repetition_penalty_range = settings.repetition_penalty_range;
|
||||
nai_settings.repetition_penalty_slope = settings.repetition_penalty_slope;
|
||||
nai_settings.repetition_penalty_frequency = settings.repetition_penalty_frequency;
|
||||
nai_settings.repetition_penalty_presence = settings.repetition_penalty_presence;
|
||||
nai_settings.tail_free_sampling = settings.tail_free_sampling;
|
||||
nai_settings.top_k = settings.top_k;
|
||||
nai_settings.top_p = settings.top_p;
|
||||
nai_settings.top_a = settings.top_a;
|
||||
nai_settings.typical_p = settings.typical_p;
|
||||
nai_settings.min_length = settings.min_length;
|
||||
nai_settings.phrase_rep_pen = settings.phrase_rep_pen;
|
||||
nai_settings.cfg_scale = settings.cfg_scale;
|
||||
nai_settings.streaming_novel = !!settings.streaming_novel;
|
||||
loadNovelSettingsUi(nai_settings);
|
||||
}
|
||||
|
||||
const phraseRepPenStrings = [
|
||||
null,
|
||||
"very_light",
|
||||
"light",
|
||||
"medium",
|
||||
"aggressive",
|
||||
"very_aggressive"
|
||||
]
|
||||
|
||||
function getPhraseRepPenString(phraseRepPenCounter) {
|
||||
if (phraseRepPenCounter < 1 || phraseRepPenCounter > 5) {
|
||||
return null;
|
||||
} else {
|
||||
return phraseRepPenStrings[phraseRepPenCounter];
|
||||
}
|
||||
}
|
||||
|
||||
function getPhraseRepPenCounter(phraseRepPenString) {
|
||||
if (phraseRepPenString === phraseRepPenStrings[1]) {
|
||||
return 1;
|
||||
} else if (phraseRepPenString === phraseRepPenStrings[2]) {
|
||||
return 2;
|
||||
} else if (phraseRepPenString === phraseRepPenStrings[3]) {
|
||||
return 3;
|
||||
} else if (phraseRepPenString === phraseRepPenStrings[4]) {
|
||||
return 4;
|
||||
} else if (phraseRepPenString === phraseRepPenStrings[5]) {
|
||||
return 5;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function loadNovelSettingsUi(ui_settings) {
|
||||
$("#temp_novel").val(ui_settings.temp_novel);
|
||||
$("#temp_counter_novel").text(Number(ui_settings.temp_novel).toFixed(2));
|
||||
$("#rep_pen_novel").val(ui_settings.rep_pen_novel);
|
||||
$("#rep_pen_counter_novel").text(Number(ui_settings.rep_pen_novel).toFixed(2));
|
||||
$("#rep_pen_size_novel").val(ui_settings.rep_pen_size_novel);
|
||||
$("#rep_pen_size_counter_novel").text(Number(ui_settings.rep_pen_size_novel).toFixed(0));
|
||||
$("#rep_pen_slope_novel").val(ui_settings.rep_pen_slope_novel);
|
||||
$("#rep_pen_slope_counter_novel").text(Number(`${ui_settings.rep_pen_slope_novel}`).toFixed(2));
|
||||
$("#rep_pen_freq_novel").val(ui_settings.rep_pen_freq_novel);
|
||||
$("#rep_pen_freq_counter_novel").text(Number(ui_settings.rep_pen_freq_novel).toFixed(5));
|
||||
$("#rep_pen_presence_novel").val(ui_settings.rep_pen_presence_novel);
|
||||
$("#rep_pen_presence_counter_novel").text(Number(ui_settings.rep_pen_presence_novel).toFixed(3));
|
||||
$("#tail_free_sampling_novel").val(ui_settings.tail_free_sampling_novel);
|
||||
$("#tail_free_sampling_counter_novel").text(Number(ui_settings.tail_free_sampling_novel).toFixed(3));
|
||||
$("#temp_novel").val(ui_settings.temperature);
|
||||
$("#temp_counter_novel").text(Number(ui_settings.temperature).toFixed(2));
|
||||
$("#rep_pen_novel").val(ui_settings.repetition_penalty);
|
||||
$("#rep_pen_counter_novel").text(Number(ui_settings.repetition_penalty).toFixed(2));
|
||||
$("#rep_pen_size_novel").val(ui_settings.repetition_penalty_range);
|
||||
$("#rep_pen_size_counter_novel").text(Number(ui_settings.repetition_penalty_range).toFixed(0));
|
||||
$("#rep_pen_slope_novel").val(ui_settings.repetition_penalty_slope);
|
||||
$("#rep_pen_slope_counter_novel").text(Number(`${ui_settings.repetition_penalty_slope}`).toFixed(2));
|
||||
$("#rep_pen_freq_novel").val(ui_settings.repetition_penalty_frequency);
|
||||
$("#rep_pen_freq_counter_novel").text(Number(ui_settings.repetition_penalty_frequency).toFixed(5));
|
||||
$("#rep_pen_presence_novel").val(ui_settings.repetition_penalty_presence);
|
||||
$("#rep_pen_presence_counter_novel").text(Number(ui_settings.repetition_penalty_presence).toFixed(3));
|
||||
$("#tail_free_sampling_novel").val(ui_settings.tail_free_sampling);
|
||||
$("#tail_free_sampling_counter_novel").text(Number(ui_settings.tail_free_sampling).toFixed(3));
|
||||
$("#top_k_novel").val(ui_settings.top_k);
|
||||
$("#top_k_counter_novel").text(Number(ui_settings.top_k).toFixed(0));
|
||||
$("#top_p_novel").val(ui_settings.top_p);
|
||||
$("#top_p_counter_novel").text(Number(ui_settings.top_p).toFixed(2));
|
||||
$("#top_a_novel").val(ui_settings.top_a);
|
||||
$("#top_a_counter_novel").text(Number(ui_settings.top_a).toFixed(2));
|
||||
$("#typical_p_novel").val(ui_settings.typical_p);
|
||||
$("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(2));
|
||||
$("#cfg_scale_novel").val(ui_settings.cfg_scale);
|
||||
$("#cfg_scale_counter_novel").text(Number(ui_settings.cfg_scale).toFixed(2));
|
||||
$("#phrase_rep_pen_novel").val(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
|
||||
$("#phrase_rep_pen_counter_novel").text(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
|
||||
$("#min_length_novel").val(ui_settings.min_length);
|
||||
$("#min_length_counter_novel").text(Number(ui_settings.min_length).toFixed(0));
|
||||
|
||||
$("#streaming_novel").prop('checked', ui_settings.streaming_novel);
|
||||
}
|
||||
|
||||
@@ -94,71 +163,117 @@ const sliders = [
|
||||
sliderId: "#temp_novel",
|
||||
counterId: "#temp_counter_novel",
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.temp_novel = Number(val).toFixed(2); },
|
||||
setValue: (val) => { nai_settings.temperature = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#rep_pen_novel",
|
||||
counterId: "#rep_pen_counter_novel",
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.rep_pen_novel = Number(val).toFixed(2); },
|
||||
setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#rep_pen_size_novel",
|
||||
counterId: "#rep_pen_size_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.rep_pen_size_novel = Number(val).toFixed(0); },
|
||||
setValue: (val) => { nai_settings.repetition_penalty_range = Number(val).toFixed(0); },
|
||||
},
|
||||
{
|
||||
sliderId: "#rep_pen_slope_novel",
|
||||
counterId: "#rep_pen_slope_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.rep_pen_slope_novel = Number(val).toFixed(2); },
|
||||
setValue: (val) => { nai_settings.repetition_penalty_slope = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#rep_pen_freq_novel",
|
||||
counterId: "#rep_pen_freq_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.rep_pen_freq_novel = Number(val).toFixed(5); },
|
||||
setValue: (val) => { nai_settings.repetition_penalty_frequency = Number(val).toFixed(5); },
|
||||
},
|
||||
{
|
||||
sliderId: "#rep_pen_presence_novel",
|
||||
counterId: "#rep_pen_presence_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.rep_pen_presence_novel = Number(val).toFixed(3); },
|
||||
setValue: (val) => { nai_settings.repetition_penalty_presence = Number(val).toFixed(3); },
|
||||
},
|
||||
{
|
||||
sliderId: "#tail_free_sampling_novel",
|
||||
counterId: "#tail_free_sampling_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.tail_free_sampling_novel = Number(val).toFixed(3); },
|
||||
setValue: (val) => { nai_settings.tail_free_sampling = Number(val).toFixed(3); },
|
||||
},
|
||||
{
|
||||
sliderId: "#top_k_novel",
|
||||
counterId: "#top_k_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.top_k = Number(val).toFixed(0); },
|
||||
},
|
||||
{
|
||||
sliderId: "#top_p_novel",
|
||||
counterId: "#top_p_counter_novel",
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.top_p = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#top_a_novel",
|
||||
counterId: "#top_a_counter_novel",
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.top_a = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#typical_p_novel",
|
||||
counterId: "#typical_p_counter_novel",
|
||||
format: (val) => Number(val).toFixed(2),
|
||||
setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#cfg_scale_novel",
|
||||
counterId: "#cfg_scale_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.cfg_scale = Number(val).toFixed(2); },
|
||||
},
|
||||
{
|
||||
sliderId: "#phrase_rep_pen_novel",
|
||||
counterId: "#phrase_rep_pen_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.phrase_rep_pen = getPhraseRepPenString(Number(val).toFixed(0)); },
|
||||
},
|
||||
{
|
||||
sliderId: "#min_length_novel",
|
||||
counterId: "#min_length_counter_novel",
|
||||
format: (val) => `${val}`,
|
||||
setValue: (val) => { nai_settings.min_length = Number(val).toFixed(0); },
|
||||
},
|
||||
];
|
||||
|
||||
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen) {
|
||||
const isNewModel = (nai_settings.model_novel.includes('clio') || nai_settings.model_novel.includes('kayra'));
|
||||
return {
|
||||
"input": finalPromt,
|
||||
"model": nai_settings.model_novel,
|
||||
"use_string": true,
|
||||
"temperature": parseFloat(nai_settings.temp_novel),
|
||||
"temperature": parseFloat(nai_settings.temperature),
|
||||
"max_length": this_amount_gen, // this_settings.max_length, // <= why?
|
||||
"min_length": this_settings.min_length,
|
||||
"tail_free_sampling": parseFloat(nai_settings.tail_free_sampling_novel),
|
||||
"repetition_penalty": parseFloat(nai_settings.rep_pen_novel),
|
||||
"repetition_penalty_range": parseInt(nai_settings.rep_pen_size_novel),
|
||||
"repetition_penalty_slope": parseFloat(nai_settings.rep_pen_slope_novel),
|
||||
"repetition_penalty_frequency": parseFloat(nai_settings.rep_pen_freq_novel),
|
||||
"repetition_penalty_presence": parseFloat(nai_settings.rep_pen_presence_novel),
|
||||
"top_a": this_settings.top_a,
|
||||
"top_p": this_settings.top_p,
|
||||
"top_k": this_settings.top_k,
|
||||
"typical_p": this_settings.typical_p,
|
||||
"min_length": parseInt(nai_settings.min_length),
|
||||
"tail_free_sampling": parseFloat(nai_settings.tail_free_sampling),
|
||||
"repetition_penalty": parseFloat(nai_settings.repetition_penalty),
|
||||
"repetition_penalty_range": parseInt(nai_settings.repetition_penalty_range),
|
||||
"repetition_penalty_slope": parseFloat(nai_settings.repetition_penalty_slope),
|
||||
"repetition_penalty_frequency": parseFloat(nai_settings.repetition_penalty_frequency),
|
||||
"repetition_penalty_presence": parseFloat(nai_settings.repetition_penalty_presence),
|
||||
"top_a": parseFloat(nai_settings.top_a),
|
||||
"top_p": parseFloat(nai_settings.top_p),
|
||||
"top_k": parseInt(nai_settings.top_k),
|
||||
"typical_p": parseFloat(nai_settings.typical_p),
|
||||
"cfg_scale": parseFloat(nai_settings.cfg_scale),
|
||||
"cfg_uc": "",
|
||||
"phrase_rep_pen": nai_settings.phrase_rep_pen,
|
||||
//"stop_sequences": {{187}},
|
||||
//bad_words_ids = {{50256}, {0}, {1}};
|
||||
"generate_until_sentence": true,
|
||||
"use_cache": false,
|
||||
"use_string": true,
|
||||
"return_full_text": false,
|
||||
"prefix": "vanilla",
|
||||
"prefix": isNewModel ? "special_instruct" : "vanilla",
|
||||
"order": this_settings.order,
|
||||
"streaming": nai_settings.streaming_novel,
|
||||
};
|
||||
@@ -212,7 +327,7 @@ $(document).ready(function () {
|
||||
const value = $(this).val();
|
||||
const formattedValue = slider.format(value);
|
||||
slider.setValue(value);
|
||||
$(slider.counterId).html(formattedValue);
|
||||
$(slider.counterId).text(formattedValue);
|
||||
console.log('saving');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
@@ -142,6 +142,8 @@ const default_settings = {
|
||||
max_context_unlocked: false,
|
||||
api_url_scale: '',
|
||||
show_external_models: false,
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@@ -178,6 +180,8 @@ const oai_settings = {
|
||||
max_context_unlocked: false,
|
||||
api_url_scale: '',
|
||||
show_external_models: false,
|
||||
proxy_password: '',
|
||||
assistant_prefill: '',
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@@ -400,7 +404,8 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
|
||||
|
||||
const jailbreak = power_user.prefer_character_jailbreak && jailbreakPrompt ? jailbreakPrompt : oai_settings.jailbreak_prompt;
|
||||
if (oai_settings.jailbreak_system && jailbreak) {
|
||||
const jailbreakMessage = { "role": "system", "content": substituteParams(jailbreak, name1, name2, oai_settings.jailbreak_prompt) };
|
||||
const jbContent = substituteParams(jailbreak, name1, name2, oai_settings.jailbreak_prompt).replace(/\r/gm, '').trim();
|
||||
const jailbreakMessage = { "role": "system", "content": jbContent };
|
||||
openai_msgs.push(jailbreakMessage);
|
||||
|
||||
total_count += handler_instance.count([jailbreakMessage], true, 'jailbreak');
|
||||
@@ -695,12 +700,10 @@ function saveModelList(data) {
|
||||
$('#model_openrouter_select').empty();
|
||||
$('#model_openrouter_select').append($('<option>', { value: openrouter_website_model, text: 'Use OpenRouter website setting' }));
|
||||
model_list.forEach((model) => {
|
||||
const selected = model.id == oai_settings.openrouter_model;
|
||||
$('#model_openrouter_select').append(
|
||||
$('<option>', {
|
||||
value: model.id,
|
||||
text: model.id,
|
||||
selected: selected,
|
||||
}));
|
||||
});
|
||||
$('#model_openrouter_select').val(oai_settings.openrouter_model).trigger('change');
|
||||
@@ -709,19 +712,16 @@ function saveModelList(data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.OPENAI) {
|
||||
$('#openai_external_category').empty();
|
||||
model_list.forEach((model) => {
|
||||
const selected = model.id == oai_settings.openai_model && oai_settings.show_external_models;
|
||||
$('#openai_external_category').append(
|
||||
$('<option>', {
|
||||
value: model.id,
|
||||
text: model.id,
|
||||
selected: selected,
|
||||
}));
|
||||
});
|
||||
// If the selected model is not in the list, revert to default
|
||||
if (oai_settings.show_external_models && model_list.find((model) => model.id == oai_settings.openai_model)) {
|
||||
$('#model_openai_select').val(oai_settings.openai_model).trigger('change');
|
||||
} else {
|
||||
$('#model_openai_select').val(default_settings.openai_model).trigger('change');
|
||||
if (oai_settings.show_external_models) {
|
||||
const model = model_list.findIndex((model) => model.id == oai_settings.openai_model) !== -1 ? oai_settings.openai_model : default_settings.openai_model;
|
||||
$('#model_openai_select').val(model).trigger('change');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -771,11 +771,13 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI].includes(oai_settings.chat_completion_source)) {
|
||||
validateReverseProxy();
|
||||
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
|
||||
generate_data['proxy_password'] = oai_settings.proxy_password;
|
||||
}
|
||||
|
||||
if (isClaude) {
|
||||
generate_data['use_claude'] = true;
|
||||
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
|
||||
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
|
||||
}
|
||||
|
||||
if (isOpenRouter) {
|
||||
@@ -1109,6 +1111,8 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
|
||||
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
|
||||
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
|
||||
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
|
||||
|
||||
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
|
||||
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
|
||||
@@ -1120,6 +1124,8 @@ function loadOpenAISettings(data, settings) {
|
||||
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
$('#api_url_scale').val(oai_settings.api_url_scale);
|
||||
$('#openai_proxy_password').val(oai_settings.proxy_password);
|
||||
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
|
||||
|
||||
$('#model_openai_select').val(oai_settings.openai_model);
|
||||
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
|
||||
@@ -1173,9 +1179,7 @@ function loadOpenAISettings(data, settings) {
|
||||
if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy;
|
||||
$('#openai_reverse_proxy').val(oai_settings.reverse_proxy);
|
||||
|
||||
if (oai_settings.reverse_proxy !== '') {
|
||||
$("#ReverseProxyWarningMessage").css('display', 'block');
|
||||
}
|
||||
$(".reverse_proxy_warning").toggle(oai_settings.reverse_proxy !== '');
|
||||
|
||||
$('#openai_logit_bias_preset').empty();
|
||||
for (const preset of Object.keys(oai_settings.bias_presets)) {
|
||||
@@ -1216,6 +1220,7 @@ async function getStatusOpen() {
|
||||
|
||||
let data = {
|
||||
reverse_proxy: oai_settings.reverse_proxy,
|
||||
proxy_password: oai_settings.proxy_password,
|
||||
use_openrouter: oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER,
|
||||
};
|
||||
|
||||
@@ -1315,6 +1320,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
impersonation_prompt: settings.impersonation_prompt,
|
||||
bias_preset_selected: settings.bias_preset_selected,
|
||||
reverse_proxy: settings.reverse_proxy,
|
||||
proxy_password: settings.proxy_password,
|
||||
legacy_streaming: settings.legacy_streaming,
|
||||
max_context_unlocked: settings.max_context_unlocked,
|
||||
nsfw_avoidance_prompt: settings.nsfw_avoidance_prompt,
|
||||
@@ -1322,6 +1328,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
stream_openai: settings.stream_openai,
|
||||
api_url_scale: settings.api_url_scale,
|
||||
show_external_models: settings.show_external_models,
|
||||
assistant_prefill: settings.assistant_prefill,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -1354,26 +1361,6 @@ async function saveOpenAIPreset(name, settings) {
|
||||
}
|
||||
}
|
||||
|
||||
async function showApiKeyUsage() {
|
||||
try {
|
||||
const response = await fetch('/openai_usage', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const text = `<h3>Total usage this month: $${Number(data.total_usage / 100).toFixed(2)}</h3>
|
||||
<a href="https://platform.openai.com/account/usage" target="_blank">Learn more (OpenAI platform website)</a>`;
|
||||
callPopup(text, 'text');
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
toastr.error('Invalid API key');
|
||||
}
|
||||
}
|
||||
|
||||
function onLogitBiasPresetChange() {
|
||||
const value = $('#openai_logit_bias_preset').find(':selected').val();
|
||||
const preset = oai_settings.bias_presets[value];
|
||||
@@ -1674,6 +1661,8 @@ function onSettingsPresetChange() {
|
||||
stream_openai: ['#stream_toggle', 'stream_openai', true],
|
||||
api_url_scale: ['#api_url_scale', 'api_url_scale', false],
|
||||
show_external_models: ['#openai_show_external_models', 'show_external_models', true],
|
||||
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
|
||||
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
|
||||
};
|
||||
|
||||
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
|
||||
@@ -1882,9 +1871,7 @@ async function onNewPresetClick() {
|
||||
|
||||
function onReverseProxyInput() {
|
||||
oai_settings.reverse_proxy = $(this).val();
|
||||
if (oai_settings.reverse_proxy == '') {
|
||||
$("#ReverseProxyWarningMessage").css('display', 'none');
|
||||
} else { $("#ReverseProxyWarningMessage").css('display', 'block'); }
|
||||
$(".reverse_proxy_warning").toggle(oai_settings.reverse_proxy != '');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
@@ -1936,7 +1923,7 @@ async function onConnectButtonClick(e) {
|
||||
await writeSecret(SECRET_KEYS.CLAUDE, api_key_claude);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.CLAUDE]) {
|
||||
if (!secret_state[SECRET_KEYS.CLAUDE] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for Claude');
|
||||
return;
|
||||
}
|
||||
@@ -1949,7 +1936,7 @@ async function onConnectButtonClick(e) {
|
||||
await writeSecret(SECRET_KEYS.OPENAI, api_key_openai);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.OPENAI]) {
|
||||
if (!secret_state[SECRET_KEYS.OPENAI] && !oai_settings.reverse_proxy) {
|
||||
console.log('No secret key saved for OpenAI');
|
||||
return;
|
||||
}
|
||||
@@ -2221,6 +2208,16 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#openai_proxy_password').on('input', function () {
|
||||
oai_settings.proxy_password = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#claude_assistant_prefill').on('input', function () {
|
||||
oai_settings.assistant_prefill = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#api_button_openai").on("click", onConnectButtonClick);
|
||||
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
|
||||
$("#model_openai_select").on("change", onModelChange);
|
||||
@@ -2231,7 +2228,6 @@ $(document).ready(function () {
|
||||
$("#settings_perset_openai").on("change", onSettingsPresetChange);
|
||||
$("#new_oai_preset").on("click", onNewPresetClick);
|
||||
$("#delete_oai_preset").on("click", onDeletePresetClick);
|
||||
$("#openai_api_usage").on("click", showApiKeyUsage);
|
||||
$("#openai_logit_bias_preset").on("change", onLogitBiasPresetChange);
|
||||
$("#openai_logit_bias_new_preset").on("click", createNewLogitBiasPreset);
|
||||
$("#openai_logit_bias_new_entry").on("click", createNewLogitBiasEntry);
|
||||
|
@@ -43,7 +43,7 @@ export {
|
||||
send_on_enter_options,
|
||||
};
|
||||
|
||||
const MAX_CONTEXT_DEFAULT = 2048;
|
||||
export const MAX_CONTEXT_DEFAULT = 4096;
|
||||
const MAX_CONTEXT_UNLOCKED = 65536;
|
||||
|
||||
const avatar_styles = {
|
||||
@@ -186,6 +186,7 @@ let power_user = {
|
||||
persona_description_position: persona_description_positions.BEFORE_CHAR,
|
||||
|
||||
custom_stopping_strings: '',
|
||||
fuzzy_search: false,
|
||||
};
|
||||
|
||||
let themes = [];
|
||||
@@ -255,7 +256,7 @@ function fixMarkdown(text) {
|
||||
// i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n"
|
||||
|
||||
// Find pairs of formatting characters and capture the text in between them
|
||||
const format = /(\*|_|~){1,2}([\s\S]*?)\1{1,2}/gm;
|
||||
const format = /([\*_]{1,2})([\s\S]*?)\1/gm;
|
||||
let matches = [];
|
||||
let match;
|
||||
while ((match = format.exec(text)) !== null) {
|
||||
@@ -266,7 +267,7 @@ function fixMarkdown(text) {
|
||||
let newText = text;
|
||||
for (let i = matches.length - 1; i >= 0; i--) {
|
||||
let matchText = matches[i][0];
|
||||
let replacementText = matchText.replace(/(\*|_|~)(\s+)|(\s+)(\*|_|~)/g, '$1$4');
|
||||
let replacementText = matchText.replace(/(\*|_)([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)|([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)(\*|_)/g, '$1$4');
|
||||
newText = newText.slice(0, matches[i].index) + replacementText + newText.slice(matches[i].index + matchText.length);
|
||||
}
|
||||
|
||||
@@ -534,6 +535,11 @@ async function applyTheme(name) {
|
||||
{
|
||||
key: 'chat_width',
|
||||
action: async () => {
|
||||
// If chat width is not set, set it to 50
|
||||
if (!power_user.chat_width) {
|
||||
power_user.chat_width = 50;
|
||||
}
|
||||
|
||||
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
|
||||
applyChatWidth();
|
||||
}
|
||||
@@ -660,6 +666,10 @@ function loadPowerUserSettings(settings, data) {
|
||||
power_user.waifuMode = false;
|
||||
}
|
||||
|
||||
if (power_user.chat_width === '') {
|
||||
power_user.chat_width = 50;
|
||||
}
|
||||
|
||||
$('#trim_spaces').prop("checked", power_user.trim_spaces);
|
||||
$('#continue_on_send').prop("checked", power_user.continue_on_send);
|
||||
$('#auto_swipe').prop("checked", power_user.auto_swipe);
|
||||
@@ -667,6 +677,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
|
||||
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
|
||||
$('#custom_stopping_strings').val(power_user.custom_stopping_strings);
|
||||
$('#fuzzy_search_checkbox').prop("checked", power_user.fuzzy_search);
|
||||
|
||||
$("#console_log_prompts").prop("checked", power_user.console_log_prompts);
|
||||
$('#auto_fix_generated_markdown').prop("checked", power_user.auto_fix_generated_markdown);
|
||||
@@ -887,6 +898,31 @@ function loadInstructMode() {
|
||||
});
|
||||
}
|
||||
|
||||
export function fuzzySearchCharacters(searchValue) {
|
||||
const fuse = new Fuse(characters, {
|
||||
keys: [
|
||||
{ name: 'data.name', weight: 5 },
|
||||
{ name: 'data.description', weight: 3 },
|
||||
{ name: 'data.mes_example', weight: 3 },
|
||||
{ name: 'data.scenario', weight: 2 },
|
||||
{ name: 'data.personality', weight: 2 },
|
||||
{ name: 'data.first_mes', weight: 2 },
|
||||
{ name: 'data.creator_notes', weight: 2 },
|
||||
{ name: 'data.creator', weight: 1 },
|
||||
{ name: 'data.tags', weight: 1 },
|
||||
{ name: 'data.alternate_greetings', weight: 1 }
|
||||
],
|
||||
includeScore: true,
|
||||
ignoreLocation: true,
|
||||
threshold: 0.2,
|
||||
});
|
||||
|
||||
const results = fuse.search(searchValue);
|
||||
console.debug('Fuzzy search results for ' + searchValue, results)
|
||||
const indices = results.map(x => x.refIndex);
|
||||
return indices;
|
||||
}
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
@@ -1151,10 +1187,10 @@ async function resetMovablePanels(type) {
|
||||
//if happening as part of preset application, do it quietly.
|
||||
if (type === 'quiet') {
|
||||
return
|
||||
//if happening due to resize, tell user.
|
||||
//if happening due to resize, tell user.
|
||||
} else if (type === 'resize') {
|
||||
toastr.warning('Panel positions reset due to zoom/resize');
|
||||
//if happening due to manual button press
|
||||
//if happening due to manual button press
|
||||
} else {
|
||||
toastr.success('Panel positions reset');
|
||||
}
|
||||
@@ -1173,7 +1209,7 @@ function doNewChat() {
|
||||
|
||||
function doRandomChat() {
|
||||
resetSelectedGroup();
|
||||
setCharacterId(Math.floor(Math.random() * characters.length));
|
||||
setCharacterId(Math.floor(Math.random() * characters.length).toString());
|
||||
setTimeout(() => {
|
||||
reloadCurrentChat();
|
||||
}, 1);
|
||||
@@ -1188,12 +1224,6 @@ async function doMesCut(_, text) {
|
||||
return
|
||||
}
|
||||
|
||||
//reject attempts to delete firstmes
|
||||
if (text === '0') {
|
||||
toastr.error('Cannot delete the First Message')
|
||||
return
|
||||
}
|
||||
|
||||
let mesIDToCut = Number(text).toFixed(0)
|
||||
let mesToCut = $("#chat").find(`.mes[mesid=${mesIDToCut}]`)
|
||||
|
||||
@@ -1963,6 +1993,11 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#fuzzy_search_checkbox').on('input', function () {
|
||||
power_user.fuzzy_search = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(window).on('focus', function () {
|
||||
browser_has_focus = true;
|
||||
});
|
||||
|
351
public/scripts/preset-manager.js
Normal file
351
public/scripts/preset-manager.js
Normal file
@@ -0,0 +1,351 @@
|
||||
import {
|
||||
amount_gen,
|
||||
callPopup,
|
||||
characters,
|
||||
eventSource,
|
||||
event_types,
|
||||
getRequestHeaders,
|
||||
koboldai_setting_names,
|
||||
koboldai_settings,
|
||||
main_api,
|
||||
max_context,
|
||||
nai_settings,
|
||||
novelai_setting_names,
|
||||
novelai_settings,
|
||||
saveSettingsDebounced,
|
||||
this_chid,
|
||||
} from "../script.js";
|
||||
import { groups, selected_group } from "./group-chats.js";
|
||||
import { kai_settings } from "./kai-settings.js";
|
||||
import {
|
||||
textgenerationwebui_preset_names,
|
||||
textgenerationwebui_presets,
|
||||
textgenerationwebui_settings,
|
||||
} from "./textgen-settings.js";
|
||||
import { download, parseJsonFile, waitUntilCondition } from "./utils.js";
|
||||
|
||||
const presetManagers = {};
|
||||
|
||||
function autoSelectPreset() {
|
||||
const presetManager = getPresetManager();
|
||||
|
||||
if (!presetManager) {
|
||||
console.debug(`Preset Manager not found for API: ${main_api}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const name = selected_group ? groups.find(x => x.id == selected_group)?.name : characters[this_chid]?.name;
|
||||
|
||||
if (!name) {
|
||||
console.debug(`Preset candidate not found for API: ${main_api}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = presetManager.findPreset(name);
|
||||
const selectedPreset = presetManager.getSelectedPreset();
|
||||
|
||||
if (preset === selectedPreset) {
|
||||
console.debug(`Preset already selected for API: ${main_api}, name: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (preset !== undefined && preset !== null) {
|
||||
console.log(`Preset found for API: ${main_api}, name: ${name}`);
|
||||
presetManager.selectPreset(preset);
|
||||
}
|
||||
}
|
||||
|
||||
function getPresetManager() {
|
||||
const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
|
||||
|
||||
if (!Object.keys(presetManagers).includes(apiId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return presetManagers[apiId];
|
||||
}
|
||||
|
||||
function registerPresetManagers() {
|
||||
$('select[data-preset-manager-for]').each((_, e) => {
|
||||
const forData = $(e).data("preset-manager-for");
|
||||
for (const apiId of forData.split(",")) {
|
||||
console.debug(`Registering preset manager for API: ${apiId}`);
|
||||
presetManagers[apiId] = new PresetManager($(e), apiId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class PresetManager {
|
||||
constructor(select, apiId) {
|
||||
this.select = select;
|
||||
this.apiId = apiId;
|
||||
}
|
||||
|
||||
findPreset(name) {
|
||||
return $(this.select).find(`option:contains(${name})`).val();
|
||||
}
|
||||
|
||||
getSelectedPreset() {
|
||||
return $(this.select).find("option:selected").val();
|
||||
}
|
||||
|
||||
getSelectedPresetName() {
|
||||
return $(this.select).find("option:selected").text();
|
||||
}
|
||||
|
||||
selectPreset(preset) {
|
||||
$(this.select).find(`option[value=${preset}]`).prop('selected', true);
|
||||
$(this.select).val(preset).trigger("change");
|
||||
}
|
||||
|
||||
async updatePreset() {
|
||||
const selected = $(this.select).find("option:selected");
|
||||
|
||||
if (selected.val() == 'gui') {
|
||||
toastr.info('Cannot update GUI preset');
|
||||
return;
|
||||
}
|
||||
|
||||
const name = selected.text();
|
||||
await this.savePreset(name);
|
||||
toastr.success('Preset updated');
|
||||
}
|
||||
|
||||
async savePresetAs() {
|
||||
const popupText = `
|
||||
<h3>Preset name:</h3>
|
||||
<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;
|
||||
const name = await callPopup(popupText, "input");
|
||||
await this.savePreset(name);
|
||||
toastr.success('Preset saved');
|
||||
}
|
||||
|
||||
async savePreset(name, settings) {
|
||||
const preset = settings ?? this.getPresetSettings();
|
||||
const res = await fetch(`/save_preset`, {
|
||||
method: "POST",
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ preset, name, apiId: this.apiId })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
toastr.error('Failed to save preset');
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
name = data.name;
|
||||
|
||||
this.updateList(name, preset);
|
||||
}
|
||||
|
||||
getPresetList() {
|
||||
let presets = [];
|
||||
let preset_names = {};
|
||||
|
||||
switch (this.apiId) {
|
||||
case "koboldhorde":
|
||||
case "kobold":
|
||||
presets = koboldai_settings;
|
||||
preset_names = koboldai_setting_names;
|
||||
break;
|
||||
case "novel":
|
||||
presets = novelai_settings;
|
||||
preset_names = novelai_setting_names;
|
||||
break;
|
||||
case "textgenerationwebui":
|
||||
presets = textgenerationwebui_presets;
|
||||
preset_names = textgenerationwebui_preset_names;
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${this.apiId}`);
|
||||
}
|
||||
|
||||
return { presets, preset_names };
|
||||
}
|
||||
|
||||
updateList(name, preset) {
|
||||
const { presets, preset_names } = this.getPresetList();
|
||||
const presetExists = this.apiId == "textgenerationwebui" ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
|
||||
|
||||
if (presetExists) {
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
presets[preset_names.indexOf(name)] = preset;
|
||||
$(this.select).find(`option[value="${name}"]`).prop('selected', true);
|
||||
$(this.select).val(name).trigger("change");
|
||||
}
|
||||
else {
|
||||
const value = preset_names[name];
|
||||
presets[value] = preset;
|
||||
$(this.select).find(`option[value="${value}"]`).prop('selected', true);
|
||||
$(this.select).val(value).trigger("change");
|
||||
}
|
||||
}
|
||||
else {
|
||||
presets.push(preset);
|
||||
const value = presets.length - 1;
|
||||
// ooba is reversed
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
preset_names[value] = name;
|
||||
const option = $('<option></option>', { value: name, text: name, selected: true });
|
||||
$(this.select).append(option);
|
||||
$(this.select).val(name).trigger("change");
|
||||
} else {
|
||||
preset_names[name] = value;
|
||||
const option = $('<option></option>', { value: value, text: name, selected: true });
|
||||
$(this.select).append(option);
|
||||
$(this.select).val(value).trigger("change");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPresetSettings() {
|
||||
function getSettingsByApiId(apiId) {
|
||||
switch (apiId) {
|
||||
case "koboldhorde":
|
||||
case "kobold":
|
||||
return kai_settings;
|
||||
case "novel":
|
||||
return nai_settings;
|
||||
case "textgenerationwebui":
|
||||
return textgenerationwebui_settings;
|
||||
default:
|
||||
console.warn(`Unknown API ID ${apiId}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const filteredKeys = ['preset', 'streaming_url', 'stopping_strings', 'use_stop_sequence'];
|
||||
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
|
||||
|
||||
for (const key of filteredKeys) {
|
||||
if (settings.hasOwnProperty(key)) {
|
||||
delete settings[key];
|
||||
}
|
||||
}
|
||||
|
||||
settings['genamt'] = amount_gen;
|
||||
settings['max_length'] = max_context;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
async deleteCurrentPreset() {
|
||||
const { presets, preset_names } = this.getPresetList();
|
||||
const value = this.getSelectedPreset();
|
||||
const nameToDelete = this.getSelectedPresetName();
|
||||
|
||||
if (value == 'gui') {
|
||||
toastr.info('Cannot delete GUI preset');
|
||||
return;
|
||||
}
|
||||
|
||||
$(this.select).find(`option[value="${value}"]`).remove();
|
||||
|
||||
if (this.apiId == "textgenerationwebui") {
|
||||
preset_names.splice(preset_names.indexOf(value), 1);
|
||||
} else {
|
||||
delete preset_names[nameToDelete];
|
||||
}
|
||||
|
||||
if (Object.keys(preset_names).length) {
|
||||
const nextPresetName = Object.keys(preset_names)[0];
|
||||
const newValue = preset_names[nextPresetName];
|
||||
$(this.select).find(`option[value="${newValue}"]`).attr('selected', true);
|
||||
$(this.select).trigger('change');
|
||||
}
|
||||
|
||||
const response = await fetch('/delete_preset', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ name: nameToDelete, apiId: this.apiId }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
toastr.warning('Preset was not deleted from server');
|
||||
} else {
|
||||
toastr.success('Preset deleted');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
await waitUntilCondition(() => eventSource !== undefined);
|
||||
|
||||
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
|
||||
registerPresetManagers();
|
||||
$(document).on("click", "[data-preset-manager-update]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
|
||||
if (!presetManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
await presetManager.updatePreset();
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-new]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
|
||||
if (!presetManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
await presetManager.savePresetAs();
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-export]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
|
||||
if (!presetManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = $(presetManager.select).find("option:selected");
|
||||
const name = selected.text();
|
||||
const preset = presetManager.getPresetSettings();
|
||||
const data = JSON.stringify(preset, null, 4);
|
||||
download(data, `${name}.json`, "application/json");
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-import]", async function () {
|
||||
$('[data-preset-manager-file]').trigger('click');
|
||||
});
|
||||
|
||||
$(document).on("change", "[data-preset-manager-file]", async function (e) {
|
||||
const presetManager = getPresetManager();
|
||||
|
||||
if (!presetManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = file.name.replace('.json', '').replace('.settings', '');
|
||||
const data = await parseJsonFile(file);
|
||||
|
||||
await presetManager.savePreset(name, data);
|
||||
toastr.success('Preset imported');
|
||||
e.target.value = null;
|
||||
});
|
||||
|
||||
$(document).on("click", "[data-preset-manager-delete]", async function () {
|
||||
const presetManager = getPresetManager();
|
||||
|
||||
if (!presetManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = await callPopup('Delete the preset? This action is irreversible and your current settings will be overwritten.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
await presetManager.deleteCurrentPreset();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
})
|
@@ -20,6 +20,7 @@ import {
|
||||
setCharacterId,
|
||||
generateQuietPrompt,
|
||||
reloadCurrentChat,
|
||||
sendMessageAsUser,
|
||||
} from "../script.js";
|
||||
import { humanizedDateTime } from "./RossAscends-mods.js";
|
||||
import { resetSelectedGroup } from "./group-chats.js";
|
||||
@@ -126,11 +127,23 @@ parser.addCommand('continue', continueChatCallback, ['cont'], ' – continues th
|
||||
parser.addCommand('go', goToCharacterCallback, ['char'], '<span class="monospace">(name)</span> – opens up a chat with the character by its name', true, true);
|
||||
parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">(prompt)</span> – generates a system message using a specified prompt', true, true);
|
||||
parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '<span class="monospace">(name)</span> – deletes all messages attributed to a specified name', true, true);
|
||||
parser.addCommand('send', sendUserMessageCallback, ['add'], '<span class="monospace">(text)</span> – adds a user message to the chat log without triggering a generation', true, true);
|
||||
|
||||
const NARRATOR_NAME_KEY = 'narrator_name';
|
||||
const NARRATOR_NAME_DEFAULT = 'System';
|
||||
const COMMENT_NAME_DEFAULT = 'Note';
|
||||
|
||||
async function sendUserMessageCallback(_, text) {
|
||||
if (!text) {
|
||||
console.warn('WARN: No text provided for /send command');
|
||||
return;
|
||||
}
|
||||
|
||||
text = text.trim();
|
||||
const bias = extractMessageBias(text);
|
||||
sendMessageAsUser(text, bias);
|
||||
}
|
||||
|
||||
async function deleteMessagesByNameCallback(_, name) {
|
||||
if (!name) {
|
||||
console.warn('WARN: No name provided for /delname command');
|
||||
@@ -192,7 +205,7 @@ function goToCharacterCallback(_, name) {
|
||||
const characterIndex = findCharacterIndex(name);
|
||||
|
||||
if (characterIndex !== -1) {
|
||||
openChat(characterIndex);
|
||||
openChat(new String(characterIndex));
|
||||
} else {
|
||||
console.warn(`No matches found for name "${name}"`);
|
||||
}
|
||||
|
@@ -256,6 +256,19 @@ async function statMesProcess(line, type, characters, this_chid, oldMesssage) {
|
||||
|
||||
let stat = charStats[characters[this_chid].avatar];
|
||||
|
||||
if (!stat) {
|
||||
stat = {
|
||||
total_gen_time: 0,
|
||||
user_word_count: 0,
|
||||
non_user_msg_count: 0,
|
||||
user_msg_count: 0,
|
||||
non_user_msg_count: 0,
|
||||
total_swipe_count: 0,
|
||||
date_first_chat: Date.now(),
|
||||
date_last_chat: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
stat.total_gen_time += calculateGenTime(
|
||||
line.gen_started,
|
||||
line.gen_finished
|
||||
|
@@ -3,6 +3,7 @@ import {
|
||||
getStoppingStrings,
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
setGenerationParamsFromPreset,
|
||||
} from "../script.js";
|
||||
|
||||
export {
|
||||
@@ -11,7 +12,7 @@ export {
|
||||
generateTextGenWithStreaming,
|
||||
}
|
||||
|
||||
let textgenerationwebui_settings = {
|
||||
const textgenerationwebui_settings = {
|
||||
temp: 0.7,
|
||||
top_p: 0.5,
|
||||
top_k: 40,
|
||||
@@ -21,6 +22,7 @@ let textgenerationwebui_settings = {
|
||||
eta_cutoff: 0,
|
||||
typical_p: 1,
|
||||
rep_pen: 1.2,
|
||||
rep_pen_range: 0,
|
||||
no_repeat_ngram_size: 0,
|
||||
penalty_alpha: 0,
|
||||
num_beams: 1,
|
||||
@@ -43,12 +45,13 @@ let textgenerationwebui_settings = {
|
||||
mirostat_eta: 0.1,
|
||||
};
|
||||
|
||||
let textgenerationwebui_presets = [];
|
||||
let textgenerationwebui_preset_names = [];
|
||||
export let textgenerationwebui_presets = [];
|
||||
export let textgenerationwebui_preset_names = [];
|
||||
|
||||
const setting_names = [
|
||||
"temp",
|
||||
"rep_pen",
|
||||
"rep_pen_range",
|
||||
"no_repeat_ngram_size",
|
||||
"top_k",
|
||||
"top_p",
|
||||
@@ -87,6 +90,7 @@ function selectPreset(name) {
|
||||
const value = preset[name];
|
||||
setSettingByName(name, value, true);
|
||||
}
|
||||
setGenerationParamsFromPreset(preset);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
@@ -212,6 +216,7 @@ export function getTextGenGenerationData(finalPromt, this_amount_gen, isImperson
|
||||
'top_p': textgenerationwebui_settings.top_p,
|
||||
'typical_p': textgenerationwebui_settings.typical_p,
|
||||
'repetition_penalty': textgenerationwebui_settings.rep_pen,
|
||||
'repetition_penalty_range': textgenerationwebui_settings.rep_pen_range,
|
||||
'encoder_repetition_penalty': textgenerationwebui_settings.encoder_rep_pen,
|
||||
'top_k': textgenerationwebui_settings.top_k,
|
||||
'min_length': textgenerationwebui_settings.min_length,
|
||||
|
@@ -470,6 +470,20 @@ export function getCharaFilename(chid) {
|
||||
}
|
||||
}
|
||||
|
||||
export function extractAllWords(value) {
|
||||
const words = [];
|
||||
|
||||
if (!value) {
|
||||
return words;
|
||||
}
|
||||
|
||||
const matches = value.matchAll(/\b\w+\b/gim);
|
||||
for (let match of matches) {
|
||||
words.push(match[0].toLowerCase());
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
export function escapeRegex(string) {
|
||||
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ export {
|
||||
world_info_budget,
|
||||
world_info_depth,
|
||||
world_info_recursive,
|
||||
world_info_overflow_alert,
|
||||
world_info_case_sensitive,
|
||||
world_info_match_whole_words,
|
||||
world_info_character_strategy,
|
||||
@@ -32,6 +33,7 @@ let world_names;
|
||||
let world_info_depth = 2;
|
||||
let world_info_budget = 25;
|
||||
let world_info_recursive = false;
|
||||
let world_info_overflow_alert = false;
|
||||
let world_info_case_sensitive = false;
|
||||
let world_info_match_whole_words = false;
|
||||
let world_info_character_strategy = world_info_insertion_strategy.character_first;
|
||||
@@ -70,6 +72,8 @@ function setWorldInfoSettings(settings, data) {
|
||||
world_info_budget = Number(settings.world_info_budget);
|
||||
if (settings.world_info_recursive !== undefined)
|
||||
world_info_recursive = Boolean(settings.world_info_recursive);
|
||||
if (settings.world_info_overflow_alert !== undefined)
|
||||
world_info_overflow_alert = Boolean(settings.world_info_overflow_alert);
|
||||
if (settings.world_info_case_sensitive !== undefined)
|
||||
world_info_case_sensitive = Boolean(settings.world_info_case_sensitive);
|
||||
if (settings.world_info_match_whole_words !== undefined)
|
||||
@@ -102,6 +106,7 @@ function setWorldInfoSettings(settings, data) {
|
||||
$("#world_info_budget").val(world_info_budget);
|
||||
|
||||
$("#world_info_recursive").prop('checked', world_info_recursive);
|
||||
$("#world_info_overflow_alert").prop('checked', world_info_overflow_alert);
|
||||
$("#world_info_case_sensitive").prop('checked', world_info_case_sensitive);
|
||||
$("#world_info_match_whole_words").prop('checked', world_info_match_whole_words);
|
||||
|
||||
@@ -970,7 +975,7 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
if (selectiveLogic === 0) {
|
||||
console.debug('saw AND logic, checking..')
|
||||
if (secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim())) {
|
||||
console.log(`activating entry ${entry.uid} with AND found`)
|
||||
console.debug(`activating entry ${entry.uid} with AND found`)
|
||||
activatedNow.add(entry);
|
||||
break secondary;
|
||||
}
|
||||
@@ -1020,6 +1025,10 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
|
||||
if (textToScanTokens + getTokenCount(newContent) >= budget) {
|
||||
console.debug(`WI budget reached, stopping`);
|
||||
if (world_info_overflow_alert) {
|
||||
console.log("Alerting");
|
||||
toastr.warning(`World info budget reached after ${count} entries.`, 'World Info');
|
||||
}
|
||||
needsToScan = false;
|
||||
break;
|
||||
}
|
||||
@@ -1250,7 +1259,7 @@ export function checkEmbeddedWorld(chid) {
|
||||
const worldName = characters[chid]?.data?.extensions?.world;
|
||||
if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
|
||||
toastr.info(
|
||||
'To import and use it, select "Import Embedded World Info" in the Options dropdown menu on the character panel.',
|
||||
'To import and use it, select "Import Card Lore" in the "More..." dropdown menu on the character panel.',
|
||||
`${characters[chid].name} has an embedded World/Lorebook`,
|
||||
{ timeOut: 10000, extendedTimeOut: 20000, positionClass: 'toast-top-center' },
|
||||
);
|
||||
@@ -1501,6 +1510,11 @@ jQuery(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#world_info_overflow_alert').on('change', function () {
|
||||
world_info_overflow_alert = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#world_button').on('click', async function () {
|
||||
const chid = $('#set_character_world').data('chid');
|
||||
|
||||
|
@@ -609,7 +609,7 @@ hr {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
#extensionsMenu>div,
|
||||
@@ -1293,7 +1293,7 @@ body.charListGrid #rm_print_characters_block .tags_inline {
|
||||
|
||||
}
|
||||
|
||||
.floating_prompt_radio_group {
|
||||
.floating_prompt_radio_group, .radio_group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -1512,8 +1512,20 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.character_name_block {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-direction: row;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.ch_avatar_url {
|
||||
float: right;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
body.charListGrid #rm_print_characters_block .character_version,
|
||||
body.charListGrid #rm_print_characters_block .ch_avatar_url {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.character_select .avatar {
|
||||
@@ -1547,7 +1559,7 @@ body.big-avatars .ch_description {
|
||||
|
||||
/*applies to both groups and solos chars in the char list*/
|
||||
#rm_print_characters_block .ch_name {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
/* max-width: calc(100% - 29px); */
|
||||
overflow: hidden;
|
||||
@@ -4243,6 +4255,10 @@ toolcool-color-picker {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
@@ -4500,14 +4516,16 @@ toolcool-color-picker {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.openai_preset_buttons {
|
||||
.openai_preset_buttons,
|
||||
.preset_buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.openai_preset_buttons select {
|
||||
.openai_preset_buttons select,
|
||||
.preset_buttons select {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -4849,6 +4867,10 @@ body.waifuMode .zoomed_avatar {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.flexGap10 {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
font-size: calc(var(--mainFontSize) * 0.7);
|
||||
font-weight: 400;
|
||||
@@ -5422,4 +5444,4 @@ body.waifuMode .zoomed_avatar {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
|
163
server.js
163
server.js
@@ -290,6 +290,7 @@ const directories = {
|
||||
instruct: 'public/instruct',
|
||||
context: 'public/context',
|
||||
backups: 'backups/',
|
||||
quickreplies: 'public/QuickReplies'
|
||||
};
|
||||
|
||||
// CSRF Protection //
|
||||
@@ -1108,7 +1109,7 @@ app.post("/editcharacterattribute", jsonParser, async function (request, respons
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/deletecharacter", urlencodedParser, async function (request, response) {
|
||||
app.post("/deletecharacter", jsonParser, async function (request, response) {
|
||||
if (!request.body || !request.body.avatar_url) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
@@ -1224,6 +1225,11 @@ const calculateChatSize = (charDir) => {
|
||||
return { chatSize, dateLastChat };
|
||||
}
|
||||
|
||||
// Calculate the total string length of the data object
|
||||
const calculateDataSize = (data) => {
|
||||
return typeof data === 'object' ? Object.values(data).reduce((acc, val) => acc + new String(val).length, 0) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* processCharacter - Process a given character, read its data and calculate its statistics.
|
||||
*
|
||||
@@ -1245,6 +1251,7 @@ const processCharacter = async (item, i) => {
|
||||
const { chatSize, dateLastChat } = calculateChatSize(char_dir);
|
||||
characters[i]['chat_size'] = chatSize;
|
||||
characters[i]['date_last_chat'] = dateLastChat;
|
||||
characters[i]['data_size'] = calculateDataSize(jsonObject?.data);
|
||||
}
|
||||
catch (err) {
|
||||
characters[i] = {
|
||||
@@ -1594,6 +1601,8 @@ app.post('/getsettings', jsonParser, (request, response) => {
|
||||
|
||||
const themes = readAndParseFromDirectory(directories.themes);
|
||||
const movingUIPresets = readAndParseFromDirectory(directories.movingUI);
|
||||
const quickReplyPresets = readAndParseFromDirectory(directories.quickreplies);
|
||||
|
||||
const instruct = readAndParseFromDirectory(directories.instruct);
|
||||
const context = readAndParseFromDirectory(directories.context);
|
||||
|
||||
@@ -1610,6 +1619,7 @@ app.post('/getsettings', jsonParser, (request, response) => {
|
||||
textgenerationwebui_preset_names,
|
||||
themes,
|
||||
movingUIPresets,
|
||||
quickReplyPresets,
|
||||
instruct,
|
||||
context,
|
||||
enable_extensions: enableExtensions,
|
||||
@@ -1666,6 +1676,17 @@ app.post('/savemovingui', jsonParser, (request, response) => {
|
||||
return response.sendStatus(200);
|
||||
});
|
||||
|
||||
app.post('/savequickreply', jsonParser, (request, response) => {
|
||||
if (!request.body || !request.body.name) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = path.join(directories.quickreplies, sanitize(request.body.name) + '.json');
|
||||
fs.writeFileSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
|
||||
|
||||
return response.sendStatus(200);
|
||||
});
|
||||
|
||||
function convertWorldInfoToCharacterBook(name, entries) {
|
||||
const result = { entries: [], name };
|
||||
|
||||
@@ -1777,10 +1798,10 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
console.log(request.body);
|
||||
const bw = require('./src/bad-words');
|
||||
const bad_words_ids = request.body.model.includes('clio') ? bw.clioBadWordsId : bw.badWordIds;
|
||||
|
||||
const novelai = require('./src/novelai');
|
||||
const isNewModel = (request.body.model.includes('clio') || request.body.model.includes('kayra'));
|
||||
const isKrake = request.body.model.includes('krake');
|
||||
const data = {
|
||||
"input": request.body.input,
|
||||
"model": request.body.model,
|
||||
@@ -1795,12 +1816,17 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
||||
"repetition_penalty_slope": request.body.repetition_penalty_slope,
|
||||
"repetition_penalty_frequency": request.body.repetition_penalty_frequency,
|
||||
"repetition_penalty_presence": request.body.repetition_penalty_presence,
|
||||
"repetition_penalty_whitelist": isNewModel ? novelai.repPenaltyAllowList : null,
|
||||
"top_a": request.body.top_a,
|
||||
"top_p": request.body.top_p,
|
||||
"top_k": request.body.top_k,
|
||||
"typical_p": request.body.typical_p,
|
||||
"cfg_scale": request.body.cfg_scale,
|
||||
"cfg_uc": request.body.cfg_uc,
|
||||
"phrase_rep_pen": request.body.phrase_rep_pen,
|
||||
//"stop_sequences": {{187}},
|
||||
"bad_words_ids": bad_words_ids,
|
||||
"bad_words_ids": isNewModel ? novelai.badWordsList : (isKrake ? novelai.krakeBadWordsList : novelai.euterpeBadWordsList),
|
||||
"logit_bias_exp": isNewModel ? novelai.logitBiasExp : null,
|
||||
//generate_until_sentence = true;
|
||||
"use_cache": request.body.use_cache,
|
||||
"use_string": true,
|
||||
@@ -1809,6 +1835,8 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
||||
"order": request.body.order
|
||||
}
|
||||
};
|
||||
const util = require('util');
|
||||
console.log(util.inspect(data, { depth: 4 }))
|
||||
|
||||
const args = {
|
||||
body: JSON.stringify(data),
|
||||
@@ -2849,7 +2877,7 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
|
||||
|
||||
if (request.body.use_openrouter == false) {
|
||||
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
|
||||
headers = {};
|
||||
} else {
|
||||
api_url = 'https://openrouter.ai/api/v1';
|
||||
@@ -2858,7 +2886,7 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
}
|
||||
|
||||
if (!api_key_openai) {
|
||||
if (!api_key_openai && !request.body.reverse_proxy) {
|
||||
return response_getstatus_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
@@ -2927,42 +2955,6 @@ app.post("/openai_bias", jsonParser, async function (request, response) {
|
||||
return response.send(result);
|
||||
});
|
||||
|
||||
// Shamelessly stolen from Agnai
|
||||
app.post("/openai_usage", jsonParser, async function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
const key = readSecret(SECRET_KEYS.OPENAI);
|
||||
|
||||
if (!key) {
|
||||
console.warn('Get key usage failed: Missing OpenAI API key.');
|
||||
return response.sendStatus(401);
|
||||
}
|
||||
|
||||
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${key}`,
|
||||
};
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(1);
|
||||
const start_date = date.toISOString().slice(0, 10);
|
||||
|
||||
date.setMonth(date.getMonth() + 1);
|
||||
const end_date = date.toISOString().slice(0, 10);
|
||||
|
||||
try {
|
||||
const res = await getAsync(
|
||||
`${api_url}/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`,
|
||||
{ headers },
|
||||
);
|
||||
return response.send(res);
|
||||
}
|
||||
catch {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/deletepreset_openai", jsonParser, function (request, response) {
|
||||
if (!request.body || !request.body.name) {
|
||||
return response.sendStatus(400);
|
||||
@@ -3091,7 +3083,7 @@ async function sendClaudeRequest(request, response) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const api_url = new URL(request.body.reverse_proxy || api_claude).toString();
|
||||
const api_key_claude = readSecret(SECRET_KEYS.CLAUDE);
|
||||
const api_key_claude = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.CLAUDE);
|
||||
|
||||
if (!api_key_claude) {
|
||||
return response.status(401).send({ error: true });
|
||||
@@ -3104,7 +3096,12 @@ async function sendClaudeRequest(request, response) {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const requestPrompt = convertClaudePrompt(request.body.messages, true, true);
|
||||
let requestPrompt = convertClaudePrompt(request.body.messages, true, true);
|
||||
|
||||
if (request.body.assistant_prefill) {
|
||||
requestPrompt += request.body.assistant_prefill;
|
||||
}
|
||||
|
||||
console.log('Claude request:', requestPrompt);
|
||||
|
||||
const generateResponse = await fetch(api_url + '/complete', {
|
||||
@@ -3180,7 +3177,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
|
||||
if (!request.body.use_openrouter) {
|
||||
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
|
||||
headers = {};
|
||||
} else {
|
||||
api_url = 'https://openrouter.ai/api/v1';
|
||||
@@ -3189,7 +3186,7 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
}
|
||||
|
||||
if (!api_key_openai) {
|
||||
if (!api_key_openai && !request.body.reverse_proxy) {
|
||||
return response_generate_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
@@ -3287,6 +3284,10 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
message = 'API key disabled or exhausted';
|
||||
console.log(message);
|
||||
break;
|
||||
case 451:
|
||||
message = error?.response?.data?.error?.message || 'Unavailable for legal reasons';
|
||||
console.log(message);
|
||||
break;
|
||||
}
|
||||
|
||||
const quota_error = error?.response?.status === 429 && error?.response?.data?.error?.type === 'insufficient_quota';
|
||||
@@ -3339,6 +3340,47 @@ app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_op
|
||||
response_tokenize_openai.send({ "token_count": num_tokens });
|
||||
});
|
||||
|
||||
app.post("/save_preset", jsonParser, function (request, response) {
|
||||
const name = sanitize(request.body.name);
|
||||
if (!request.body.preset || !name) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const directory = getPresetFolderByApiId(request.body.apiId);
|
||||
|
||||
if (!directory) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const fullpath = path.join(directory, filename);
|
||||
fs.writeFileSync(fullpath, JSON.stringify(request.body.preset, null, 4), 'utf-8');
|
||||
return response.send({ name });
|
||||
});
|
||||
|
||||
app.post("/delete_preset", jsonParser, function (request, response) {
|
||||
const name = sanitize(request.body.name);
|
||||
if (!name) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const directory = getPresetFolderByApiId(request.body.apiId);
|
||||
|
||||
if (!directory) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const fullpath = path.join(directory, filename);
|
||||
|
||||
if (fs.existsSync) {
|
||||
fs.unlinkSync(fullpath);
|
||||
return response.sendStatus(200);
|
||||
} else {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/savepreset_openai", jsonParser, function (request, response) {
|
||||
const name = sanitize(request.query.name);
|
||||
if (!request.body || !name) {
|
||||
@@ -3351,6 +3393,20 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
|
||||
return response.send({ name });
|
||||
});
|
||||
|
||||
function getPresetFolderByApiId(apiId) {
|
||||
switch (apiId) {
|
||||
case 'kobold':
|
||||
case 'koboldhorde':
|
||||
return directories.koboldAI_Settings;
|
||||
case 'novel':
|
||||
return directories.novelAI_Settings;
|
||||
case 'textgenerationwebui':
|
||||
return directories.textGen_Settings;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function createTokenizationHandler(getTokenizerFn) {
|
||||
return async function (request, response) {
|
||||
if (!request.body) {
|
||||
@@ -3391,17 +3447,6 @@ app.post("/tokenize_via_api", jsonParser, async function (request, response) {
|
||||
|
||||
// ** REST CLIENT ASYNC WRAPPERS **
|
||||
|
||||
function putAsync(url, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.put(url, args, (data, response) => {
|
||||
if (response.statusCode >= 400) {
|
||||
reject(data);
|
||||
}
|
||||
resolve(data);
|
||||
}).on('error', e => reject(e));
|
||||
})
|
||||
}
|
||||
|
||||
async function postAsync(url, args) {
|
||||
const fetch = require('node-fetch').default;
|
||||
const response = await fetch(url, { method: 'POST', timeout: 0, ...args });
|
||||
|
326
src/bad-words.js
326
src/bad-words.js
@@ -1,326 +0,0 @@
|
||||
const badWordIds = [
|
||||
[60],
|
||||
[62],
|
||||
[544],
|
||||
[683],
|
||||
[696],
|
||||
[880],
|
||||
[905],
|
||||
[1008],
|
||||
[1019],
|
||||
[1084],
|
||||
[1092],
|
||||
[1181],
|
||||
[1184],
|
||||
[1254],
|
||||
[1447],
|
||||
[1570],
|
||||
[1656],
|
||||
[2194],
|
||||
[2470],
|
||||
[2479],
|
||||
[2498],
|
||||
[2947],
|
||||
[3138],
|
||||
[3291],
|
||||
[3455],
|
||||
[3725],
|
||||
[3851],
|
||||
[3891],
|
||||
[3921],
|
||||
[3951],
|
||||
[4207],
|
||||
[4299],
|
||||
[4622],
|
||||
[4681],
|
||||
[5013],
|
||||
[5032],
|
||||
[5180],
|
||||
[5218],
|
||||
[5290],
|
||||
[5413],
|
||||
[5456],
|
||||
[5709],
|
||||
[5749],
|
||||
[5774],
|
||||
[6038],
|
||||
[6257],
|
||||
[6334],
|
||||
[6660],
|
||||
[6904],
|
||||
[7082],
|
||||
[7086],
|
||||
[7254],
|
||||
[7444],
|
||||
[7748],
|
||||
[8001],
|
||||
[8088],
|
||||
[8168],
|
||||
[8562],
|
||||
[8605],
|
||||
[8795],
|
||||
[8850],
|
||||
[9014],
|
||||
[9102],
|
||||
[9259],
|
||||
[9318],
|
||||
[9336],
|
||||
[9502],
|
||||
[9686],
|
||||
[9793],
|
||||
[9855],
|
||||
[9899],
|
||||
[9955],
|
||||
[10148],
|
||||
[10174],
|
||||
[10943],
|
||||
[11326],
|
||||
[11337],
|
||||
[11661],
|
||||
[12004],
|
||||
[12084],
|
||||
[12159],
|
||||
[12520],
|
||||
[12977],
|
||||
[13380],
|
||||
[13488],
|
||||
[13663],
|
||||
[13811],
|
||||
[13976],
|
||||
[14412],
|
||||
[14598],
|
||||
[14767],
|
||||
[15640],
|
||||
[15707],
|
||||
[15775],
|
||||
[15830],
|
||||
[16079],
|
||||
[16354],
|
||||
[16369],
|
||||
[16445],
|
||||
[16595],
|
||||
[16614],
|
||||
[16731],
|
||||
[16943],
|
||||
[17278],
|
||||
[17281],
|
||||
[17548],
|
||||
[17555],
|
||||
[17981],
|
||||
[18022],
|
||||
[18095],
|
||||
[18297],
|
||||
[18413],
|
||||
[18736],
|
||||
[18772],
|
||||
[18990],
|
||||
[19181],
|
||||
[20095],
|
||||
[20197],
|
||||
[20481],
|
||||
[20629],
|
||||
[20871],
|
||||
[20879],
|
||||
[20924],
|
||||
[20977],
|
||||
[21375],
|
||||
[21382],
|
||||
[21391],
|
||||
[21687],
|
||||
[21810],
|
||||
[21828],
|
||||
[21938],
|
||||
[22367],
|
||||
[22372],
|
||||
[22734],
|
||||
[23405],
|
||||
[23505],
|
||||
[23734],
|
||||
[23741],
|
||||
[23781],
|
||||
[24237],
|
||||
[24254],
|
||||
[24345],
|
||||
[24430],
|
||||
[25416],
|
||||
[25896],
|
||||
[26119],
|
||||
[26635],
|
||||
[26842],
|
||||
[26991],
|
||||
[26997],
|
||||
[27075],
|
||||
[27114],
|
||||
[27468],
|
||||
[27501],
|
||||
[27618],
|
||||
[27655],
|
||||
[27720],
|
||||
[27829],
|
||||
[28052],
|
||||
[28118],
|
||||
[28231],
|
||||
[28532],
|
||||
[28571],
|
||||
[28591],
|
||||
[28653],
|
||||
[29013],
|
||||
[29547],
|
||||
[29650],
|
||||
[29925],
|
||||
[30522],
|
||||
[30537],
|
||||
[30996],
|
||||
[31011],
|
||||
[31053],
|
||||
[31096],
|
||||
[31148],
|
||||
[31258],
|
||||
[31350],
|
||||
[31379],
|
||||
[31422],
|
||||
[31789],
|
||||
[31830],
|
||||
[32214],
|
||||
[32666],
|
||||
[32871],
|
||||
[33094],
|
||||
[33376],
|
||||
[33440],
|
||||
[33805],
|
||||
[34368],
|
||||
[34398],
|
||||
[34417],
|
||||
[34418],
|
||||
[34419],
|
||||
[34476],
|
||||
[34494],
|
||||
[34607],
|
||||
[34758],
|
||||
[34761],
|
||||
[34904],
|
||||
[34993],
|
||||
[35117],
|
||||
[35138],
|
||||
[35237],
|
||||
[35487],
|
||||
[35830],
|
||||
[35869],
|
||||
[36033],
|
||||
[36134],
|
||||
[36320],
|
||||
[36399],
|
||||
[36487],
|
||||
[36586],
|
||||
[36676],
|
||||
[36692],
|
||||
[36786],
|
||||
[37077],
|
||||
[37594],
|
||||
[37596],
|
||||
[37786],
|
||||
[37982],
|
||||
[38475],
|
||||
[38791],
|
||||
[39083],
|
||||
[39258],
|
||||
[39487],
|
||||
[39822],
|
||||
[40116],
|
||||
[40125],
|
||||
[41000],
|
||||
[41018],
|
||||
[41256],
|
||||
[41305],
|
||||
[41361],
|
||||
[41447],
|
||||
[41449],
|
||||
[41512],
|
||||
[41604],
|
||||
[42041],
|
||||
[42274],
|
||||
[42368],
|
||||
[42696],
|
||||
[42767],
|
||||
[42804],
|
||||
[42854],
|
||||
[42944],
|
||||
[42989],
|
||||
[43134],
|
||||
[43144],
|
||||
[43189],
|
||||
[43521],
|
||||
[43782],
|
||||
[44082],
|
||||
[44162],
|
||||
[44270],
|
||||
[44308],
|
||||
[44479],
|
||||
[44524],
|
||||
[44965],
|
||||
[45114],
|
||||
[45301],
|
||||
[45382],
|
||||
[45443],
|
||||
[45472],
|
||||
[45488],
|
||||
[45507],
|
||||
[45564],
|
||||
[45662],
|
||||
[46265],
|
||||
[46267],
|
||||
[46275],
|
||||
[46295],
|
||||
[46462],
|
||||
[46468],
|
||||
[46576],
|
||||
[46694],
|
||||
[47093],
|
||||
[47384],
|
||||
[47389],
|
||||
[47446],
|
||||
[47552],
|
||||
[47686],
|
||||
[47744],
|
||||
[47916],
|
||||
[48064],
|
||||
[48167],
|
||||
[48392],
|
||||
[48471],
|
||||
[48664],
|
||||
[48701],
|
||||
[49021],
|
||||
[49193],
|
||||
[49236],
|
||||
[49550],
|
||||
[49694],
|
||||
[49806],
|
||||
[49824],
|
||||
[50001],
|
||||
[50256],
|
||||
[0],
|
||||
[1],
|
||||
]
|
||||
|
||||
const clioBadWordsId = [
|
||||
[3],
|
||||
[49356],
|
||||
[1431],
|
||||
[31715],
|
||||
[34387],
|
||||
[20765],
|
||||
[30702],
|
||||
[10691],
|
||||
[49333],
|
||||
[1266],
|
||||
[19438],
|
||||
[43145],
|
||||
[26523],
|
||||
[41471],
|
||||
[2936],
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
badWordIds,
|
||||
clioBadWordsId,
|
||||
};
|
75
src/novelai.js
Normal file
75
src/novelai.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Ban bracket generation, plus defaults
|
||||
const euterpeBadWordsList = [
|
||||
[8162], [17202], [8162], [17202], [8162], [17202], [8162], [17202], [8162], [17202], [46256, 224], [2343, 223, 224],
|
||||
[46256, 224], [2343, 223, 224], [46256, 224], [2343, 223, 224], [46256, 224], [2343, 223, 224], [46256, 224],
|
||||
[2343, 223, 224], [58], [60], [90], [92], [685], [1391], [1782], [2361], [3693], [4083], [4357], [4895], [5512],
|
||||
[5974], [7131], [8183], [8351], [8762], [8964], [8973], [9063], [11208], [11709], [11907], [11919], [12878], [12962],
|
||||
[13018], [13412], [14631], [14692], [14980], [15090], [15437], [16151], [16410], [16589], [17241], [17414], [17635],
|
||||
[17816], [17912], [18083], [18161], [18477], [19629], [19779], [19953], [20520], [20598], [20662], [20740], [21476],
|
||||
[21737], [22133], [22241], [22345], [22935], [23330], [23785], [23834], [23884], [25295], [25597], [25719], [25787],
|
||||
[25915], [26076], [26358], [26398], [26894], [26933], [27007], [27422], [28013], [29164], [29225], [29342], [29565],
|
||||
[29795], [30072], [30109], [30138], [30866], [31161], [31478], [32092], [32239], [32509], [33116], [33250], [33761],
|
||||
[34171], [34758], [34949], [35944], [36338], [36463], [36563], [36786], [36796], [36937], [37250], [37913], [37981],
|
||||
[38165], [38362], [38381], [38430], [38892], [39850], [39893], [41832], [41888], [42535], [42669], [42785], [42924],
|
||||
[43839], [44438], [44587], [44926], [45144], [45297], [46110], [46570], [46581], [46956], [47175], [47182], [47527],
|
||||
[47715], [48600], [48683], [48688], [48874], [48999], [49074], [49082], [49146], [49946], [10221], [4841], [1427],
|
||||
[2602, 834], [29343], [37405], [35780], [2602], [50256],
|
||||
]
|
||||
|
||||
// Ban bracket generation, plus defaults
|
||||
const krakeBadWordsList = [
|
||||
[9264], [14244], [9264], [14244], [9264], [14244], [9264], [14244], [9264], [14244], [25086, 213], [27344, 213],
|
||||
[25086, 213], [27344, 213], [25086, 213], [27344, 213], [25086, 213], [27344, 213], [25086, 213], [27344, 213], [60],
|
||||
[62], [544], [683], [696], [880], [905], [1008], [1019], [1084], [1092], [1181], [1184], [1254], [1447], [1570], [1656],
|
||||
[2194], [2470], [2479], [2498], [2947], [3138], [3291], [3455], [3725], [3851], [3891], [3921], [3951], [4207], [4299],
|
||||
[4622], [4681], [5013], [5032], [5180], [5218], [5290], [5413], [5456], [5709], [5749], [5774], [6038], [6257], [6334],
|
||||
[6660], [6904], [7082], [7086], [7254], [7444], [7748], [8001], [8088], [8168], [8562], [8605], [8795], [8850], [9014],
|
||||
[9102], [9259], [9318], [9336], [9502], [9686], [9793], [9855], [9899], [9955], [10148], [10174], [10943], [11326],
|
||||
[11337], [11661], [12004], [12084], [12159], [12520], [12977], [13380], [13488], [13663], [13811], [13976], [14412],
|
||||
[14598], [14767], [15640], [15707], [15775], [15830], [16079], [16354], [16369], [16445], [16595], [16614], [16731],
|
||||
[16943], [17278], [17281], [17548], [17555], [17981], [18022], [18095], [18297], [18413], [18736], [18772], [18990],
|
||||
[19181], [20095], [20197], [20481], [20629], [20871], [20879], [20924], [20977], [21375], [21382], [21391], [21687],
|
||||
[21810], [21828], [21938], [22367], [22372], [22734], [23405], [23505], [23734], [23741], [23781], [24237], [24254],
|
||||
[24345], [24430], [25416], [25896], [26119], [26635], [26842], [26991], [26997], [27075], [27114], [27468], [27501],
|
||||
[27618], [27655], [27720], [27829], [28052], [28118], [28231], [28532], [28571], [28591], [28653], [29013], [29547],
|
||||
[29650], [29925], [30522], [30537], [30996], [31011], [31053], [31096], [31148], [31258], [31350], [31379], [31422],
|
||||
[31789], [31830], [32214], [32666], [32871], [33094], [33376], [33440], [33805], [34368], [34398], [34417], [34418],
|
||||
[34419], [34476], [34494], [34607], [34758], [34761], [34904], [34993], [35117], [35138], [35237], [35487], [35830],
|
||||
[35869], [36033], [36134], [36320], [36399], [36487], [36586], [36676], [36692], [36786], [37077], [37594], [37596],
|
||||
[37786], [37982], [38475], [38791], [39083], [39258], [39487], [39822], [40116], [40125], [41000], [41018], [41256],
|
||||
[41305], [41361], [41447], [41449], [41512], [41604], [42041], [42274], [42368], [42696], [42767], [42804], [42854],
|
||||
[42944], [42989], [43134], [43144], [43189], [43521], [43782], [44082], [44162], [44270], [44308], [44479], [44524],
|
||||
[44965], [45114], [45301], [45382], [45443], [45472], [45488], [45507], [45564], [45662], [46265], [46267], [46275],
|
||||
[46295], [46462], [46468], [46576], [46694], [47093], [47384], [47389], [47446], [47552], [47686], [47744], [47916],
|
||||
[48064], [48167], [48392], [48471], [48664], [48701], [49021], [49193], [49236], [49550], [49694], [49806], [49824],
|
||||
[50001], [50256], [0], [1]
|
||||
]
|
||||
|
||||
// Ban bracket generation, plus defaults
|
||||
const badWordsList = [
|
||||
[23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [21], [49209, 21],
|
||||
[21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [3], [49356], [1431], [31715], [34387],
|
||||
[20765], [30702], [10691], [49333], [1266], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115]
|
||||
]
|
||||
|
||||
// Used for phrase repetition penalty
|
||||
const repPenaltyAllowList = [
|
||||
[49256, 49264, 49231, 49230, 49287, 85, 49255, 49399, 49262, 336, 333, 432, 363, 468, 492, 745, 401, 426, 623, 794,
|
||||
1096, 2919, 2072, 7379, 1259, 2110, 620, 526, 487, 16562, 603, 805, 761, 2681, 942, 8917, 653, 3513, 506, 5301,
|
||||
562, 5010, 614, 10942, 539, 2976, 462, 5189, 567, 2032, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 588,
|
||||
803, 1040, 49209, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
]
|
||||
|
||||
// Ban the dinkus and asterism
|
||||
const logitBiasExp = [
|
||||
{ "sequence": [23], "bias": -0.08, "ensure_sequence_finish": false, "generate_once": false },
|
||||
{ "sequence": [21], "bias": -0.08, "ensure_sequence_finish": false, "generate_once": false }
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
euterpeBadWordsList,
|
||||
krakeBadWordsList,
|
||||
badWordsList,
|
||||
repPenaltyAllowList,
|
||||
logitBiasExp
|
||||
};
|
Reference in New Issue
Block a user