This commit is contained in:
RossAscends
2023-04-23 05:07:29 +09:00
38 changed files with 906 additions and 515 deletions

32
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -0,0 +1,32 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ config.conf
public/settings.json public/settings.json
/thumbnails /thumbnails
whitelist.txt whitelist.txt
.vscode

4
.npmignore Normal file
View File

@ -0,0 +1,4 @@
node_modules/
/uploads/
.DS_Store
/thumbnails

174
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "TavernAI", "name": "sillytavern",
"version": "1.3.0", "version": "1.4.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "TavernAI", "name": "sillytavern",
"version": "1.3.0", "version": "1.4.8",
"dependencies": { "dependencies": {
"@dqbd/tiktoken": "^1.0.2", "@dqbd/tiktoken": "^1.0.2",
"axios": "^1.3.4", "axios": "^1.3.4",
@ -31,10 +31,11 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"webp-converter": "2.3.2", "webp-converter": "2.3.2",
"ws": "^8.13.0" "ws": "^8.13.0",
"yargs": "^17.7.1"
}, },
"bin": { "bin": {
"TavernAI": "server.js" "sillytavern": "server.js"
} }
}, },
"node_modules/@dqbd/tiktoken": { "node_modules/@dqbd/tiktoken": {
@ -476,6 +477,28 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/any-base": { "node_modules/any-base": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
@ -652,6 +675,35 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -880,6 +932,11 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": { "node_modules/encodeurl": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -888,6 +945,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -1073,6 +1138,14 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
@ -1245,6 +1318,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-function": { "node_modules/is-function": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
@ -1779,6 +1860,14 @@
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -1937,6 +2026,30 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}, },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strtok3": { "node_modules/strtok3": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
@ -2083,6 +2196,22 @@
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
} }
}, },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -2151,6 +2280,39 @@
"engines": { "engines": {
"node": ">=0.4" "node": ">=0.4"
} }
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
"integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
} }
} }
} }

View File

@ -23,17 +23,21 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"webp-converter": "2.3.2", "webp-converter": "2.3.2",
"ws": "^8.13.0" "ws": "^8.13.0",
"yargs": "^17.7.1"
}, },
"overrides": { "overrides": {
"parse-bmfont-xml": { "parse-bmfont-xml": {
"xml2js": "^0.5.0" "xml2js": "^0.5.0"
} }
}, },
"name": "TavernAI", "name": "sillytavern",
"version": "1.3.0", "version": "1.4.8",
"scripts": {
"start": "node server.js"
},
"bin": { "bin": {
"TavernAI": "server.js" "sillytavern": "./server.js"
}, },
"rules": { "rules": {
"no-path-concat": "off", "no-path-concat": "off",

View File

@ -1416,13 +1416,27 @@
<div title="Token counts may be inaccurate and provided just for reference." id="result_info"></div> <div title="Token counts may be inaccurate and provided just for reference." id="result_info"></div>
</div> </div>
<hr> <hr>
<div id="description_div" class="margin-bot-10px"> <div id="fav_chara_wrap">
Description <div id="fav_chara_label" class="margin-bot-10px">
<a href="/notes/1" class="notes-link" target="_blank"> <label for="fav_checkbox" class="checkbox_label">
<span class="note-link-span">?</span> <input type="checkbox" id="fav_checkbox" name="fav"/>
</a> Favorite
<a href="/notes/15" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
</label>
</div>
<div>
</div>
</div>
</div> <div id="description_div" class="margin-bot-10px">
Description
<a href="/notes/1" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
</div>
<textarea id="description_textarea" placeholder="Describe your character's physical and mental traits here." class="margin-bot-10px" name="description" placeholder=""></textarea> <textarea id="description_textarea" placeholder="Describe your character's physical and mental traits here." class="margin-bot-10px" name="description" placeholder=""></textarea>
<div id="first_message_div" class="margin-bot-10px"> <div id="first_message_div" class="margin-bot-10px">
@ -1459,6 +1473,10 @@
</div> </div>
<div id="rm_group_buttons"> <div id="rm_group_buttons">
<div class="rm_group_settings"> <div class="rm_group_settings">
<label class="checkbox_label">
<input id="rm_group_fav" type="checkbox" />
Favorite
</label>
<label class="checkbox_label"> <label class="checkbox_label">
<input id="rm_group_allow_self_responses" type="checkbox" /> <input id="rm_group_allow_self_responses" type="checkbox" />
Allow bot responses to self Allow bot responses to self
@ -1518,6 +1536,8 @@
<div class="fa-solid fa-user-group"></div> <div class="fa-solid fa-user-group"></div>
</div> </div>
<div class="ch_name"></div> <div class="ch_name"></div>
<i class='group_fav_icon fa-solid fa-star fa-2xs'></i>
<input class="ch_fav" value="" hidden />
</div> </div>
</div> </div>
</div> </div>
@ -1533,6 +1553,7 @@
<div id="rm_button_create" title="Create New Character" class="menu_button fa-solid fa-user-plus "></div> <div id="rm_button_create" title="Create New Character" class="menu_button fa-solid fa-user-plus "></div>
<div id="character_import_button" title="Import Character from File" class="menu_button fa-solid fa-file-arrow-up "></div> <div id="character_import_button" title="Import Character from File" class="menu_button fa-solid fa-file-arrow-up "></div>
<div id="rm_button_group_chats" title="Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div> <div id="rm_button_group_chats" title="Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
<div id="filter_by_fav" title="Filter By Favorite" class="menu_button fa-solid fa-star"></div>
</div> </div>
<form id="form_character_search_form" action="javascript:void(null);"> <form id="form_character_search_form" action="javascript:void(null);">
<input id="character_search_bar" class="text_pole" type="search" placeholder="Character search..." maxlength="50" /> <input id="character_search_bar" class="text_pole" type="search" placeholder="Character search..." maxlength="50" />

View File

@ -1,47 +1,47 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Character Descriptions</title> <title>Character Descriptions</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>Character description</h2> <h2>Character description</h2>
<p> <p>
Used to add the character description and the rest that the AI should know.<br><br> Used to add the character description and the rest that the AI should know.<br><br>
For example, you can add information about the world in which the action takes place and describe For example, you can add information about the world in which the action takes place and describe
the characteristics for the character you are playing for.<br><br> the characteristics for the character you are playing for.<br><br>
Usually it all takes 200-350 tokens. Usually it all takes 200-350 tokens.
</p> </p>
<h3>Methods and format</h3> <h3>Methods and format</h3>
<p> <p>
For most Kobold's models the easiest way is to use a free form for description, and in each sentence it is desirable to specify the name of the character.<br><br> For most Kobold's models the easiest way is to use a free form for description, and in each sentence it
The entire description should be in one line without hyphenation.<br><br> is desirable to specify the name of the character.<br><br>
For example:<br><br> The entire description should be in one line without hyphenation.<br><br>
<code> For example:<br><br>
Chloe is a female elf. Chloe wears black-white maid dress with green collar and red glasses. Chloe has medium length black hair. Chloe's personality is... <code>
</code> Chloe is a female elf. Chloe wears black-white maid dress with green collar and red glasses. Chloe has medium length black hair. Chloe's personality is...
</p> </code>
</p>
<p> <p>
But that the AI would be less confused the best way is to use the W++ format.<Br><br> But that the AI would be less confused the best way is to use the W++ format.<Br><br>
Details here: <a target="_blank" href="https://github.com/KoboldAI/KoboldAI-Client/wiki/Pro-Tips">Pro-Tips</a> Details here: <a target="_blank"
</p> href="https://github.com/KoboldAI/KoboldAI-Client/wiki/Pro-Tips">Pro-Tips</a>
<hr> </p>
<p> <hr>
<u>A list of tags that are replaced when sending to generate:</u><br><br> <p>
{{user}} and &lt;USER&gt; are replaced by the User's Name<br> <u>A list of tags that are replaced when sending to generate:</u><br><br>
{{char}} and &lt;BOT&gt; are replaced by the Character's Name {{user}} and &lt;USER&gt; are replaced by the User's Name<br>
</p> {{char}} and &lt;BOT&gt; are replaced by the Character's Name
</div> </p>
</div> </div>
</div>
</body> </body>
</html> </html>

View File

@ -1,13 +1,11 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Import Chat</title> <title>Import Chat</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -1,13 +1,11 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Example Dialogues</title> <title>Example Dialogues</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -1,13 +1,11 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Scenario</title> <title>Scenario</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
@ -15,15 +13,20 @@
<div id="content"> <div id="content">
<h2>World Info</h2> <h2>World Info</h2>
<h4>World Info enhances AI's understanding of the details in your world.</h4> <h4>World Info enhances AI's understanding of the details in your world.</h4>
<p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries when keywords associated with the entries are present in the message text.</p> <p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries
<p>The SillyTavern engine activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.</p> when keywords associated with the entries are present in the message text.</p>
<p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does not guarantee its appearance in the generated output messages.</i></p> <p>The SillyTavern engine activates and seamlessly integrates the appropriate lore into the prompt,
providing background information to the AI.</p>
<p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does
not guarantee its appearance in the generated output messages.</i></p>
<h3>Pro Tips</h3> <h3>Pro Tips</h3>
<ul> <ul>
<li>The AI does not insert keywords into context, so each World Info entry should be a comprehensive, standalone description.</li> <li>The AI does not insert keywords into context, so each World Info entry should be a comprehensive,
standalone description.</li>
<li>To create a rich and detailed world lore, entries can be interlinked and reference one another.</li> <li>To create a rich and detailed world lore, entries can be interlinked and reference one another.</li>
<li>To conserve tokens, it is advisable to keep entry contents concise, with a general recommended limit of 50 tokens per entry.</li> <li>To conserve tokens, it is advisable to keep entry contents concise, with a general recommended limit
of 50 tokens per entry.</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -4,10 +4,8 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> </head>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head>
<body> <body>
<div id="main"> <div id="main">
<div id="content"> <div id="content">

22
public/notes/15.html Normal file
View File

@ -0,0 +1,22 @@
<html>
<head>
<title>Favorite Character</title>
<link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
</head>
<body>
<div id="main">
<div id="content">
<h2>Favorite Character</h2>
<p>
Mark character as favorite to quickly filter on the side menu bar by pressing the star button.
</p>
</div>
</div>
</body>
</html>

View File

@ -1,13 +1,11 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Personality Summary</title> <title>Personality Summary</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
@ -15,7 +13,8 @@
<div id="content"> <div id="content">
<h2>Personality summary</h2> <h2>Personality summary</h2>
<p> <p>
A brief description of the personality. It is added to the chat at a depth of 8-15 messages, so it has a significant impact on the character. A brief description of the personality. It is added to the chat at a depth of 8-15 messages, so it has a
significant impact on the character.
</p> </p>
Example: Example:

View File

@ -1,40 +1,40 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - First Message</title> <title>First Message</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>First message</h2> <h2>First message</h2>
<p> <p>
The First Message is an important thing that sets exactly how and in what style the character will communicate.<Br><br> The First Message is an important thing that sets exactly how and in what style the character will
It is desirable that the character's first message be long, so that later it would be less likely that the character would respond in with very short messages. <br><br> communicate.<Br><br>
You can also use asterisks ** to describe the character's actions. It is desirable that the character's first message be long, so that later it would be less likely that
</P> the character would respond in with very short messages. <br><br>
You can also use asterisks ** to describe the character's actions.
</P>
For example: For example:
<br><br> <br><br>
<code> <code>
*I noticed you came inside, I walked up and stood right in front of you* Welcome. I'm glad to see you here. *I noticed you came inside, I walked up and stood right in front of you* Welcome. I'm glad to see you here.
*I said with toothy smug sunny smile looking you straight in the eye* What brings you... *I said with toothy smug sunny smile looking you straight in the eye* What brings you...
</code> </code>
<Br> <Br>
<hr> <hr>
<p> <p>
<u>A list of tags that are replaced when sending to generate:</u><br><br> <u>A list of tags that are replaced when sending to generate:</u><br><br>
{{user}} and &lt;USER&gt; are replaced by the User's Name<br> {{user}} and &lt;USER&gt; are replaced by the User's Name<br>
{{char}} and &lt;BOT&gt; are replaced by the Character's Name {{char}} and &lt;BOT&gt; are replaced by the Character's Name
</p> </p>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,13 +1,11 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - KobolAI Settings</title> <title>KoboldAI Settings</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -1,36 +1,37 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Novel AI API Key</title> <title>NovelAI API Key</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>Finding your NAI API key</h2> <h2>Finding your NAI API key</h2>
<h4>To get a NovelAI API key, follow these instructions:</h4> <h4>To get a NovelAI API key, follow these instructions:</h4>
<p> <p>
1. Go to the NovelAI website and Login.<br><br> 1. Go to the NovelAI website and Login.<br><br>
2. Create a new story, or open an existing story.<br><br> 2. Create a new story, or open an existing story.<br><br>
3. Open the Network Tools on your web browser. (For Chrome or Firefox, you do this by pressing Ctrl+Shift+I, then switching to the Network tab.)<br><br> 3. Open the Network Tools on your web browser. (For Chrome or Firefox, you do this by pressing
Ctrl+Shift+I, then switching to the Network tab.)<br><br>
4. Generate something. You should see two requests to api.novelai.net/ai/generate-stream, which might look something like this:<br><br> 4. Generate something. You should see two requests to api.novelai.net/ai/generate-stream, which might
<img src="1.png"><br><br> look something like this:<br><br>
5. Select the second request, then in the Headers tab of the inspection panel, scroll down to the very bottom. Look for a header called Authorization:<br><br> <img src="1.png"><br><br>
<img src="2.png"><br><br> 5. Select the second request, then in the Headers tab of the inspection panel, scroll down to the very
The long string (after "Bearer", not including it) is your API key.<br><br> bottom. Look for a header called Authorization:<br><br>
* Proxies and Cloudflare-type services may interfere with connection. <img src="2.png"><br><br>
</p> The long string (after "Bearer", not including it) is your API key.<br><br>
</div> * Proxies and Cloudflare-type services may interfere with connection.
</div> </p>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -1,43 +1,43 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - NovelAI Settings</title> <title>NovelAI Settings</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>NovelAI settings</h2> <h2>NovelAI settings</h2>
<p> <p>
The files with the settings are here (SillyTavern\public\NovelAI Settings).<br> The files with the settings are here (SillyTavern\public\NovelAI Settings).<br>
You can also manually add your own settings files. You can also manually add your own settings files.
</p> </p>
<h3>Temperature</h3> <h3>Temperature</h3>
<p> <p>
Value from 0.1 to 2.0. <br> Value from 0.1 to 2.0. <br>
Lower value - the answers are more logical, but less creative. <br> Lower value - the answers are more logical, but less creative. <br>
Higher value - the answers are more creative, but less logical. Higher value - the answers are more creative, but less logical.
</p> </p>
<h3>Repetition penalty</h3> <h3>Repetition penalty</h3>
<p> <p>
Repetition penalty is responsible for the penalty of repeated words. <br> Repetition penalty is responsible for the penalty of repeated words. <br>
If the character is fixated on something or repeats the same phrase, then increasing this parameter will fix it. <br> If the character is fixated on something or repeats the same phrase, then increasing this parameter will
It is not recommended to increase this parameter too much for the chat format, as it may break this format. <br> fix it. <br>
<b>The standard value for chat is approximately 1.0 - 1.05</b> It is not recommended to increase this parameter too much for the chat format, as it may break this
</p> format. <br>
<h3>Repetition penalty range</h3> <b>The standard value for chat is approximately 1.0 - 1.05</b>
<p> </p>
The range of influence of Repetition penalty in tokens. <h3>Repetition penalty range</h3>
</p> <p>
</div> The range of influence of Repetition penalty in tokens.
</div> </p>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -1,13 +1,11 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - NovelAI Models</title> <title>NovelAI Models</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -1,49 +1,53 @@
<html> <html>
<head> <head>
<title>SillyTavern - Note - Anchors</title> <title>Anchors</title>
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>
<div id="main"> <div id="main">
<div id="content"> <div id="content">
<h2>Anchors</h2> <h2>Anchors</h2>
<p> <p>
Anchors are used to increase the length of messages.<br> Anchors are used to increase the length of messages.<br>
There are two types of anchors: <u>Character Anchor</u> and <u>Style Anchor</u> There are two types of anchors: <u>Character Anchor</u> and <u>Style Anchor</u>
</p> </p>
<p> <p>
<u>Character Anchor</u> - affects the character played by the AI by motivating it to write longer messages.<br><br> <u>Character Anchor</u> - affects the character played by the AI by motivating it to write longer
Looks like: messages.<br><br>
<code>[Elaborate speaker]</code> Looks like:
</p> <code>[Elaborate speaker]</code>
<p> </p>
<u>Style Anchor</u> - affects the entire AI model, motivating the AI to write longer messages even when it is not acting as the character.<Br><br> <p>
Looks like: <u>Style Anchor</u> - affects the entire AI model, motivating the AI to write longer messages even when
<code>[Writing style: very long messages]</code> it is not acting as the character.<Br><br>
</p> Looks like:
<hr> <code>[Writing style: very long messages]</code>
<p> </p>
Anchors Order sets the location of anchors in the promt, the first anchor in the order is much further back in the context and thus has less influence than second. <hr>
</p> <p>
<p> Anchors Order sets the location of anchors in the promt, the first anchor in the order is much further
The second anchor is only turned on after 8-12 messages, because when the chat still only has a few messages, the first anchor creates enough effect on its own. back in the context and thus has less influence than second.
</p> </p>
<p> <p>
Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently long messages. The second anchor is only turned on after 8-12 messages, because when the chat still only has a few
For these cases, you can disable the anchors by unchecking their respective boxes. messages, the first anchor creates enough effect on its own.
</p> </p>
<p> <p>
<u>When using Pygmalion models these anchors are automatically disabled, since Pygmalion already generates long enough messages.</u> Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently
</p> long messages.
</div> For these cases, you can disable the anchors by unchecking their respective boxes.
</div> </p>
<p>
<u>When using Pygmalion models these anchors are automatically disabled, since Pygmalion already
generates long enough messages.</u>
</p>
</div>
</div>
</body> </body>
</html> </html>

View File

@ -5,11 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap"
rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,11 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap"
rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,11 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap"
rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,11 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap"
rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -5,9 +5,7 @@
<link rel="stylesheet" href="/css/notes.css"> <link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&amp;display=swap" rel="stylesheet">
</head> </head>
<body> <body>

View File

@ -159,7 +159,7 @@ export {
} }
// API OBJECT FOR EXTERNAL WIRING // API OBJECT FOR EXTERNAL WIRING
window["TavernAI"] = {}; window["SillyTavern"] = {};
let converter = new showdown.Converter({ emoji: "true" }); let converter = new showdown.Converter({ emoji: "true" });
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' }); const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
@ -204,6 +204,8 @@ let dialogueResolve = null;
let chat_metadata = {}; let chat_metadata = {};
let streamingProcessor = null; let streamingProcessor = null;
let fav_ch_checked = false;
window.filterByFav = false;
const durationSaveEdit = 200; const durationSaveEdit = 200;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit); const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
@ -330,6 +332,7 @@ var menu_type = ""; //what is selected in the menu
var selected_button = ""; //which button pressed var selected_button = ""; //which button pressed
//create pole save //create pole save
var create_save_name = ""; var create_save_name = "";
var create_fav_chara = "";
var create_save_description = ""; var create_save_description = "";
var create_save_personality = ""; var create_save_personality = "";
var create_save_first_message = ""; var create_save_first_message = "";
@ -635,8 +638,6 @@ function updateSoftPromptsList(soft_prompts) {
} }
function printCharacters() { function printCharacters() {
//console.log('printCharacters() entered');
$("#rm_print_characters_block").empty(); $("#rm_print_characters_block").empty();
//console.log('printCharacters() -- sees '+characters.length+' characters.'); //console.log('printCharacters() -- sees '+characters.length+' characters.');
characters.forEach(function (item, i, arr) { characters.forEach(function (item, i, arr) {
@ -648,7 +649,8 @@ function printCharacters() {
`<div class=character_select chid=${i} id="CharID${i}"> `<div class=character_select chid=${i} id="CharID${i}">
<div class=avatar><img src="${this_avatar}"></div> <div class=avatar><img src="${this_avatar}"></div>
<div class=ch_name>${item.name}</div> <div class=ch_name>${item.name} ${item.fav == "true" ? '<i class="fa-solid fa-star fa-2xs"></i>' : ''}</div>
<input class="ch_fav" value=${item.fav} hidden />
</div>` </div>`
); );
//console.log('printcharacters() -- printing -- ChID '+i+' ('+item.name+')'); //console.log('printcharacters() -- printing -- ChID '+i+' ('+item.name+')');
@ -1314,7 +1316,7 @@ class StreamingProcessor {
} }
async function Generate(type, automatic_trigger, force_name2) { async function Generate(type, automatic_trigger, force_name2) {
console.log('Generate entered'); //console.log('Generate entered');
setGenerationProgress(0); setGenerationProgress(0);
tokens_already_generated = 0; tokens_already_generated = 0;
const isImpersonate = type == "impersonate"; const isImpersonate = type == "impersonate";
@ -3166,6 +3168,9 @@ function select_selected_character(chid) {
if (characters[chid].avatar != "none") { if (characters[chid].avatar != "none") {
this_avatar = getThumbnailUrl('avatar', characters[chid].avatar); this_avatar = getThumbnailUrl('avatar', characters[chid].avatar);
} }
$("#fav_checkbox").prop("checked", characters[chid].fav == "true");
$("#avatar_load_preview").attr("src", this_avatar); $("#avatar_load_preview").attr("src", this_avatar);
$("#name_div").css("display", "none"); $("#name_div").css("display", "none");
@ -3456,7 +3461,7 @@ function isHordeGenerationNotAllowed() {
return false; return false;
} }
window["TavernAI"].getContext = function () { window["SillyTavern"].getContext = function () {
return { return {
chat: chat, chat: chat,
characters: characters, characters: characters,
@ -3791,6 +3796,25 @@ $(document).ready(function () {
} }
}); });
$("#filter_by_fav").click(function() {
filterByFav = !filterByFav;
const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
if(filterByFav){
$(selector).each(function () {
if($(this).children(".ch_fav").length !== 0){
$(this).children(".ch_fav").val().toLowerCase().includes(true)
? $(this).show()
: $(this).hide();
}
});
$("#filter_by_fav").addClass("fav_on");
}else{
$(selector).show();
$("#filter_by_fav").removeClass("fav_on");
}
});
$("#send_but").click(function () { $("#send_but").click(function () {
if (is_send_press == false) { if (is_send_press == false) {
is_send_press = true; is_send_press = true;
@ -3833,6 +3857,7 @@ $(document).ready(function () {
selected_button = "character_edit"; selected_button = "character_edit";
select_selected_character(this_chid); select_selected_character(this_chid);
} }
$("#character_search_bar").val("").trigger("input");
}); });
$(document).on("click", ".character_select", function () { $(document).on("click", ".character_select", function () {
@ -3860,7 +3885,6 @@ $(document).ready(function () {
selected_button = "character_edit"; selected_button = "character_edit";
select_selected_character(this_chid); select_selected_character(this_chid);
} }
$("#character_search_bar").val("").trigger("input");
}); });
@ -4128,6 +4152,7 @@ $(document).ready(function () {
$("#rm_info_avatar").html(""); $("#rm_info_avatar").html("");
let save_name = create_save_name; let save_name = create_save_name;
var formData = new FormData($("#form_create").get(0)); var formData = new FormData($("#form_create").get(0));
formData.set('fav', fav_ch_checked);
if ($("#form_create").attr("actiontype") == "createcharacter") { if ($("#form_create").attr("actiontype") == "createcharacter") {
if ($("#character_name_pole").val().length > 0) { if ($("#character_name_pole").val().length > 0) {
//if the character name text area isn't empty (only posible when creating a new character) //if the character name text area isn't empty (only posible when creating a new character)
@ -4294,11 +4319,18 @@ $(document).ready(function () {
create_save_scenario = $("#scenario_pole").val(); create_save_scenario = $("#scenario_pole").val();
create_save_mes_example = $("#mes_example_textarea").val(); create_save_mes_example = $("#mes_example_textarea").val();
create_save_first_message = $("#firstmessage_textarea").val(); create_save_first_message = $("#firstmessage_textarea").val();
create_fav_chara = $("#fav_checkbox").val();
} else { } else {
saveCharacterDebounced(); saveCharacterDebounced();
} }
}); });
$("#fav_checkbox").change(function(){
fav_ch_checked = $(this).prop("checked");
if (menu_type != "create") {
saveCharacterDebounced();
}
});
$("#talkativeness_slider").on("input", function () { $("#talkativeness_slider").on("input", function () {
if (menu_type == "create") { if (menu_type == "create") {

View File

@ -30,7 +30,7 @@ const extension_settings = {
let modules = []; let modules = [];
let activeExtensions = new Set(); let activeExtensions = new Set();
const getContext = () => window['TavernAI'].getContext(); const getContext = () => window['SillyTavern'].getContext();
const getApiUrl = () => extension_settings.apiUrl; const getApiUrl = () => extension_settings.apiUrl;
const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } }; const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } };
let connectedToApi = false; let connectedToApi = false;

View File

@ -0,0 +1,102 @@
export { ElevenLabsTtsProvider }
class ElevenLabsTtsProvider {
API_KEY
set API_KEY(apiKey) {
this.API_KEY = apiKey
}
get API_KEY() {
return this.API_KEY
}
async fetchTtsVoiceIds() {
const headers = {
'xi-api-key': this.API_KEY
}
const response = await fetch(`https://api.elevenlabs.io/v1/voices`, {
headers: headers
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
}
const responseJson = await response.json()
return responseJson.voices
}
async fetchTtsVoiceSettings() {
const headers = {
'xi-api-key': this.API_KEY
}
const response = await fetch(
`https://api.elevenlabs.io/v1/voices/settings/default`,
{
headers: headers
}
)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
}
return response.json()
}
async fetchTtsGeneration(text, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`)
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`,
{
method: 'POST',
headers: {
'xi-api-key': this.API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: text })
}
)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
}
return response
}
async fetchTtsFromHistory(history_item_id) {
console.info(`Fetched existing TTS with history_item_id ${history_item_id}`)
const response = await fetch(
`https://api.elevenlabs.io/v1/history/${history_item_id}/audio`,
{
headers: {
'xi-api-key': this.API_KEY
}
}
)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
}
return response
}
async fetchTtsHistory() {
const headers = {
'xi-api-key': this.API_KEY
}
const response = await fetch(`https://api.elevenlabs.io/v1/history`, {
headers: headers
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`)
}
const responseJson = await response.json()
return responseJson.history
}
async findTtsGenerationInHistory(message, voiceId) {
const ttsHistory = await this.fetchTtsHistory()
for (const history of ttsHistory) {
const text = history.text
const itemId = history.history_item_id
if (message === text && history.voice_id == voiceId) {
console.info(`Existing TTS history item ${itemId} found: ${text} `)
return itemId
}
}
return ''
}
}

View File

@ -1,160 +1,80 @@
import { callPopup, saveSettingsDebounced } from "../../../script.js"; import { callPopup, saveSettingsDebounced } from '../../../script.js'
import { extension_settings, getContext } from "../../extensions.js"; import { extension_settings, getContext } from '../../extensions.js'
import { getStringHash } from "../../utils.js"; import { getStringHash } from '../../utils.js'
import { ElevenLabsTtsProvider } from './elevenlabs.js'
const UPDATE_INTERVAL = 1000; const UPDATE_INTERVAL = 1000
let API_KEY
let voiceMap = {} // {charName:voiceid, charName2:voiceid2} let voiceMap = {} // {charName:voiceid, charName2:voiceid2}
let elevenlabsTtsVoices = [] let elevenlabsTtsVoices = []
let audioControl let audioControl
let lastCharacterId = null
let lastGroupId = null
let lastChatId = null
let lastMessageHash = null
let lastCharacterId = null; let ttsProvider = new ElevenLabsTtsProvider()
let lastGroupId = null;
let lastChatId = null;
let lastMessageHash = null;
async function moduleWorker() { async function moduleWorker() {
// Primarily determinign when to add new chat to the TTS queue // Primarily determinign when to add new chat to the TTS queue
const enabled = $("#elevenlabs_enabled").is(':checked'); const enabled = $('#elevenlabs_enabled').is(':checked')
if (!enabled) { if (!enabled) {
return; return
} }
const context = getContext() const context = getContext()
const chat = context.chat; const chat = context.chat
processTtsQueue(); processTtsQueue()
processAudioJobQueue(); processAudioJobQueue()
updateUiAudioPlayState(); updateUiAudioPlayState()
// no characters or group selected // no characters or group selected
if (!context.groupId && !context.characterId) { if (!context.groupId && !context.characterId) {
return; return
} }
// Chat/character/group changed // Chat/character/group changed
if ((context.groupId && lastGroupId !== context.groupId) || (context.characterId !== lastCharacterId) || (context.chatId !== lastChatId)) { if (
(context.groupId && lastGroupId !== context.groupId) ||
context.characterId !== lastCharacterId ||
context.chatId !== lastChatId
) {
currentMessageNumber = context.chat.length ? context.chat.length : 0 currentMessageNumber = context.chat.length ? context.chat.length : 0
saveLastValues(); saveLastValues()
return; return
} }
// take the count of messages // take the count of messages
let lastMessageNumber = context.chat.length ? context.chat.length : 0; let lastMessageNumber = context.chat.length ? context.chat.length : 0
// There's no new messages // There's no new messages
let diff = lastMessageNumber - currentMessageNumber; let diff = lastMessageNumber - currentMessageNumber
let hashNew = getStringHash((chat.length && chat[chat.length - 1].mes) ?? ''); let hashNew = getStringHash((chat.length && chat[chat.length - 1].mes) ?? '')
if (diff == 0 && hashNew === lastMessageHash) { if (diff == 0 && hashNew === lastMessageHash) {
return; return
} }
const message = chat[chat.length - 1]; const message = chat[chat.length - 1]
// We're currently swiping or streaming. Don't generate voice // We're currently swiping or streaming. Don't generate voice
if (message.mes === '...' || (context.streamingProcessor && !context.streamingProcessor.isFinished)) { if (
return; message.mes === '...' ||
(context.streamingProcessor && !context.streamingProcessor.isFinished)
) {
return
} }
// New messages, add new chat to history // New messages, add new chat to history
lastMessageHash = hashNew; lastMessageHash = hashNew
currentMessageNumber = lastMessageNumber; currentMessageNumber = lastMessageNumber
console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); console.debug(
ttsJobQueue.push(message); `Adding message from ${message.name} for TTS processing: "${message.mes}"`
} )
ttsJobQueue.push(message)
//#################//
// TTS API Calls //
//#################//
async function fetchTtsVoiceIds() {
const headers = {
'xi-api-key': API_KEY
};
const response = await fetch(`https://api.elevenlabs.io/v1/voices`, {
headers: headers
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
const responseJson = await response.json();
return responseJson.voices;
}
async function fetchTtsVoiceSettings() {
const headers = {
'xi-api-key': API_KEY
};
const response = await fetch(`https://api.elevenlabs.io/v1/voices/settings/default`, {
headers: headers
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
return response.json();
}
async function fetchTtsGeneration(text, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`);
const response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
method: 'POST',
headers: {
'xi-api-key': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: text })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
return response;
}
async function fetchTtsFromHistory(history_item_id) {
console.info(`Fetched existing TTS with history_item_id ${history_item_id}`);
const response = await fetch(`https://api.elevenlabs.io/v1/history/${history_item_id}/audio`, {
headers: {
'xi-api-key': API_KEY
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
return response;
}
async function fetchTtsHistory() {
const headers = {
'xi-api-key': API_KEY
};
const response = await fetch(`https://api.elevenlabs.io/v1/history`, {
headers: headers
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
const responseJson = await response.json();
return responseJson.history;
}
async function findTtsGenerationInHistory(message, voiceId) {
const ttsHistory = await fetchTtsHistory();
for (const history of ttsHistory) {
const text = history.text;
const itemId = history.history_item_id;
if (message === text && history.voice_id == voiceId) {
console.info(`Existing TTS history item ${itemId} found: ${text} `)
return itemId;
}
}
return ''
} }
//##################// //##################//
@ -170,14 +90,13 @@ let queueProcessorReady = true
let lastAudioPosition = 0 let lastAudioPosition = 0
async function playAudioData(audioBlob) { async function playAudioData(audioBlob) {
const reader = new FileReader(); const reader = new FileReader()
reader.onload = function (e) { reader.onload = function (e) {
const srcUrl = e.target.result; const srcUrl = e.target.result
audioElement.src = srcUrl; audioElement.src = srcUrl
}; }
reader.readAsDataURL(audioBlob); reader.readAsDataURL(audioBlob)
audioElement.addEventListener('ended', completeCurrentAudioJob) audioElement.addEventListener('ended', completeCurrentAudioJob)
audioElement.addEventListener('canplay', () => { audioElement.addEventListener('canplay', () => {
console.debug(`Starting TTS playback`) console.debug(`Starting TTS playback`)
@ -185,28 +104,26 @@ async function playAudioData(audioBlob) {
}) })
} }
window['elevenlabsPreview'] = function(id) { window['elevenlabsPreview'] = function (id) {
const audio = document.getElementById(id); const audio = document.getElementById(id)
audio.play(); audio.play()
} }
async function onElevenlabsVoicesClick() { async function onElevenlabsVoicesClick() {
let popupText = ''; let popupText = ''
try { try {
const voiceIds = await fetchTtsVoiceIds(); const voiceIds = await ttsProvider.fetchTtsVoiceIds()
for (const voice of voiceIds) { for (const voice of voiceIds) {
popupText += `<div class="voice_preview"><b>${voice.name}</b> <i onclick="elevenlabsPreview('${voice.voice_id}')" class="fa-solid fa-play"></i></div>`; popupText += `<div class="voice_preview"><b>${voice.name}</b> <i onclick="elevenlabsPreview('${voice.voice_id}')" class="fa-solid fa-play"></i></div>`
popupText += `<audio id="${voice.voice_id}" src="${voice.preview_url}"></audio>`; popupText += `<audio id="${voice.voice_id}" src="${voice.preview_url}"></audio>`
} }
} } catch {
catch {
popupText = 'Could not load voices list. Check your API key.' popupText = 'Could not load voices list. Check your API key.'
} }
callPopup(popupText, 'text')
callPopup(popupText, 'text');
} }
function completeCurrentAudioJob() { function completeCurrentAudioJob() {
@ -217,21 +134,21 @@ function completeCurrentAudioJob() {
/** /**
* Accepts an HTTP response containing audio/mpeg data, and puts the data as a Blob() on the queue for playback * Accepts an HTTP response containing audio/mpeg data, and puts the data as a Blob() on the queue for playback
* @param {*} response * @param {*} response
*/ */
async function addAudioJob(response) { async function addAudioJob(response) {
const audioData = await response.blob() const audioData = await response.blob()
if (audioData.type != "audio/mpeg") { if (audioData.type != 'audio/mpeg') {
throw `TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${audioData.type}` throw `TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${audioData.type}`
} }
audioJobQueue.push(audioData) audioJobQueue.push(audioData)
console.debug("Pushed audio job to queue.") console.debug('Pushed audio job to queue.')
} }
async function processAudioJobQueue() { async function processAudioJobQueue() {
// Nothing to do, audio not completed, or audio paused - stop processing. // Nothing to do, audio not completed, or audio paused - stop processing.
if (audioJobQueue.length == 0 || !queueProcessorReady || audioPaused) { if (audioJobQueue.length == 0 || !queueProcessorReady || audioPaused) {
return; return
} }
try { try {
queueProcessorReady = false queueProcessorReady = false
@ -243,7 +160,6 @@ async function processAudioJobQueue() {
} }
} }
//################// //################//
// TTS Control // // TTS Control //
//################// //################//
@ -259,22 +175,24 @@ function completeTtsJob() {
function saveLastValues() { function saveLastValues() {
const context = getContext() const context = getContext()
lastGroupId = context.groupId; lastGroupId = context.groupId
lastCharacterId = context.characterId; lastCharacterId = context.characterId
lastChatId = context.chatId; lastChatId = context.chatId
lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''); lastMessageHash = getStringHash(
(context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''
)
} }
async function tts(text, voiceId) { async function tts(text, voiceId) {
const historyId = await findTtsGenerationInHistory(text, voiceId); const historyId = await ttsProvider.findTtsGenerationInHistory(text, voiceId)
let response; let response
if (historyId) { if (historyId) {
console.debug(`Found existing TTS generation with id ${historyId}`) console.debug(`Found existing TTS generation with id ${historyId}`)
response = await fetchTtsFromHistory(historyId); response = await ttsProvider.fetchTtsFromHistory(historyId)
} else { } else {
console.debug(`No existing TTS generation found, requesting new generation`) console.debug(`No existing TTS generation found, requesting new generation`)
response = await fetchTtsGeneration(text, voiceId); response = await ttsProvider.fetchTtsGeneration(text, voiceId)
} }
addAudioJob(response) addAudioJob(response)
completeTtsJob() completeTtsJob()
@ -283,12 +201,12 @@ async function tts(text, voiceId) {
async function processTtsQueue() { async function processTtsQueue() {
// Called each moduleWorker iteration to pull chat messages from queue // Called each moduleWorker iteration to pull chat messages from queue
if (currentTtsJob || ttsJobQueue.length <= 0 || audioPaused) { if (currentTtsJob || ttsJobQueue.length <= 0 || audioPaused) {
return; return
} }
console.debug("New message found, running TTS") console.debug('New message found, running TTS')
currentTtsJob = ttsJobQueue.shift() currentTtsJob = ttsJobQueue.shift()
const text = currentTtsJob.mes.replaceAll('*', '...'); const text = currentTtsJob.mes.replaceAll('*', '...')
const char = currentTtsJob.name const char = currentTtsJob.name
try { try {
@ -298,20 +216,19 @@ async function processTtsQueue() {
const voice = await getTtsVoice(voiceMap[char]) const voice = await getTtsVoice(voiceMap[char])
const voiceId = voice.voice_id const voiceId = voice.voice_id
if (voiceId == null) { if (voiceId == null) {
throw (`Unable to attain voiceId for ${char}`) throw `Unable to attain voiceId for ${char}`
} }
tts(text, voiceId) tts(text, voiceId)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
currentTtsJob = null currentTtsJob = null
} }
} }
// Secret function for now // Secret function for now
async function playFullConversation() { async function playFullConversation() {
const context = getContext() const context = getContext()
const chat = context.chat; const chat = context.chat
ttsJobQueue = chat ttsJobQueue = chat
} }
window.playFullConversation = playFullConversation window.playFullConversation = playFullConversation
@ -323,52 +240,60 @@ window.playFullConversation = playFullConversation
function loadSettings() { function loadSettings() {
const context = getContext() const context = getContext()
if (Object.keys(extension_settings.elevenlabstts).length === 0) { if (Object.keys(extension_settings.elevenlabstts).length === 0) {
Object.assign(extension_settings.elevenlabstts, defaultSettings); Object.assign(extension_settings.elevenlabstts, defaultSettings)
} }
$('#elevenlabs_api_key').val(extension_settings.elevenlabstts.elevenlabsApiKey); $('#elevenlabs_api_key').val(
$('#elevenlabs_voice_map').val(extension_settings.elevenlabstts.elevenlabsVoiceMap); extension_settings.elevenlabstts.elevenlabsApiKey
$('#elevenlabs_enabled').prop('checked', extension_settings.elevenlabstts.enabled); )
$('#elevenlabs_voice_map').val(
extension_settings.elevenlabstts.elevenlabsVoiceMap
)
$('#elevenlabs_enabled').prop(
'checked',
extension_settings.elevenlabstts.enabled
)
onElevenlabsApplyClick() onElevenlabsApplyClick()
} }
const defaultSettings = { const defaultSettings = {
elevenlabsApiKey: "", elevenlabsApiKey: '',
elevenlabsVoiceMap: "", elevenlabsVoiceMap: '',
elevenlabsEnabed: false elevenlabsEnabed: false
}; }
function setElevenLabsStatus(status, success) { function setElevenLabsStatus(status, success) {
$('#elevenlabs_status').text(status) $('#elevenlabs_status').text(status)
if (success) { if (success) {
$("#elevenlabs_status").removeAttr("style"); $('#elevenlabs_status').removeAttr('style')
} else { } else {
$('#elevenlabs_status').css('color', 'red'); $('#elevenlabs_status').css('color', 'red')
} }
} }
async function updateApiKey() { async function updateApiKey() {
const context = getContext(); const context = getContext()
const value = $('#elevenlabs_api_key').val(); const value = $('#elevenlabs_api_key').val()
// Using this call to validate API key // Using this call to validate API key
API_KEY = String(value) ttsProvider.API_KEY = String(value)
await fetchTtsVoiceIds().catch((error => { await ttsProvider.fetchTtsVoiceIds().catch(error => {
API_KEY = null ttsProvider.API_KEY = null
throw `ElevenLabs TTS API key invalid` throw `ElevenLabs TTS API key invalid`
})) })
extension_settings.elevenlabstts.elevenlabsApiKey = String(value); extension_settings.elevenlabstts.elevenlabsApiKey = String(value)
console.debug(`Saved new API_KEY: ${value}`); console.debug(`Saved new API_KEY: ${value}`)
saveSettingsDebounced(); saveSettingsDebounced()
} }
function parseVoiceMap(voiceMapString) { function parseVoiceMap(voiceMapString) {
let parsedVoiceMap = {} let parsedVoiceMap = {}
for (const [charName, voiceId] of voiceMapString.split(",").map(s => s.split(":"))) { for (const [charName, voiceId] of voiceMapString
.split(',')
.map(s => s.split(':'))) {
if (charName && voiceId) { if (charName && voiceId) {
parsedVoiceMap[charName.trim()] = voiceId.trim(); parsedVoiceMap[charName.trim()] = voiceId.trim()
} }
} }
return parsedVoiceMap return parsedVoiceMap
@ -377,24 +302,26 @@ function parseVoiceMap(voiceMapString) {
async function getTtsVoice(name) { async function getTtsVoice(name) {
// We're caching the list of voice_ids. This might cause trouble if the user creates a new voice without restarting // We're caching the list of voice_ids. This might cause trouble if the user creates a new voice without restarting
if (elevenlabsTtsVoices.length == 0) { if (elevenlabsTtsVoices.length == 0) {
elevenlabsTtsVoices = await fetchTtsVoiceIds(); elevenlabsTtsVoices = await ttsProvider.fetchTtsVoiceIds()
} }
const match = elevenlabsTtsVoices.filter((elevenVoice) => elevenVoice.name == name)[0]; const match = elevenlabsTtsVoices.filter(
elevenVoice => elevenVoice.name == name
)[0]
if (!match) { if (!match) {
throw `TTS Voice name ${name} not found in ElevenLabs account`; throw `TTS Voice name ${name} not found in ElevenLabs account`
} }
return match; return match
} }
async function voicemapIsValid(parsedVoiceMap) { async function voicemapIsValid(parsedVoiceMap) {
let valid = true let valid = true
for (const characterName in parsedVoiceMap) { for (const characterName in parsedVoiceMap) {
const parsedVoiceName = parsedVoiceMap[characterName]; const parsedVoiceName = parsedVoiceMap[characterName]
try { try {
await getTtsVoice(parsedVoiceName); await getTtsVoice(parsedVoiceName)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
valid = false; valid = false
} }
} }
return valid return valid
@ -402,19 +329,19 @@ async function voicemapIsValid(parsedVoiceMap) {
async function updateVoiceMap() { async function updateVoiceMap() {
let isValidResult = false let isValidResult = false
const context = getContext(); const context = getContext()
// console.debug("onElevenlabsVoiceMapSubmit"); // console.debug("onElevenlabsVoiceMapSubmit");
const value = $('#elevenlabs_voice_map').val(); const value = $('#elevenlabs_voice_map').val()
const parsedVoiceMap = parseVoiceMap(value); const parsedVoiceMap = parseVoiceMap(value)
isValidResult = await voicemapIsValid(parsedVoiceMap); isValidResult = await voicemapIsValid(parsedVoiceMap)
if (isValidResult) { if (isValidResult) {
extension_settings.elevenlabstts.elevenlabsVoiceMap = String(value); extension_settings.elevenlabstts.elevenlabsVoiceMap = String(value)
context.elevenlabsVoiceMap = String(value) context.elevenlabsVoiceMap = String(value)
voiceMap = parsedVoiceMap voiceMap = parsedVoiceMap
console.debug(`Saved new voiceMap: ${value}`) console.debug(`Saved new voiceMap: ${value}`)
saveSettingsDebounced(); saveSettingsDebounced()
} else { } else {
throw "Voice map is invalid, check console for errors" throw 'Voice map is invalid, check console for errors'
} }
} }
@ -422,23 +349,27 @@ function onElevenlabsApplyClick() {
Promise.all([updateApiKey(), updateVoiceMap()]) Promise.all([updateApiKey(), updateVoiceMap()])
.then(([result1, result2]) => { .then(([result1, result2]) => {
updateUiAudioPlayState() updateUiAudioPlayState()
setElevenLabsStatus("Successfully applied settings", true) setElevenLabsStatus('Successfully applied settings', true)
}) })
.catch((error) => { .catch(error => {
setElevenLabsStatus(error, false) setElevenLabsStatus(error, false)
}); })
} }
function onElevenlabsEnableClick() { function onElevenlabsEnableClick() {
extension_settings.elevenlabstts.enabled = $("#elevenlabs_enabled").is(':checked'); extension_settings.elevenlabstts.enabled = $('#elevenlabs_enabled').is(
':checked'
)
updateUiAudioPlayState() updateUiAudioPlayState()
saveSettingsDebounced(); saveSettingsDebounced()
} }
function updateUiAudioPlayState() { function updateUiAudioPlayState() {
if (extension_settings.elevenlabstts.enabled == true) { if (extension_settings.elevenlabstts.enabled == true) {
audioControl.style.display = 'flex' audioControl.style.display = 'flex'
const img = !audioElement.paused ? "fa-solid fa-circle-pause" : "fa-solid fa-circle-play" const img = !audioElement.paused
? 'fa-solid fa-circle-pause'
: 'fa-solid fa-circle-play'
audioControl.className = img audioControl.className = img
} else { } else {
audioControl.style.display = 'none' audioControl.style.display = 'none'
@ -452,9 +383,9 @@ function onAudioControlClicked() {
function addAudioControl() { function addAudioControl() {
$('#send_but_sheld').prepend('<div id="tts_media_control"/>') $('#send_but_sheld').prepend('<div id="tts_media_control"/>')
$('#tts_media_control').on('click', onAudioControlClicked) $('#send_but_sheld').on('click', onAudioControlClicked)
audioControl = document.getElementById('tts_media_control'); audioControl = document.getElementById('tts_media_control')
updateUiAudioPlayState(); updateUiAudioPlayState()
} }
$(document).ready(function () { $(document).ready(function () {
@ -487,14 +418,14 @@ $(document).ready(function () {
</div> </div>
</div> </div>
</div> </div>
`; `
$('#extensions_settings').append(settingsHtml); $('#extensions_settings').append(settingsHtml)
$('#elevenlabs_apply').on('click', onElevenlabsApplyClick); $('#elevenlabs_apply').on('click', onElevenlabsApplyClick)
$('#elevenlabs_enabled').on('click', onElevenlabsEnableClick); $('#elevenlabs_enabled').on('click', onElevenlabsEnableClick)
$('#elevenlabs_voices').on('click', onElevenlabsVoicesClick); $('#elevenlabs_voices').on('click', onElevenlabsVoicesClick)
} }
addAudioControl(); addAudioControl()
addExtensionControls(); addExtensionControls()
loadSettings(); loadSettings()
setInterval(moduleWorker, UPDATE_INTERVAL); setInterval(moduleWorker, UPDATE_INTERVAL)
}); })

View File

@ -266,7 +266,6 @@ async function getSpritesList(name) {
} }
async function getExpressionsList() { async function getExpressionsList() {
console.log('getting expressions list');
// get something for offline mode (default images) // get something for offline mode (default images)
if (!modules.includes('classify')) { if (!modules.includes('classify')) {
return DEFAULT_EXPRESSIONS; return DEFAULT_EXPRESSIONS;

View File

@ -214,7 +214,9 @@ function printGroups() {
const template = $("#group_list_template .group_select").clone(); const template = $("#group_list_template .group_select").clone();
template.data("id", group.id); template.data("id", group.id);
template.attr("grid", group.id); template.attr("grid", group.id);
template.find(".ch_name").text(group.name); template.find(".ch_name").html(group.name);
group.fav ? template.find(".group_fav_icon").show() : template.find(".group_fav_icon").hide();
template.find(".ch_fav").val(group.fav);
$("#rm_print_characters_block").prepend(template); $("#rm_print_characters_block").prepend(template);
updateGroupAvatar(group); updateGroupAvatar(group);
} }
@ -695,7 +697,6 @@ async function reorderGroupMember(chat_id, groupMember, direction) {
function select_group_chats(chat_id, skipAnimation) { function select_group_chats(chat_id, skipAnimation) {
const group = chat_id && groups.find((x) => x.id == chat_id); const group = chat_id && groups.find((x) => x.id == chat_id);
const groupName = group?.name ?? ""; const groupName = group?.name ?? "";
$("#rm_group_chat_name").val(groupName); $("#rm_group_chat_name").val(groupName);
$("#rm_group_chat_name").off(); $("#rm_group_chat_name").off();
$("#rm_group_chat_name").on("input", async function () { $("#rm_group_chat_name").on("input", async function () {
@ -753,6 +754,7 @@ function select_group_chats(chat_id, skipAnimation) {
const groupHasMembers = !!$("#rm_group_members").children().length; const groupHasMembers = !!$("#rm_group_members").children().length;
$("#rm_group_submit").prop("disabled", !groupHasMembers); $("#rm_group_submit").prop("disabled", !groupHasMembers);
$("#rm_group_allow_self_responses").prop("checked", group && group.allow_self_responses); $("#rm_group_allow_self_responses").prop("checked", group && group.allow_self_responses);
$("#rm_group_fav").prop("checked", group && group.fav);
// bottom buttons // bottom buttons
if (chat_id) { if (chat_id) {
@ -774,11 +776,22 @@ function select_group_chats(chat_id, skipAnimation) {
callPopup("<h3>Delete the group?</h3>", "del_group"); callPopup("<h3>Delete the group?</h3>", "del_group");
}); });
$("#rm_group_fav").off();
$("#rm_group_fav").on("input", async function(){
if (group) {
let _thisGroup = groups.find((x) => x.id == chat_id);
const value = $(this).prop("checked");
_thisGroup.fav = value;
await editGroup(chat_id);
}
});
$("#rm_group_allow_self_responses").off(); $("#rm_group_allow_self_responses").off();
$("#rm_group_allow_self_responses").on("input", async function () { $("#rm_group_allow_self_responses").on("input", async function () {
if (group) { if (group) {
let _thisGroup = groups.find((x) => x.id == chat_id);
const value = $(this).prop("checked"); const value = $(this).prop("checked");
group.allow_self_responses = value; _thisGroup.allow_self_responses = value;
await editGroup(chat_id); await editGroup(chat_id);
} }
}); });
@ -829,6 +842,9 @@ $(document).ready(() => {
updateChatMetadata({}, true); updateChatMetadata({}, true);
chat.length = 0; chat.length = 0;
await getGroupChat(id); await getGroupChat(id);
//to avoid the filter being lit up yellow and left at true while the list of character and group reseted.
$("#filter_by_fav").removeClass("fav_on");
filterByFav = false;
} }
select_group_chats(id); select_group_chats(id);
@ -852,6 +868,7 @@ $(document).ready(() => {
$("#rm_group_submit").click(async function () { $("#rm_group_submit").click(async function () {
let name = $("#rm_group_chat_name").val(); let name = $("#rm_group_chat_name").val();
let allow_self_responses = !!$("#rm_group_allow_self_responses").prop("checked"); let allow_self_responses = !!$("#rm_group_allow_self_responses").prop("checked");
let fav = $("#rm_group_fav").prop("checked");
let activation_strategy = $('input[name="rm_group_activation_strategy"]:checked').val() ?? group_activation_strategy.NATURAL; let activation_strategy = $('input[name="rm_group_activation_strategy"]:checked').val() ?? group_activation_strategy.NATURAL;
const members = $("#rm_group_members .group_member") const members = $("#rm_group_members .group_member")
.map((_, x) => $(x).data("id")) .map((_, x) => $(x).data("id"))
@ -877,6 +894,7 @@ $(document).ready(() => {
allow_self_responses: allow_self_responses, allow_self_responses: allow_self_responses,
activation_strategy: activation_strategy, activation_strategy: activation_strategy,
chat_metadata: {}, chat_metadata: {},
fav: fav,
}), }),
}); });

View File

@ -909,6 +909,9 @@ select option:not(:checked) {
cursor: not-allowed; cursor: not-allowed;
} }
.fav_on {
color: #ffff00 !important;
}
#api_url_text, #api_url_text,
#textgenerationwebui_api_url_text { #textgenerationwebui_api_url_text {
@ -1138,6 +1141,17 @@ input[type=search]:focus::-webkit-search-cancel-button {
margin-bottom: 4px; margin-bottom: 4px;
} }
#fav_chara_wrap{
display: flex;
margin: 5px 0px;
}
#fav_chara {
border: none;
font-size: var(--mainFontSize);
display: flex;
}
#description_div { #description_div {
position: relative; position: relative;
} }
@ -2444,6 +2458,9 @@ h5 {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
width: calc(100% - 110px); width: calc(100% - 110px);
display: flex;
align-items: center;
gap: 5px;
} }
/* Rules for icon display */ /* Rules for icon display */
@ -2484,12 +2501,15 @@ h5 {
} }
.group_select .ch_name { .group_select .ch_name {
flex-grow: 1;
max-width: calc(100% - 100px); max-width: calc(100% - 100px);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.group_select .group_fav_icon{
margin-left: 5px;
}
#typing_indicator_template { #typing_indicator_template {
display: none !important; display: none !important;
} }

154
server.js
View File

@ -1,3 +1,27 @@
#!/usr/bin/env node
const process = require('process')
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const cliArguments = yargs(hideBin(process.argv))
.option('ssl', {
type: 'boolean',
default: 'false',
describe: 'Enables SSL'
}).option('certPath', {
type: 'string',
default: 'certs/cert.pem',
describe: 'Path to your certificate file.'
}).option('keyPath', {
type: 'string',
default: 'certs/privkey.pem',
describe: 'Path to your private key file.'
}).argv;
// change all relative paths
process.chdir(__dirname)
const express = require('express'); const express = require('express');
const compression = require('compression'); const compression = require('compression');
const app = express(); const app = express();
@ -9,6 +33,7 @@ const open = require('open');
const rimraf = require("rimraf"); const rimraf = require("rimraf");
const multer = require("multer"); const multer = require("multer");
const http = require("http");
const https = require('https'); const https = require('https');
//const PNG = require('pngjs').PNG; //const PNG = require('pngjs').PNG;
const extract = require('png-chunks-extract'); const extract = require('png-chunks-extract');
@ -29,10 +54,10 @@ const ExifReader = require('exifreader');
const exif = require('piexifjs'); const exif = require('piexifjs');
const webp = require('webp-converter'); const webp = require('webp-converter');
const config = require(path.join(process.cwd(), './config.conf')); const config = require(path.join(__dirname, './config.conf'));
const server_port = process.env.SILLY_TAVERN_PORT || config.port; const server_port = process.env.SILLY_TAVERN_PORT || config.port;
const whitelistPath = path.join(process.cwd(), "./whitelist.txt"); const whitelistPath = path.join(__dirname, "./whitelist.txt");
let whitelist = config.whitelist; let whitelist = config.whitelist;
if (fs.existsSync(whitelistPath)) { if (fs.existsSync(whitelistPath)) {
@ -43,7 +68,7 @@ if (fs.existsSync(whitelistPath)) {
} }
const whitelistMode = config.whitelistMode; const whitelistMode = config.whitelistMode;
const autorun = config.autorun; const autorun = config.autorun && !cliArguments.ssl;
const enableExtensions = config.enableExtensions; const enableExtensions = config.enableExtensions;
const listen = config.listen; const listen = config.listen;
@ -183,8 +208,8 @@ app.use(function (req, res, next) { //Security
//clientIp = req.connection.remoteAddress.split(':').pop(); //clientIp = req.connection.remoteAddress.split(':').pop();
if (whitelistMode === true && !whitelist.includes(clientIp)) { if (whitelistMode === true && !whitelist.includes(clientIp)) {
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of TavernAI folder.\n'); console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n');
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of TavernAI folder.'); return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.');
} }
next(); next();
}); });
@ -211,7 +236,7 @@ app.use((req, res, next) => {
app.use(express.static(__dirname + "/public", { refresh: true })); app.use(express.static(__dirname + "/public", { refresh: true }));
app.use('/backgrounds', (req, res) => { app.use('/backgrounds', (req, res) => {
const filePath = decodeURIComponent(path.join(process.cwd(), 'public/backgrounds', req.url.replace(/%20/g, ' '))); const filePath = decodeURIComponent(path.join(__dirname, 'public/backgrounds', req.url.replace(/%20/g, ' ')));
fs.readFile(filePath, (err, data) => { fs.readFile(filePath, (err, data) => {
if (err) { if (err) {
res.status(404).send('File not found'); res.status(404).send('File not found');
@ -223,7 +248,7 @@ app.use('/backgrounds', (req, res) => {
}); });
app.use('/characters', (req, res) => { app.use('/characters', (req, res) => {
const filePath = decodeURIComponent(path.join(process.cwd(), charactersPath, req.url.replace(/%20/g, ' '))); const filePath = decodeURIComponent(path.join(__dirname, charactersPath, req.url.replace(/%20/g, ' ')));
fs.readFile(filePath, (err, data) => { fs.readFile(filePath, (err, data) => {
if (err) { if (err) {
res.status(404).send('File not found'); res.status(404).send('File not found');
@ -368,7 +393,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
if (!!request.header('X-Response-Streaming')) { if (!!request.header('X-Response-Streaming')) {
const fn_index = Number(request.header('X-Gradio-Streaming-Function')); const fn_index = Number(request.header('X-Gradio-Streaming-Function'));
let isStreamingStopped = false; let isStreamingStopped = false;
request.socket.on('close', function() { request.socket.on('close', function () {
isStreamingStopped = true; isStreamingStopped = true;
}); });
@ -675,7 +700,7 @@ function checkServer() {
//***************** Main functions //***************** Main functions
function charaFormatData(data) { function charaFormatData(data) {
var char = { "name": data.ch_name, "description": data.description, "personality": data.personality, "first_mes": data.first_mes, "avatar": 'none', "chat": data.ch_name + ' - ' + humanizedISO8601DateTime(), "mes_example": data.mes_example, "scenario": data.scenario, "create_date": humanizedISO8601DateTime(), "talkativeness": data.talkativeness }; var char = { "name": data.ch_name, "description": data.description, "personality": data.personality, "first_mes": data.first_mes, "avatar": 'none', "chat": data.ch_name + ' - ' + humanizedISO8601DateTime(), "mes_example": data.mes_example, "scenario": data.scenario, "create_date": humanizedISO8601DateTime(), "talkativeness": data.talkativeness, "fav": data.fav};
return char; return char;
} }
app.post("/createcharacter", urlencodedParser, function (request, response) { app.post("/createcharacter", urlencodedParser, function (request, response) {
@ -729,10 +754,8 @@ app.post("/editcharacter", urlencodedParser, async function (request, response)
var char = charaFormatData(request.body);//{"name": request.body.ch_name, "description": request.body.description, "personality": request.body.personality, "first_mes": request.body.first_mes, "avatar": request.body.avatar_url, "chat": request.body.chat, "last_mes": request.body.last_mes, "mes_example": ''}; var char = charaFormatData(request.body);//{"name": request.body.ch_name, "description": request.body.description, "personality": request.body.personality, "first_mes": request.body.first_mes, "avatar": request.body.avatar_url, "chat": request.body.chat, "last_mes": request.body.last_mes, "mes_example": ''};
char.chat = request.body.chat; char.chat = request.body.chat;
char.create_date = request.body.create_date; char.create_date = request.body.create_date;
char = JSON.stringify(char); char = JSON.stringify(char);
let target_img = (request.body.avatar_url).replace('.png', ''); let target_img = (request.body.avatar_url).replace('.png', '');
try { try {
if (!filedata) { if (!filedata) {
@ -1598,18 +1621,18 @@ app.post("/importchat", urlencodedParser, function (request, response) {
const errors = []; const errors = [];
newChats.forEach(chat => fs.writeFile( newChats.forEach(chat => fs.writeFile(
chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl', chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl',
chat.map(JSON.stringify).join('\n'), chat.map(JSON.stringify).join('\n'),
'utf8', 'utf8',
(err) => err ?? errors.push(err) (err) => err ?? errors.push(err)
) )
); );
if (0 < errors.length) { if (0 < errors.length) {
response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors)); response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
} }
response.send({res: true}); response.send({ res: true });
} else { } else {
response.send({ error: true }); response.send({ error: true });
} }
@ -1755,6 +1778,7 @@ app.post('/creategroup', jsonParser, (request, response) => {
allow_self_responses: !!request.body.allow_self_responses, allow_self_responses: !!request.body.allow_self_responses,
activation_strategy: request.body.activation_strategy ?? 0, activation_strategy: request.body.activation_strategy ?? 0,
chat_metadata: request.body.chat_metadata ?? {}, chat_metadata: request.body.chat_metadata ?? {},
fav: request.body.fav,
}; };
const pathToFile = path.join(directories.groups, `${id}.json`); const pathToFile = path.join(directories.groups, `${id}.json`);
const fileData = JSON.stringify(chatMetadata); const fileData = JSON.stringify(chatMetadata);
@ -1771,7 +1795,6 @@ app.post('/editgroup', jsonParser, (request, response) => {
if (!request.body || !request.body.id) { if (!request.body || !request.body.id) {
return response.sendStatus(400); return response.sendStatus(400);
} }
const id = request.body.id; const id = request.body.id;
const pathToFile = path.join(directories.groups, `${id}.json`); const pathToFile = path.join(directories.groups, `${id}.json`);
const fileData = JSON.stringify(request.body); const fileData = JSON.stringify(request.body);
@ -1840,7 +1863,7 @@ app.post('/deletegroup', jsonParser, async (request, response) => {
const POE_DEFAULT_BOT = 'a2'; const POE_DEFAULT_BOT = 'a2';
async function getPoeClient(token, useCache=false) { async function getPoeClient(token, useCache = false) {
let client = new poe.Client(false, useCache); let client = new poe.Client(false, useCache);
await client.init(token); await client.init(token);
return client; return client;
@ -1906,7 +1929,7 @@ app.post('/generate_poe', jsonParser, async (request, response) => {
if (streaming) { if (streaming) {
let isStreamingStopped = false; let isStreamingStopped = false;
request.socket.on('close', function() { request.socket.on('close', function () {
isStreamingStopped = true; isStreamingStopped = true;
client.abortController.abort(); client.abortController.abort();
}); });
@ -1972,17 +1995,17 @@ app.get('/get_sprites', jsonParser, function (request, response) {
try { try {
if (fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) { if (fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) {
sprites = fs.readdirSync(spritesPath) sprites = fs.readdirSync(spritesPath)
.filter(file => { .filter(file => {
const mimeType = mime.lookup(file); const mimeType = mime.lookup(file);
return mimeType && mimeType.startsWith('image/'); return mimeType && mimeType.startsWith('image/');
}) })
.map((file) => { .map((file) => {
const pathToSprite = path.join(spritesPath, file); const pathToSprite = path.join(spritesPath, file);
return { return {
label: path.parse(pathToSprite).name.toLowerCase(), label: path.parse(pathToSprite).name.toLowerCase(),
path: `/characters/${name}/${file}`, path: `/characters/${name}/${file}`,
}; };
}); });
} }
} }
catch (err) { catch (err) {
@ -2152,12 +2175,42 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
}); });
}); });
// Shamelessly stolen from Agnai
app.post("/openai_usage", jsonParser, async function (_, response) {
if (!request.body) return response.sendStatus(400);
const key = request.body.key;
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("/generate_openai", jsonParser, function (request, response_generate_openai) { app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
if (!request.body) return response_generate_openai.sendStatus(400); if (!request.body) return response_generate_openai.sendStatus(400);
const api_url = new URL(request.body.reverse_proxy || api_openai).toString(); const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
const controller = new AbortController(); const controller = new AbortController();
request.socket.on('close', function() { request.socket.on('close', function () {
controller.abort(); controller.abort();
}); });
@ -2319,23 +2372,46 @@ function getAsync(url, args) {
} }
// ** END ** // ** END **
app.listen(server_port, (listen ? '0.0.0.0' : '127.0.0.1'), async function () { const tavernUrl = new URL(
(cliArguments.ssl ? 'https://' : 'http://') +
(listen ? '0.0.0.0' : '127.0.0.1') +
(':' + server_port)
);
const setupTasks = async function () {
ensurePublicDirectoriesExist(); ensurePublicDirectoriesExist();
await ensureThumbnailCache(); await ensureThumbnailCache();
// Colab users could run the embedded tool // Colab users could run the embedded tool
if (!is_colab) { if (!is_colab) await convertWebp();
await convertWebp();
}
console.log('Launching...'); console.log('Launching...');
if (autorun) open('http://127.0.0.1:' + server_port);
console.log('TavernAI started: http://127.0.0.1:' + server_port); if (autorun) open(tavernUrl);
console.log('SillyTavern started: ' + tavernUrl);
if (fs.existsSync('public/characters/update.txt') && !is_colab) { if (fs.existsSync('public/characters/update.txt') && !is_colab) {
convertStage1(); convertStage1();
} }
}
}); if (true === cliArguments.ssl)
https.createServer(
{
cert: fs.readFileSync(cliArguments.certPath),
key: fs.readFileSync(cliArguments.keyPath)
}, app)
.listen(
tavernUrl.port,
tavernUrl.hostname,
setupTasks
);
else
http.createServer(app).listen(
tavernUrl.port,
tavernUrl.hostname,
setupTasks
);
//#####################CONVERTING IN NEW FORMAT######################## //#####################CONVERTING IN NEW FORMAT########################