sillytavern 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/.dockerignore +4 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/.github/workflows/npm-publish.yml +32 -0
  5. package/Dockerfile +49 -0
  6. package/Start.bat +3 -0
  7. package/colab/GPU.ipynb +339 -0
  8. package/colab/extras_server.py +40 -0
  9. package/colab/globals.py +2 -0
  10. package/colab/models.py +77 -0
  11. package/docker/docker-compose.yml +12 -0
  12. package/docker/docker-entrypoint.sh +28 -0
  13. package/faq.md +208 -0
  14. package/package.json +49 -0
  15. package/poe-client.js +638 -0
  16. package/poe-test.js +21 -0
  17. package/poe_graphql/AddHumanMessageMutation.graphql +52 -0
  18. package/poe_graphql/AddMessageBreakMutation.graphql +17 -0
  19. package/poe_graphql/AutoSubscriptionMutation.graphql +7 -0
  20. package/poe_graphql/BioFragment.graphql +8 -0
  21. package/poe_graphql/ChatAddedSubscription.graphql +5 -0
  22. package/poe_graphql/ChatFragment.graphql +6 -0
  23. package/poe_graphql/ChatListPaginationQuery.graphql +316 -0
  24. package/poe_graphql/ChatPaginationQuery.graphql +26 -0
  25. package/poe_graphql/ChatViewQuery.graphql +8 -0
  26. package/poe_graphql/DeleteHumanMessagesMutation.graphql +7 -0
  27. package/poe_graphql/DeleteMessageMutation.graphql +7 -0
  28. package/poe_graphql/HandleFragment.graphql +8 -0
  29. package/poe_graphql/LoginWithVerificationCodeMutation.graphql +13 -0
  30. package/poe_graphql/MessageAddedSubscription.graphql +115 -0
  31. package/poe_graphql/MessageDeletedSubscription.graphql +8 -0
  32. package/poe_graphql/MessageFragment.graphql +13 -0
  33. package/poe_graphql/MessageRemoveVoteMutation.graphql +7 -0
  34. package/poe_graphql/MessageSetVoteMutation.graphql +7 -0
  35. package/poe_graphql/SendVerificationCodeForLoginMutation.graphql +12 -0
  36. package/poe_graphql/ShareMessagesMutation.graphql +9 -0
  37. package/poe_graphql/SignupWithVerificationCodeMutation.graphql +13 -0
  38. package/poe_graphql/StaleChatUpdateMutation.graphql +7 -0
  39. package/poe_graphql/SubscriptionsMutation.graphql +9 -0
  40. package/poe_graphql/SummarizePlainPostQuery.graphql +3 -0
  41. package/poe_graphql/SummarizeQuotePostQuery.graphql +3 -0
  42. package/poe_graphql/SummarizeSharePostQuery.graphql +3 -0
  43. package/poe_graphql/UserSnippetFragment.graphql +14 -0
  44. package/poe_graphql/ViewerInfoQuery.graphql +21 -0
  45. package/poe_graphql/ViewerMessageLimitUpdatedSubscription.graphql +81 -0
  46. package/poe_graphql/ViewerStateFragment.graphql +30 -0
  47. package/poe_graphql/ViewerStateUpdatedSubscription.graphql +63 -0
  48. package/public/KoboldAI Settings/Ace of Spades 13B.settings +22 -0
  49. package/public/KoboldAI Settings/Adventurer-NeoX-20B-Erebus.settings +54 -0
  50. package/public/KoboldAI Settings/Basic Coherence 13B.settings +22 -0
  51. package/public/KoboldAI Settings/Best Guess 6B.settings +22 -0
  52. package/public/KoboldAI Settings/Calibrated-Pygmalion-6b.settings +47 -0
  53. package/public/KoboldAI Settings/Classic-Pygmalion-2.7b.settings +46 -0
  54. package/public/KoboldAI Settings/Classic-Pygmalion-6b.settings +47 -0
  55. package/public/KoboldAI Settings/Coherent Creativity 6B.settings +22 -0
  56. package/public/KoboldAI Settings/Default-TavernAI.settings +22 -0
  57. package/public/KoboldAI Settings/DragonSlayer-Pygmalion-6b.settings +47 -0
  58. package/public/KoboldAI Settings/GPU-Pygmalion-6b.settings +47 -0
  59. package/public/KoboldAI Settings/Genesis 13B.settings +22 -0
  60. package/public/KoboldAI Settings/Godlike.settings +22 -0
  61. package/public/KoboldAI Settings/Good Winds.settings +22 -0
  62. package/public/KoboldAI Settings/Lancer-OPT-2.7B-Erebus.settings +54 -0
  63. package/public/KoboldAI Settings/Liminal Drift.settings +22 -0
  64. package/public/KoboldAI Settings/Low Rider 13B.settings +22 -0
  65. package/public/KoboldAI Settings/Luna Moth 6B.settings +22 -0
  66. package/public/KoboldAI Settings/Mayday.settings +22 -0
  67. package/public/KoboldAI Settings/Ouroboros.settings +22 -0
  68. package/public/KoboldAI Settings/Pleasing Results 6B.settings +22 -0
  69. package/public/KoboldAI Settings/Pro Writer 13B.settings +22 -0
  70. package/public/KoboldAI Settings/RA - Pygmalion-1.3b.settings +46 -0
  71. package/public/KoboldAI Settings/Storywriter 6B.settings +22 -0
  72. package/public/NovelAI Settings/Classic-Euterpe.settings +14 -0
  73. package/public/NovelAI Settings/Classic-Krake.settings +14 -0
  74. package/public/OpenAI Settings/Default.settings +15 -0
  75. package/public/TextGen Settings/Beam Search.settings +15 -0
  76. package/public/TextGen Settings/Contrastive Search.settings +15 -0
  77. package/public/TextGen Settings/Default.settings +15 -0
  78. package/public/TextGen Settings/Deterministic.settings +15 -0
  79. package/public/TextGen Settings/Kobold (Godlike).settings +15 -0
  80. package/public/TextGen Settings/Kobold (Liminal Drift).settings +15 -0
  81. package/public/TextGen Settings/Naive.settings +15 -0
  82. package/public/TextGen Settings/NovelAI (Best Guess).settings +15 -0
  83. package/public/TextGen Settings/NovelAI (Decadence).settings +15 -0
  84. package/public/TextGen Settings/NovelAI (Genesis).settings +15 -0
  85. package/public/TextGen Settings/NovelAI (Lycaenidae).settings +15 -0
  86. package/public/TextGen Settings/NovelAI (Ouroboros).settings +15 -0
  87. package/public/TextGen Settings/NovelAI (Pleasing Results).settings +15 -0
  88. package/public/TextGen Settings/NovelAI (Sphinx Moth).settings +15 -0
  89. package/public/TextGen Settings/NovelAI (Storywriter).settings +15 -0
  90. package/public/TextGen Settings/Pygmalion.settings +15 -0
  91. package/public/css/fontawesome.css +8488 -0
  92. package/public/css/notes.css +46 -0
  93. package/public/css/solid.css +24 -0
  94. package/public/favicon.ico +0 -0
  95. package/public/img/No-Image-Placeholder.svg +309 -0
  96. package/public/img/addbg3.png +0 -0
  97. package/public/img/ai4.png +0 -0
  98. package/public/img/apple-icon-114x114.png +0 -0
  99. package/public/img/apple-icon-144x144.png +0 -0
  100. package/public/img/apple-icon-57x57.png +0 -0
  101. package/public/img/apple-icon-72x72.png +0 -0
  102. package/public/img/book2.png +0 -0
  103. package/public/img/default-expressions/admiration.png +0 -0
  104. package/public/img/default-expressions/amusement.png +0 -0
  105. package/public/img/default-expressions/anger.png +0 -0
  106. package/public/img/default-expressions/annoyance.png +0 -0
  107. package/public/img/default-expressions/approval.png +0 -0
  108. package/public/img/default-expressions/caring.png +0 -0
  109. package/public/img/default-expressions/confusion.png +0 -0
  110. package/public/img/default-expressions/curiosity.png +0 -0
  111. package/public/img/default-expressions/desire.png +0 -0
  112. package/public/img/default-expressions/desire1.png +0 -0
  113. package/public/img/default-expressions/desire2.png +0 -0
  114. package/public/img/default-expressions/disappointment.png +0 -0
  115. package/public/img/default-expressions/disapproval.png +0 -0
  116. package/public/img/default-expressions/disgust.png +0 -0
  117. package/public/img/default-expressions/embarrassment.png +0 -0
  118. package/public/img/default-expressions/excitement.png +0 -0
  119. package/public/img/default-expressions/fear.png +0 -0
  120. package/public/img/default-expressions/gratitude.png +0 -0
  121. package/public/img/default-expressions/grief.png +0 -0
  122. package/public/img/default-expressions/joy.png +0 -0
  123. package/public/img/default-expressions/love.png +0 -0
  124. package/public/img/default-expressions/nervousness.png +0 -0
  125. package/public/img/default-expressions/neutral.png +0 -0
  126. package/public/img/default-expressions/optimism.png +0 -0
  127. package/public/img/default-expressions/pride.png +0 -0
  128. package/public/img/default-expressions/realization.png +0 -0
  129. package/public/img/default-expressions/relief.png +0 -0
  130. package/public/img/default-expressions/remorse.png +0 -0
  131. package/public/img/default-expressions/sadness.png +0 -0
  132. package/public/img/default-expressions/surprise.png +0 -0
  133. package/public/img/five.png +0 -0
  134. package/public/img/times-circle.svg +1 -0
  135. package/public/img/you.png +0 -0
  136. package/public/index.html +1899 -0
  137. package/public/notes/1.html +44 -0
  138. package/public/notes/1.png +0 -0
  139. package/public/notes/10.html +20 -0
  140. package/public/notes/11.html +33 -0
  141. package/public/notes/12.html +27 -0
  142. package/public/notes/13.html +29 -0
  143. package/public/notes/13_1.html +24 -0
  144. package/public/notes/13_2.html +25 -0
  145. package/public/notes/13_3.html +89 -0
  146. package/public/notes/14.html +30 -0
  147. package/public/notes/2.html +37 -0
  148. package/public/notes/2.png +0 -0
  149. package/public/notes/3.html +37 -0
  150. package/public/notes/4.html +105 -0
  151. package/public/notes/6.html +33 -0
  152. package/public/notes/7.html +40 -0
  153. package/public/notes/8.html +19 -0
  154. package/public/notes/9.html +46 -0
  155. package/public/notes/advanced_formatting.html +91 -0
  156. package/public/notes/group_reply_strategy.html +63 -0
  157. package/public/notes/message_sound.html +33 -0
  158. package/public/notes/multigen.html +36 -0
  159. package/public/notes/oai_api_key.html +37 -0
  160. package/public/notes/textgen_streaming.html +29 -0
  161. package/public/notes/token-limits.html +68 -0
  162. package/public/script.js +5131 -0
  163. package/public/scripts/RossAscends-mods.js +750 -0
  164. package/public/scripts/bookmarks.js +130 -0
  165. package/public/scripts/extensions/backgrounds/index.js +129 -0
  166. package/public/scripts/extensions/backgrounds/manifest.json +11 -0
  167. package/public/scripts/extensions/backgrounds/style.css +45 -0
  168. package/public/scripts/extensions/caption/index.js +117 -0
  169. package/public/scripts/extensions/caption/manifest.json +13 -0
  170. package/public/scripts/extensions/caption/style.css +23 -0
  171. package/public/scripts/extensions/dice/droll.js +108 -0
  172. package/public/scripts/extensions/dice/index.js +96 -0
  173. package/public/scripts/extensions/dice/manifest.json +11 -0
  174. package/public/scripts/extensions/dice/style.css +26 -0
  175. package/public/scripts/extensions/expressions/index.js +387 -0
  176. package/public/scripts/extensions/expressions/manifest.json +13 -0
  177. package/public/scripts/extensions/expressions/style.css +115 -0
  178. package/public/scripts/extensions/floating-prompt/index.js +151 -0
  179. package/public/scripts/extensions/floating-prompt/manifest.json +11 -0
  180. package/public/scripts/extensions/floating-prompt/style.css +19 -0
  181. package/public/scripts/extensions/memory/index.js +358 -0
  182. package/public/scripts/extensions/memory/manifest.json +13 -0
  183. package/public/scripts/extensions/memory/style.css +34 -0
  184. package/public/scripts/extensions.js +288 -0
  185. package/public/scripts/f-localStorage.js +27 -0
  186. package/public/scripts/gpt-2-3-tokenizer/README.md +28 -0
  187. package/public/scripts/gpt-2-3-tokenizer/encoder.js +1 -0
  188. package/public/scripts/gpt-2-3-tokenizer/mod.js +169 -0
  189. package/public/scripts/gpt-2-3-tokenizer/vocab.bpe.js +1 -0
  190. package/public/scripts/gpt-3-tokenizer/array-keyed-map.js +210 -0
  191. package/public/scripts/gpt-3-tokenizer/gpt3-tokenizer.js +271 -0
  192. package/public/scripts/gpt-3-tokenizer/gpt3-tokenizer.js.map +1 -0
  193. package/public/scripts/group-chats.js +899 -0
  194. package/public/scripts/horde.js +214 -0
  195. package/public/scripts/jquery-3.5.1.min.js +2 -0
  196. package/public/scripts/jquery-cookie-1.4.1.min.js +2 -0
  197. package/public/scripts/jquery.transit.min.js +1 -0
  198. package/public/scripts/kai-settings.js +179 -0
  199. package/public/scripts/nai-settings.js +108 -0
  200. package/public/scripts/openai.js +973 -0
  201. package/public/scripts/poe.js +360 -0
  202. package/public/scripts/popper.js +2008 -0
  203. package/public/scripts/popper.js.map +1 -0
  204. package/public/scripts/power-user.js +571 -0
  205. package/public/scripts/purify.min.js +3 -0
  206. package/public/scripts/purify.min.js.map +1 -0
  207. package/public/scripts/showdown.min.js +3 -0
  208. package/public/scripts/showdown.min.js.map +1 -0
  209. package/public/scripts/swiped-events.js +165 -0
  210. package/public/scripts/textgen-settings.js +194 -0
  211. package/public/scripts/toolcool-color-picker.js +87 -0
  212. package/public/scripts/utils.js +109 -0
  213. package/public/scripts/world-info.js +661 -0
  214. package/public/sounds/message.mp3 +0 -0
  215. package/public/style.css +3633 -0
  216. package/public/webfonts/NotoSans/NotoSans-Black.woff +0 -0
  217. package/public/webfonts/NotoSans/NotoSans-Black.woff2 +0 -0
  218. package/public/webfonts/NotoSans/NotoSans-BlackItalic.woff +0 -0
  219. package/public/webfonts/NotoSans/NotoSans-BlackItalic.woff2 +0 -0
  220. package/public/webfonts/NotoSans/NotoSans-Bold.woff +0 -0
  221. package/public/webfonts/NotoSans/NotoSans-Bold.woff2 +0 -0
  222. package/public/webfonts/NotoSans/NotoSans-BoldItalic.woff +0 -0
  223. package/public/webfonts/NotoSans/NotoSans-BoldItalic.woff2 +0 -0
  224. package/public/webfonts/NotoSans/NotoSans-ExtraBold.woff +0 -0
  225. package/public/webfonts/NotoSans/NotoSans-ExtraBold.woff2 +0 -0
  226. package/public/webfonts/NotoSans/NotoSans-ExtraBoldItalic.woff +0 -0
  227. package/public/webfonts/NotoSans/NotoSans-ExtraBoldItalic.woff2 +0 -0
  228. package/public/webfonts/NotoSans/NotoSans-ExtraLight.woff +0 -0
  229. package/public/webfonts/NotoSans/NotoSans-ExtraLight.woff2 +0 -0
  230. package/public/webfonts/NotoSans/NotoSans-ExtraLightItalic.woff +0 -0
  231. package/public/webfonts/NotoSans/NotoSans-ExtraLightItalic.woff2 +0 -0
  232. package/public/webfonts/NotoSans/NotoSans-Italic.woff +0 -0
  233. package/public/webfonts/NotoSans/NotoSans-Italic.woff2 +0 -0
  234. package/public/webfonts/NotoSans/NotoSans-Light.woff +0 -0
  235. package/public/webfonts/NotoSans/NotoSans-Light.woff2 +0 -0
  236. package/public/webfonts/NotoSans/NotoSans-LightItalic.woff +0 -0
  237. package/public/webfonts/NotoSans/NotoSans-LightItalic.woff2 +0 -0
  238. package/public/webfonts/NotoSans/NotoSans-Medium.woff +0 -0
  239. package/public/webfonts/NotoSans/NotoSans-Medium.woff2 +0 -0
  240. package/public/webfonts/NotoSans/NotoSans-MediumItalic.woff +0 -0
  241. package/public/webfonts/NotoSans/NotoSans-MediumItalic.woff2 +0 -0
  242. package/public/webfonts/NotoSans/NotoSans-Regular.woff +0 -0
  243. package/public/webfonts/NotoSans/NotoSans-Regular.woff2 +0 -0
  244. package/public/webfonts/NotoSans/NotoSans-SemiBold.woff +0 -0
  245. package/public/webfonts/NotoSans/NotoSans-SemiBold.woff2 +0 -0
  246. package/public/webfonts/NotoSans/NotoSans-SemiBoldItalic.woff +0 -0
  247. package/public/webfonts/NotoSans/NotoSans-SemiBoldItalic.woff2 +0 -0
  248. package/public/webfonts/NotoSans/NotoSans-Thin.woff +0 -0
  249. package/public/webfonts/NotoSans/NotoSans-Thin.woff2 +0 -0
  250. package/public/webfonts/NotoSans/NotoSans-ThinItalic.woff +0 -0
  251. package/public/webfonts/NotoSans/NotoSans-ThinItalic.woff2 +0 -0
  252. package/public/webfonts/NotoSans/stylesheet.css +162 -0
  253. package/public/webfonts/fa-solid-900.ttf +0 -0
  254. package/public/webfonts/fa-solid-900.woff2 +0 -0
  255. package/readme.md +187 -0
  256. package/server.js +2593 -0
  257. package/start.sh +24 -0
  258. package/tools/charaverter/main.mjs +110 -0
  259. package/tools/charaverter/package.json +10 -0
package/server.js ADDED
@@ -0,0 +1,2593 @@
1
+ const express = require('express');
2
+ const compression = require('compression');
3
+ const app = express();
4
+ app.use(compression());
5
+
6
+ const fs = require('fs');
7
+ const readline = require('readline');
8
+ const open = require('open');
9
+
10
+ const rimraf = require("rimraf");
11
+ const multer = require("multer");
12
+ const https = require('https');
13
+ //const PNG = require('pngjs').PNG;
14
+ const extract = require('png-chunks-extract');
15
+ const encode = require('png-chunks-encode');
16
+ const PNGtext = require('png-chunk-text');
17
+
18
+ const jimp = require('jimp');
19
+ const path = require('path');
20
+ const sanitize = require('sanitize-filename');
21
+ const mime = require('mime-types');
22
+
23
+ const cookieParser = require('cookie-parser');
24
+ const crypto = require('crypto');
25
+ const ipaddr = require('ipaddr.js');
26
+ const json5 = require('json5');
27
+
28
+ const ExifReader = require('exifreader');
29
+ const exif = require('piexifjs');
30
+ const webp = require('webp-converter');
31
+
32
+ const config = require(path.join(process.cwd(), './config.conf'));
33
+ const server_port = process.env.SILLY_TAVERN_PORT || config.port;
34
+
35
+ const whitelistPath = path.join(process.cwd(), "./whitelist.txt");
36
+ let whitelist = config.whitelist;
37
+
38
+ if (fs.existsSync(whitelistPath)) {
39
+ try {
40
+ let whitelistTxt = fs.readFileSync(whitelistPath, 'utf-8');
41
+ whitelist = whitelistTxt.split("\n").filter(ip => ip).map(ip => ip.trim());
42
+ } catch (e) { }
43
+ }
44
+
45
+ const whitelistMode = config.whitelistMode;
46
+ const autorun = config.autorun;
47
+ const enableExtensions = config.enableExtensions;
48
+ const listen = config.listen;
49
+
50
+ const axios = require('axios');
51
+ const tiktoken = require('@dqbd/tiktoken');
52
+ const WebSocket = require('ws');
53
+
54
+ var Client = require('node-rest-client').Client;
55
+ var client = new Client();
56
+
57
+ client.on('error', (err) => {
58
+ console.error('An error occurred:', err);
59
+ });
60
+
61
+ let poe = require('./poe-client');
62
+
63
+ var api_server = "http://0.0.0.0:5000";
64
+ var api_novelai = "https://api.novelai.net";
65
+ let api_openai = "https://api.openai.com/v1";
66
+ var main_api = "kobold";
67
+
68
+ var response_get_story;
69
+ var response_generate;
70
+ var response_generate_novel;
71
+ var request_promt;
72
+ var response_promt;
73
+ var characters = {};
74
+ var character_i = 0;
75
+ var response_create;
76
+ var response_edit;
77
+ var response_dw_bg;
78
+ var response_getstatus;
79
+ var response_getstatus_novel;
80
+ var response_getlastversion;
81
+ var api_key_novel;
82
+
83
+ let response_generate_openai;
84
+ let response_getstatus_openai;
85
+ let api_key_openai;
86
+
87
+ //RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
88
+ //Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
89
+ //During testing, this performs the same as previous date.now() structure.
90
+ //It also does not break old characters/chats, as the code just uses whatever timestamp exists in the chat.
91
+ //New chats made with characters will use this new formatting.
92
+ //Useable variable is (( humanizedISO8601Datetime ))
93
+
94
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
95
+
96
+ function humanizedISO8601DateTime() {
97
+ let baseDate = new Date(Date.now());
98
+ let humanYear = baseDate.getFullYear();
99
+ let humanMonth = (baseDate.getMonth() + 1);
100
+ let humanDate = baseDate.getDate();
101
+ let humanHour = (baseDate.getHours() < 10 ? '0' : '') + baseDate.getHours();
102
+ let humanMinute = (baseDate.getMinutes() < 10 ? '0' : '') + baseDate.getMinutes();
103
+ let humanSecond = (baseDate.getSeconds() < 10 ? '0' : '') + baseDate.getSeconds();
104
+ let humanMillisecond = (baseDate.getMilliseconds() < 10 ? '0' : '') + baseDate.getMilliseconds();
105
+ let HumanizedDateTime = (humanYear + "-" + humanMonth + "-" + humanDate + " @" + humanHour + "h " + humanMinute + "m " + humanSecond + "s " + humanMillisecond + "ms");
106
+ return HumanizedDateTime;
107
+ };
108
+
109
+ var is_colab = process.env.colaburl !== undefined;
110
+ var charactersPath = 'public/characters/';
111
+ var chatsPath = 'public/chats/';
112
+ const jsonParser = express.json({ limit: '100mb' });
113
+ const urlencodedParser = express.urlencoded({ extended: true, limit: '100mb' });
114
+ const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
115
+ const directories = {
116
+ worlds: 'public/worlds/',
117
+ avatars: 'public/User Avatars',
118
+ groups: 'public/groups/',
119
+ groupChats: 'public/group chats',
120
+ chats: 'public/chats/',
121
+ characters: 'public/characters/',
122
+ backgrounds: 'public/backgrounds',
123
+ novelAI_Settings: 'public/NovelAI Settings',
124
+ koboldAI_Settings: 'public/KoboldAI Settings',
125
+ openAI_Settings: 'public/OpenAI Settings',
126
+ textGen_Settings: 'public/TextGen Settings',
127
+ thumbnails: 'thumbnails/',
128
+ thumbnailsBg: 'thumbnails/bg/',
129
+ thumbnailsAvatar: 'thumbnails/avatar/',
130
+ themes: 'public/themes',
131
+ extensions: 'public/scripts/extensions'
132
+ };
133
+
134
+ // CSRF Protection //
135
+ const doubleCsrf = require('csrf-csrf').doubleCsrf;
136
+
137
+ const CSRF_SECRET = crypto.randomBytes(8).toString('hex');
138
+ const COOKIES_SECRET = crypto.randomBytes(8).toString('hex');
139
+
140
+ const { invalidCsrfTokenError, generateToken, doubleCsrfProtection } = doubleCsrf({
141
+ getSecret: () => CSRF_SECRET,
142
+ cookieName: "X-CSRF-Token",
143
+ cookieOptions: {
144
+ httpOnly: true,
145
+ sameSite: "strict",
146
+ secure: false
147
+ },
148
+ size: 64,
149
+ getTokenFromRequest: (req) => req.headers["x-csrf-token"]
150
+ });
151
+
152
+
153
+
154
+ app.get("/csrf-token", (req, res) => {
155
+ res.json({
156
+ "token": generateToken(res)
157
+ });
158
+ });
159
+
160
+ app.use(cookieParser(COOKIES_SECRET));
161
+ app.use(doubleCsrfProtection);
162
+
163
+ // CORS Settings //
164
+ const cors = require('cors');
165
+ const CORS = cors({
166
+ origin: 'null',
167
+ methods: ['OPTIONS']
168
+ });
169
+
170
+ app.use(CORS);
171
+
172
+ app.use(function (req, res, next) { //Security
173
+ let clientIp = req.connection.remoteAddress;
174
+ let ip = ipaddr.parse(clientIp);
175
+ // Check if the IP address is IPv4-mapped IPv6 address
176
+ if (ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
177
+ const ipv4 = ip.toIPv4Address().toString();
178
+ clientIp = ipv4;
179
+ } else {
180
+ clientIp = ip;
181
+ clientIp = clientIp.toString();
182
+ }
183
+
184
+ //clientIp = req.connection.remoteAddress.split(':').pop();
185
+ if (whitelistMode === true && !whitelist.includes(clientIp)) {
186
+ 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');
187
+ 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.');
188
+ }
189
+ next();
190
+ });
191
+
192
+ app.use((req, res, next) => {
193
+ if (req.url.startsWith('/characters/') && is_colab && process.env.googledrive == 2) {
194
+
195
+ const filePath = path.join(charactersPath, decodeURIComponent(req.url.substr('/characters'.length)));
196
+ console.log('req.url: ' + req.url);
197
+ console.log(filePath);
198
+ fs.access(filePath, fs.constants.R_OK, (err) => {
199
+ if (!err) {
200
+ res.sendFile(filePath, { root: __dirname });
201
+ } else {
202
+ res.send('Character not found: ' + filePath);
203
+ //next();
204
+ }
205
+ });
206
+ } else {
207
+ next();
208
+ }
209
+ });
210
+
211
+ app.use(express.static(__dirname + "/public", { refresh: true }));
212
+
213
+ app.use('/backgrounds', (req, res) => {
214
+ const filePath = decodeURIComponent(path.join(process.cwd(), 'public/backgrounds', req.url.replace(/%20/g, ' ')));
215
+ fs.readFile(filePath, (err, data) => {
216
+ if (err) {
217
+ res.status(404).send('File not found');
218
+ return;
219
+ }
220
+ //res.contentType('image/jpeg');
221
+ res.send(data);
222
+ });
223
+ });
224
+
225
+ app.use('/characters', (req, res) => {
226
+ const filePath = decodeURIComponent(path.join(process.cwd(), charactersPath, req.url.replace(/%20/g, ' ')));
227
+ fs.readFile(filePath, (err, data) => {
228
+ if (err) {
229
+ res.status(404).send('File not found');
230
+ return;
231
+ }
232
+ res.send(data);
233
+ });
234
+ });
235
+ app.use(multer({ dest: "uploads" }).single("avatar"));
236
+ app.get("/", function (request, response) {
237
+ response.sendFile(__dirname + "/public/index.html");
238
+ });
239
+ app.get("/notes/*", function (request, response) {
240
+ response.sendFile(__dirname + "/public" + request.url + ".html");
241
+ });
242
+
243
+ //**************Kobold api
244
+ app.post("/generate", jsonParser, async function (request, response_generate = response) {
245
+ if (!request.body) return response_generate.sendStatus(400);
246
+ //console.log(request.body.prompt);
247
+ //const dataJson = JSON.parse(request.body);
248
+ request_promt = request.body.prompt;
249
+
250
+ //console.log(request.body);
251
+ var this_settings = {
252
+ prompt: request_promt,
253
+ use_story: false,
254
+ use_memory: false,
255
+ use_authors_note: false,
256
+ use_world_info: false,
257
+ max_context_length: request.body.max_context_length,
258
+ singleline: !!request.body.singleline,
259
+ //temperature: request.body.temperature,
260
+ //max_length: request.body.max_length
261
+ };
262
+
263
+ if (request.body.gui_settings == false) {
264
+ var sampler_order = [request.body.s1, request.body.s2, request.body.s3, request.body.s4, request.body.s5, request.body.s6, request.body.s7];
265
+ this_settings = {
266
+ prompt: request_promt,
267
+ use_story: false,
268
+ use_memory: false,
269
+ use_authors_note: false,
270
+ use_world_info: false,
271
+ max_context_length: request.body.max_context_length,
272
+ max_length: request.body.max_length,
273
+ rep_pen: request.body.rep_pen,
274
+ rep_pen_range: request.body.rep_pen_range,
275
+ rep_pen_slope: request.body.rep_pen_slope,
276
+ temperature: request.body.temperature,
277
+ tfs: request.body.tfs,
278
+ top_a: request.body.top_a,
279
+ top_k: request.body.top_k,
280
+ top_p: request.body.top_p,
281
+ typical: request.body.typical,
282
+ sampler_order: sampler_order,
283
+ singleline: !!request.body.singleline,
284
+ };
285
+ if (!!request.body.stop_sequence) {
286
+ this_settings['stop_sequence'] = request.body.stop_sequence;
287
+ }
288
+ }
289
+
290
+ console.log(this_settings);
291
+ var args = {
292
+ data: this_settings,
293
+ headers: { "Content-Type": "application/json" }
294
+ };
295
+
296
+ const MAX_RETRIES = 10;
297
+ const delayAmount = 3000;
298
+ for (let i = 0; i < MAX_RETRIES; i++) {
299
+ try {
300
+ const data = await postAsync(api_server + "/v1/generate", args);
301
+ console.log(data);
302
+ return response_generate.send(data);
303
+ }
304
+ catch (error) {
305
+ // data
306
+ console.log(error[0]);
307
+
308
+ // response
309
+ if (error[1]) {
310
+ switch (error[1].statusCode) {
311
+ case 503:
312
+ await delay(delayAmount);
313
+ break;
314
+ default:
315
+ return response_generate.send({ error: true });
316
+ }
317
+ } else {
318
+ return response_generate.send({ error: true });
319
+ }
320
+ }
321
+ }
322
+ });
323
+
324
+ function randomHash() {
325
+ const letters = 'abcdefghijklmnopqrstuvwxyz0123456789';
326
+ let result = '';
327
+ for (let i = 0; i < 9; i++) {
328
+ result += letters.charAt(Math.floor(Math.random() * letters.length));
329
+ }
330
+ return result;
331
+ }
332
+
333
+ function textGenProcessStartedHandler(websocket, content, session, prompt, fn_index) {
334
+ switch (content.msg) {
335
+ case "send_hash":
336
+ const send_hash = JSON.stringify({ "session_hash": session, "fn_index": fn_index });
337
+ websocket.send(send_hash);
338
+ break;
339
+ case "estimation":
340
+ break;
341
+ case "send_data":
342
+ const send_data = JSON.stringify({ "session_hash": session, "fn_index": fn_index, "data": prompt.data });
343
+ console.log(send_data);
344
+ websocket.send(send_data);
345
+ break;
346
+ case "process_starts":
347
+ break;
348
+ case "process_generating":
349
+ return { text: content.output.data[0], completed: false };
350
+ case "process_completed":
351
+ try {
352
+ return { text: content.output.data[0], completed: true };
353
+ }
354
+ catch {
355
+ return { text: '', completed: true };
356
+ }
357
+ }
358
+
359
+ return { text: '', completed: false };
360
+ }
361
+
362
+ //************** Text generation web UI
363
+ app.post("/generate_textgenerationwebui", jsonParser, async function (request, response_generate = response) {
364
+ if (!request.body) return response_generate.sendStatus(400);
365
+
366
+ console.log(request.body);
367
+
368
+ if (!!request.header('X-Response-Streaming')) {
369
+ const fn_index = Number(request.header('X-Gradio-Streaming-Function'));
370
+ let isStreamingStopped = false;
371
+ request.socket.on('close', function () {
372
+ isStreamingStopped = true;
373
+ });
374
+
375
+ response_generate.writeHead(200, {
376
+ 'Content-Type': 'text/plain;charset=utf-8',
377
+ 'Transfer-Encoding': 'chunked',
378
+ 'Cache-Control': 'no-transform',
379
+ });
380
+
381
+ async function* readWebsocket() {
382
+ const session = randomHash();
383
+ const url = new URL(api_server);
384
+ const websocket = new WebSocket(`ws://${url.host}/queue/join`, { perMessageDeflate: false });
385
+ let text = '';
386
+ let completed = false;
387
+
388
+ websocket.on('open', async function () {
389
+ console.log('websocket open');
390
+ });
391
+
392
+ websocket.on('error', (err) => {
393
+ console.error(err);
394
+ websocket.close();
395
+ });
396
+
397
+ websocket.on('close', (code, buffer) => {
398
+ const reason = new TextDecoder().decode(buffer)
399
+ console.log(reason);
400
+ });
401
+
402
+ websocket.on('message', async (message) => {
403
+ const content = json5.parse(message);
404
+ console.log(content);
405
+ let result = textGenProcessStartedHandler(websocket, content, session, request.body, fn_index);
406
+ text = result.text;
407
+ completed = result.completed;
408
+ });
409
+
410
+ while (true) {
411
+ if (isStreamingStopped) {
412
+ console.error('Streaming stopped by user. Closing websocket...');
413
+ websocket.close();
414
+ return null;
415
+ }
416
+
417
+ if (websocket.readyState == 0 || websocket.readyState == 1 || websocket.readyState == 2) {
418
+ await delay(50);
419
+ yield text;
420
+
421
+ if (completed || (!text && typeof text !== 'string')) {
422
+ websocket.close();
423
+ yield null;
424
+ break;
425
+ }
426
+ }
427
+ else {
428
+ break;
429
+ }
430
+ }
431
+
432
+ return null;
433
+ }
434
+
435
+ let result = JSON.parse(request.body.data)[0];
436
+ let prompt = result;
437
+ let stopping_strings = JSON.parse(request.body.data)[1].stopping_strings;
438
+
439
+ try {
440
+ for await (const text of readWebsocket()) {
441
+ if (text == null || typeof text !== 'string') {
442
+ break;
443
+ }
444
+
445
+ let newText = text.substring(result.length);
446
+
447
+ if (!newText) {
448
+ continue;
449
+ }
450
+
451
+ result = text;
452
+
453
+ const generatedText = result.substring(prompt.length);
454
+
455
+ response_generate.write(JSON.stringify({ delta: newText }) + '\n');
456
+
457
+ if (generatedText) {
458
+ for (const str of stopping_strings) {
459
+ if (generatedText.indexOf(str) !== -1) {
460
+ break;
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ finally {
467
+ response_generate.end();
468
+ }
469
+ }
470
+ else {
471
+ var args = {
472
+ data: request.body,
473
+ headers: { "Content-Type": "application/json" }
474
+ };
475
+ client.post(api_server + "/run/textgen", args, function (data, response) {
476
+ console.log("####", data);
477
+ if (response.statusCode == 200) {
478
+ console.log(data);
479
+ response_generate.send(data);
480
+ }
481
+ if (response.statusCode == 422) {
482
+ console.log('Validation error');
483
+ response_generate.send({ error: true });
484
+ }
485
+ if (response.statusCode == 501 || response.statusCode == 503 || response.statusCode == 507) {
486
+ console.log(data);
487
+ response_generate.send({ error: true });
488
+ }
489
+ }).on('error', function (err) {
490
+ console.log(err);
491
+ //console.log('something went wrong on the request', err.request.options);
492
+ response_generate.send({ error: true });
493
+ });
494
+ }
495
+ });
496
+
497
+
498
+ app.post("/savechat", jsonParser, function (request, response) {
499
+ var dir_name = String(request.body.avatar_url).replace('.png', '');
500
+ let chat_data = request.body.chat;
501
+ let jsonlData = chat_data.map(JSON.stringify).join('\n');
502
+ fs.writeFile(chatsPath + dir_name + "/" + request.body.file_name + '.jsonl', jsonlData, 'utf8', function (err) {
503
+ if (err) {
504
+ response.send(err);
505
+ return console.log(err);
506
+ } else {
507
+ response.send({ result: "ok" });
508
+ }
509
+ });
510
+
511
+ });
512
+ app.post("/getchat", jsonParser, function (request, response) {
513
+ var dir_name = String(request.body.avatar_url).replace('.png', '');
514
+
515
+ fs.stat(chatsPath + dir_name, function (err, stat) {
516
+
517
+ if (stat === undefined) { //if no chat dir for the character is found, make one with the character name
518
+
519
+ fs.mkdirSync(chatsPath + dir_name);
520
+ response.send({});
521
+ return;
522
+ } else {
523
+
524
+ if (err === null) { //if there is a dir, then read the requested file from the JSON call
525
+
526
+ fs.stat(chatsPath + dir_name + "/" + request.body.file_name + ".jsonl", function (err, stat) {
527
+
528
+ if (err === null) { //if no error (the file exists), read the file
529
+ if (stat !== undefined) {
530
+ fs.readFile(chatsPath + dir_name + "/" + request.body.file_name + ".jsonl", 'utf8', (err, data) => {
531
+ if (err) {
532
+ console.error(err);
533
+ response.send(err);
534
+ return;
535
+ }
536
+ //console.log(data);
537
+ const lines = data.split('\n');
538
+
539
+ // Iterate through the array of strings and parse each line as JSON
540
+ const jsonData = lines.map(json5.parse);
541
+ response.send(jsonData);
542
+ //console.log('read the requested file')
543
+
544
+ });
545
+ }
546
+ } else {
547
+ response.send({});
548
+ //return console.log(err);
549
+ return;
550
+ }
551
+ });
552
+ } else {
553
+ console.error(err);
554
+ response.send({});
555
+ return;
556
+ }
557
+ }
558
+ });
559
+
560
+
561
+ });
562
+ app.post("/getstatus", jsonParser, async function (request, response_getstatus = response) {
563
+ if (!request.body) return response_getstatus.sendStatus(400);
564
+ api_server = request.body.api_server;
565
+ main_api = request.body.main_api;
566
+ if (api_server.indexOf('localhost') != -1) {
567
+ api_server = api_server.replace('localhost', '127.0.0.1');
568
+ }
569
+ var args = {
570
+ headers: { "Content-Type": "application/json" }
571
+ };
572
+ var url = api_server + "/v1/model";
573
+ let version = '';
574
+ if (main_api == "textgenerationwebui") {
575
+ url = api_server;
576
+ args = {}
577
+ }
578
+ if (main_api == "kobold") {
579
+ try {
580
+ version = (await getAsync(api_server + "/v1/info/version")).result;
581
+ }
582
+ catch {
583
+ version = '0.0.0';
584
+ }
585
+ }
586
+ client.get(url, args, function (data, response) {
587
+ if (response.statusCode == 200) {
588
+ if (main_api == "textgenerationwebui") {
589
+ // console.log(body);
590
+ try {
591
+ var body = data.toString();
592
+ var response = body.match(/gradio_config[ =]*(\{.*\});/)[1];
593
+ if (!response)
594
+ throw "no_connection";
595
+ let model = json5.parse(response).components.filter((x) => x.props.label == "Model" && x.type == "dropdown")[0].props.value;
596
+ data = { result: model, gradio_config: response };
597
+ if (!data)
598
+ throw "no_connection";
599
+ } catch {
600
+ data = { result: "no_connection" };
601
+ }
602
+ } else {
603
+ data.version = version;
604
+ if (data.result != "ReadOnly") {
605
+ } else {
606
+ data.result = "no_connection";
607
+ }
608
+ }
609
+ } else {
610
+ data.result = "no_connection";
611
+ }
612
+ response_getstatus.send(data);
613
+ }).on('error', function (err) {
614
+ //console.log(url);
615
+ //console.log('something went wrong on the request', err.request.options);
616
+ response_getstatus.send({ result: "no_connection" });
617
+ });
618
+ });
619
+
620
+ const formatApiUrl = (url) => (url.indexOf('localhost') !== -1)
621
+ ? url.replace('localhost', '127.0.0.1')
622
+ : url;
623
+
624
+ app.post('/getsoftprompts', jsonParser, async function (request, response) {
625
+ if (!request.body || !request.body.api_server) {
626
+ return response.sendStatus(400);
627
+ }
628
+
629
+ const baseUrl = formatApiUrl(request.body.api_server);
630
+ let soft_prompts = [];
631
+
632
+ try {
633
+ const softPromptsList = (await getAsync(`${baseUrl}/v1/config/soft_prompts_list`, baseRequestArgs)).values.map(x => x.value);
634
+ const softPromptSelected = (await getAsync(`${baseUrl}/v1/config/soft_prompt`, baseRequestArgs)).value;
635
+ soft_prompts = softPromptsList.map(x => ({ name: x, selected: x === softPromptSelected }));
636
+ } catch (err) {
637
+ soft_prompts = [];
638
+ }
639
+
640
+ return response.send({ soft_prompts });
641
+ });
642
+
643
+ app.post("/setsoftprompt", jsonParser, async function (request, response) {
644
+ if (!request.body || !request.body.api_server) {
645
+ return response.sendStatus(400);
646
+ }
647
+
648
+ const baseUrl = formatApiUrl(request.body.api_server);
649
+ const args = {
650
+ headers: { "Content-Type": "application/json" },
651
+ data: { value: request.body.name ?? '' },
652
+ };
653
+
654
+ try {
655
+ await putAsync(`${baseUrl}/v1/config/soft_prompt`, args);
656
+ } catch {
657
+ return response.sendStatus(500);
658
+ }
659
+
660
+ return response.sendStatus(200);
661
+ });
662
+
663
+ function checkServer() {
664
+ api_server = 'http://127.0.0.1:5000';
665
+ var args = {
666
+ headers: { "Content-Type": "application/json" }
667
+ };
668
+ client.get(api_server + "/v1/model", args, function (data, response) {
669
+ console.log(data.result);
670
+ console.log(data);
671
+ }).on('error', function (err) {
672
+ console.log(err);
673
+ });
674
+ }
675
+
676
+ //***************** Main functions
677
+ function charaFormatData(data) {
678
+ 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 };
679
+ return char;
680
+ }
681
+ app.post("/createcharacter", urlencodedParser, function (request, response) {
682
+ //var sameNameChar = fs.existsSync(charactersPath+request.body.ch_name+'.png');
683
+ //if (sameNameChar == true) return response.sendStatus(500);
684
+ if (!request.body) return response.sendStatus(400);
685
+
686
+ request.body.ch_name = sanitize(request.body.ch_name);
687
+
688
+ console.log('/createcharacter -- looking for -- ' + (charactersPath + request.body.ch_name + '.png'));
689
+ console.log('Does this file already exists? ' + fs.existsSync(charactersPath + request.body.ch_name + '.png'));
690
+ if (!fs.existsSync(charactersPath + request.body.ch_name + '.png')) {
691
+ if (!fs.existsSync(chatsPath + request.body.ch_name)) fs.mkdirSync(chatsPath + request.body.ch_name);
692
+ let filedata = request.file;
693
+ //console.log(filedata.mimetype);
694
+ var fileType = ".png";
695
+ var img_file = "ai";
696
+ var img_path = "public/img/";
697
+ 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": 'none', "chat": Date.now(), "last_mes": '', "mes_example": ''};
698
+ char = JSON.stringify(char);
699
+ if (!filedata) {
700
+ charaWrite('./public/img/ai4.png', char, request.body.ch_name, response);
701
+ } else {
702
+
703
+ img_path = "./uploads/";
704
+ img_file = filedata.filename
705
+ if (filedata.mimetype == "image/jpeg") fileType = ".jpeg";
706
+ if (filedata.mimetype == "image/png") fileType = ".png";
707
+ if (filedata.mimetype == "image/gif") fileType = ".gif";
708
+ if (filedata.mimetype == "image/bmp") fileType = ".bmp";
709
+ charaWrite(img_path + img_file, char, request.body.ch_name, response);
710
+ }
711
+ //console.log("The file was saved.");
712
+
713
+ } else {
714
+ console.error("Error: Cannot save file. A character with that name already exists.");
715
+ response.send("Error: A character with that name already exists.");
716
+ //response.send({error: true});
717
+ }
718
+ });
719
+
720
+
721
+ app.post("/editcharacter", urlencodedParser, async function (request, response) {
722
+ if (!request.body) return response.sendStatus(400);
723
+ let filedata = request.file;
724
+ //console.log(filedata.mimetype);
725
+ var fileType = ".png";
726
+ var img_file = "ai";
727
+ var img_path = charactersPath;
728
+
729
+ 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": ''};
730
+ char.chat = request.body.chat;
731
+ char.create_date = request.body.create_date;
732
+
733
+ char = JSON.stringify(char);
734
+ let target_img = (request.body.avatar_url).replace('.png', '');
735
+
736
+ try {
737
+ if (!filedata) {
738
+
739
+ await charaWrite(img_path + request.body.avatar_url, char, target_img, response, 'Character saved');
740
+ } else {
741
+ //console.log(filedata.filename);
742
+ img_path = "uploads/";
743
+ img_file = filedata.filename;
744
+
745
+ invalidateThumbnail('avatar', request.body.avatar_url);
746
+ await charaWrite(img_path + img_file, char, target_img, response, 'Character saved');
747
+ //response.send('Character saved');
748
+ }
749
+ }
750
+ catch {
751
+ return response.send(400);
752
+ }
753
+ });
754
+ app.post("/deletecharacter", urlencodedParser, function (request, response) {
755
+ if (!request.body || !request.body.avatar_url) {
756
+ return response.sendStatus(400);
757
+ }
758
+
759
+ if (request.body.avatar_url !== sanitize(request.body.avatar_url)) {
760
+ console.error('Malicious filename prevented');
761
+ return response.sendStatus(403);
762
+ }
763
+
764
+ const avatarPath = charactersPath + request.body.avatar_url;
765
+ if (!fs.existsSync(avatarPath)) {
766
+ return response.sendStatus(400);
767
+ }
768
+
769
+ fs.rmSync(avatarPath);
770
+ invalidateThumbnail('avatar', request.body.avatar_url);
771
+ let dir_name = (request.body.avatar_url.replace('.png', ''));
772
+
773
+ if (!dir_name.length) {
774
+ console.error('Malicious dirname prevented');
775
+ return response.sendStatus(403);
776
+ }
777
+
778
+ rimraf(path.join(chatsPath, sanitize(dir_name)), (err) => {
779
+ if (err) {
780
+ response.send(err);
781
+ return console.log(err);
782
+ } else {
783
+ //response.redirect("/");
784
+
785
+ response.send('ok');
786
+ }
787
+ });
788
+ });
789
+
790
+ async function charaWrite(img_url, data, target_img, response = undefined, mes = 'ok') {
791
+ try {
792
+ // Read the image, resize, and save it as a PNG into the buffer
793
+ const rawImg = await jimp.read(img_url);
794
+ const image = await rawImg.cover(400, 600).getBufferAsync(jimp.MIME_PNG);
795
+
796
+ // Get the chunks
797
+ const chunks = extract(image);
798
+ const tEXtChunks = chunks.filter(chunk => chunk.create_date === 'tEXt');
799
+
800
+ // Remove all existing tEXt chunks
801
+ for (let tEXtChunk of tEXtChunks) {
802
+ chunks.splice(chunks.indexOf(tEXtChunk), 1);
803
+ }
804
+ // Add new chunks before the IEND chunk
805
+ const base64EncodedData = Buffer.from(data, 'utf8').toString('base64');
806
+ chunks.splice(-1, 0, PNGtext.encode('chara', base64EncodedData));
807
+ //chunks.splice(-1, 0, text.encode('lorem', 'ipsum'));
808
+
809
+ fs.writeFileSync(charactersPath + target_img + '.png', new Buffer.from(encode(chunks)));
810
+ if (response !== undefined) response.send(mes);
811
+
812
+
813
+ } catch (err) {
814
+ console.log(err);
815
+ if (response !== undefined) response.send(err);
816
+ }
817
+ }
818
+
819
+ async function charaRead(img_url, input_format) {
820
+ let format;
821
+ if (input_format === undefined) {
822
+ if (img_url.indexOf('.webp') !== -1) {
823
+ format = 'webp';
824
+ } else {
825
+ format = 'png';
826
+ }
827
+ } else {
828
+ format = input_format;
829
+ }
830
+
831
+ switch (format) {
832
+ case 'webp':
833
+ const exif_data = await ExifReader.load(fs.readFileSync(img_url));
834
+ const char_data = exif_data['UserComment']['description'];
835
+ if (char_data === 'Undefined' && exif_data['UserComment'].value && exif_data['UserComment'].value.length === 1) {
836
+ return exif_data['UserComment'].value[0];
837
+ }
838
+ return char_data;
839
+ case 'png':
840
+ const buffer = fs.readFileSync(img_url);
841
+ const chunks = extract(buffer);
842
+
843
+ const textChunks = chunks.filter(function (chunk) {
844
+ return chunk.name === 'tEXt';
845
+ }).map(function (chunk) {
846
+ return PNGtext.decode(chunk.data);
847
+ });
848
+ var base64DecodedData = Buffer.from(textChunks[0].text, 'base64').toString('utf8');
849
+ return base64DecodedData;//textChunks[0].text;
850
+ default:
851
+ break;
852
+ }
853
+ }
854
+
855
+ app.post("/getcharacters", jsonParser, function (request, response) {
856
+ fs.readdir(charactersPath, async (err, files) => {
857
+ if (err) {
858
+ console.error(err);
859
+ return;
860
+ }
861
+
862
+ const pngFiles = files.filter(file => file.endsWith('.png'));
863
+
864
+ //console.log(pngFiles);
865
+ characters = {};
866
+ var i = 0;
867
+ for (const item of pngFiles) {
868
+ try {
869
+ var img_data = await charaRead(charactersPath + item);
870
+ let jsonObject = json5.parse(img_data);
871
+ jsonObject.avatar = item;
872
+ //console.log(jsonObject);
873
+ characters[i] = {};
874
+ characters[i] = jsonObject;
875
+
876
+ try {
877
+ const charStat = fs.statSync(path.join(charactersPath, item));
878
+ characters[i]['date_added'] = charStat.birthtimeMs;
879
+ const char_dir = path.join(chatsPath, item.replace('.png', ''));
880
+
881
+ let chat_size = 0;
882
+ let date_last_chat = 0;
883
+
884
+ if (fs.existsSync(char_dir)) {
885
+ const chats = fs.readdirSync(char_dir);
886
+
887
+ if (Array.isArray(chats) && chats.length) {
888
+ for (const chat of chats) {
889
+ const chatStat = fs.statSync(path.join(char_dir, chat));
890
+ chat_size += chatStat.size;
891
+ date_last_chat = Math.max(date_last_chat, chatStat.mtimeMs);
892
+ }
893
+ }
894
+ }
895
+
896
+ characters[i]['date_last_chat'] = date_last_chat;
897
+ characters[i]['chat_size'] = chat_size;
898
+ }
899
+ catch {
900
+ characters[i]['date_added'] = 0;
901
+ characters[i]['date_last_chat'] = 0;
902
+ characters[i]['chat_size'] = 0;
903
+ }
904
+
905
+ i++;
906
+ } catch (error) {
907
+ console.log(`Could not read character: ${item}`);
908
+ if (error instanceof SyntaxError) {
909
+ console.log("String [" + (i) + "] is not valid JSON!");
910
+ } else {
911
+ console.log("An unexpected error occurred: ", error);
912
+ }
913
+ }
914
+ };
915
+ //console.log(characters);
916
+ response.send(JSON.stringify(characters));
917
+ });
918
+
919
+ });
920
+ app.post("/getbackgrounds", jsonParser, function (request, response) {
921
+ var images = getImages("public/backgrounds");
922
+ response.send(JSON.stringify(images));
923
+
924
+ });
925
+ app.post("/iscolab", jsonParser, function (request, response) {
926
+ let send_data = false;
927
+ if (is_colab) {
928
+ send_data = String(process.env.colaburl).trim();
929
+ }
930
+ response.send({ colaburl: send_data });
931
+
932
+ });
933
+ app.post("/getuseravatars", jsonParser, function (request, response) {
934
+ var images = getImages("public/User Avatars");
935
+ response.send(JSON.stringify(images));
936
+
937
+ });
938
+ app.post("/setbackground", jsonParser, function (request, response) {
939
+ var bg = "#bg1 {background-image: url(../backgrounds/" + request.body.bg + ");}";
940
+ fs.writeFile('public/css/bg_load.css', bg, 'utf8', function (err) {
941
+ if (err) {
942
+ response.send(err);
943
+ return console.log(err);
944
+ } else {
945
+ //response.redirect("/");
946
+ response.send({ result: 'ok' });
947
+ }
948
+ });
949
+
950
+ });
951
+ app.post("/delbackground", jsonParser, function (request, response) {
952
+ if (!request.body) return response.sendStatus(400);
953
+
954
+ if (request.body.bg !== sanitize(request.body.bg)) {
955
+ console.error('Malicious bg name prevented');
956
+ return response.sendStatus(403);
957
+ }
958
+
959
+ const fileName = path.join('public/backgrounds/', sanitize(request.body.bg));
960
+
961
+ if (!fs.existsSync(fileName)) {
962
+ console.log('BG file not found');
963
+ return response.sendStatus(400);
964
+ }
965
+
966
+ fs.rmSync(fileName);
967
+ invalidateThumbnail('bg', request.body.bg);
968
+ return response.send('ok');
969
+ });
970
+
971
+ app.post("/delchat", jsonParser, function (request, response) {
972
+ console.log('/delchat entered');
973
+ if (!request.body) {
974
+ console.log('no request body seen');
975
+ return response.sendStatus(400);
976
+ }
977
+
978
+ if (request.body.chatfile !== sanitize(request.body.chatfile)) {
979
+ console.error('Malicious chat name prevented');
980
+ return response.sendStatus(403);
981
+ }
982
+
983
+ const fileName = path.join(directories.chats, '/', sanitize(request.body.id), '/', sanitize(request.body.chatfile));
984
+ if (!fs.existsSync(fileName)) {
985
+ console.log('Chat file not found');
986
+ return response.sendStatus(400);
987
+ } else {
988
+ console.log('found the chat file: ' + fileName);
989
+ /* fs.unlinkSync(fileName); */
990
+ fs.rmSync(fileName);
991
+ console.log('deleted chat file: ' + fileName);
992
+
993
+ }
994
+
995
+
996
+ return response.send('ok');
997
+ });
998
+
999
+
1000
+ app.post("/downloadbackground", urlencodedParser, function (request, response) {
1001
+ response_dw_bg = response;
1002
+ if (!request.body) return response.sendStatus(400);
1003
+
1004
+ let filedata = request.file;
1005
+ //console.log(filedata.mimetype);
1006
+ var fileType = ".png";
1007
+ var img_file = "ai";
1008
+ var img_path = "public/img/";
1009
+
1010
+ img_path = "uploads/";
1011
+ img_file = filedata.filename;
1012
+ if (filedata.mimetype == "image/jpeg") fileType = ".jpeg";
1013
+ if (filedata.mimetype == "image/png") fileType = ".png";
1014
+ if (filedata.mimetype == "image/gif") fileType = ".gif";
1015
+ if (filedata.mimetype == "image/bmp") fileType = ".bmp";
1016
+ fs.copyFile(img_path + img_file, 'public/backgrounds/' + img_file + fileType, (err) => {
1017
+ invalidateThumbnail('bg', img_file + fileType);
1018
+ if (err) {
1019
+
1020
+ return console.log(err);
1021
+ } else {
1022
+ //console.log(img_file+fileType);
1023
+ response_dw_bg.send(img_file + fileType);
1024
+ }
1025
+ //console.log('The image was copied from temp directory.');
1026
+ });
1027
+
1028
+
1029
+ });
1030
+
1031
+ app.post("/savesettings", jsonParser, function (request, response) {
1032
+ fs.writeFile('public/settings.json', JSON.stringify(request.body), 'utf8', function (err) {
1033
+ if (err) {
1034
+ response.send(err);
1035
+ return console.log(err);
1036
+ //response.send(err);
1037
+ } else {
1038
+ //response.redirect("/");
1039
+ response.send({ result: "ok" });
1040
+ }
1041
+ });
1042
+ });
1043
+
1044
+ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's code
1045
+ const koboldai_settings = [];
1046
+ const koboldai_setting_names = [];
1047
+ const novelai_settings = [];
1048
+ const novelai_setting_names = [];
1049
+ const openai_settings = [];
1050
+ const openai_setting_names = [];
1051
+ const textgenerationwebui_presets = [];
1052
+ const textgenerationwebui_preset_names = [];
1053
+ const themes = [];
1054
+ const settings = fs.readFileSync('public/settings.json', 'utf8', (err, data) => {
1055
+ if (err) return response.sendStatus(500);
1056
+
1057
+ return data;
1058
+ });
1059
+ //Kobold
1060
+ const files = fs
1061
+ .readdirSync('public/KoboldAI Settings')
1062
+ .sort(
1063
+ (a, b) =>
1064
+ new Date(fs.statSync(`public/KoboldAI Settings/${b}`).mtime) -
1065
+ new Date(fs.statSync(`public/KoboldAI Settings/${a}`).mtime)
1066
+ );
1067
+
1068
+ const worldFiles = fs
1069
+ .readdirSync(directories.worlds)
1070
+ .filter(file => path.extname(file).toLowerCase() === '.json')
1071
+ .sort((a, b) => a < b);
1072
+ const world_names = worldFiles.map(item => path.parse(item).name);
1073
+
1074
+ files.forEach(item => {
1075
+ const file = fs.readFileSync(
1076
+ `public/KoboldAI Settings/${item}`,
1077
+ 'utf8',
1078
+ (err, data) => {
1079
+ if (err) return response.sendStatus(500)
1080
+
1081
+ return data;
1082
+ }
1083
+ );
1084
+ koboldai_settings.push(file);
1085
+ koboldai_setting_names.push(item.replace(/\.[^/.]+$/, ''));
1086
+ });
1087
+
1088
+ //Novel
1089
+ const files2 = fs
1090
+ .readdirSync('public/NovelAI Settings')
1091
+ .sort(
1092
+ (a, b) =>
1093
+ new Date(fs.statSync(`public/NovelAI Settings/${b}`).mtime) -
1094
+ new Date(fs.statSync(`public/NovelAI Settings/${a}`).mtime)
1095
+ );
1096
+
1097
+ files2.forEach(item => {
1098
+ const file2 = fs.readFileSync(
1099
+ `public/NovelAI Settings/${item}`,
1100
+ 'utf8',
1101
+ (err, data) => {
1102
+ if (err) return response.sendStatus(500);
1103
+
1104
+ return data;
1105
+ }
1106
+ );
1107
+
1108
+ novelai_settings.push(file2);
1109
+ novelai_setting_names.push(item.replace(/\.[^/.]+$/, ''));
1110
+ });
1111
+
1112
+ //OpenAI
1113
+ const files3 = fs
1114
+ .readdirSync('public/OpenAI Settings')
1115
+ .sort(
1116
+ (a, b) =>
1117
+ new Date(fs.statSync(`public/OpenAI Settings/${b}`).mtime) -
1118
+ new Date(fs.statSync(`public/OpenAI Settings/${a}`).mtime)
1119
+ );
1120
+
1121
+ files3.forEach(item => {
1122
+ const file3 = fs.readFileSync(
1123
+ `public/OpenAI Settings/${item}`,
1124
+ 'utf8',
1125
+ (err, data) => {
1126
+ if (err) return response.sendStatus(500);
1127
+
1128
+ return data;
1129
+ }
1130
+ );
1131
+
1132
+ openai_settings.push(file3);
1133
+ openai_setting_names.push(item.replace(/\.[^/.]+$/, ''));
1134
+ });
1135
+
1136
+ // TextGenerationWebUI
1137
+ const textGenFiles = fs
1138
+ .readdirSync(directories.textGen_Settings)
1139
+ .sort();
1140
+
1141
+ textGenFiles.forEach(item => {
1142
+ const file = fs.readFileSync(
1143
+ path.join(directories.textGen_Settings, item),
1144
+ 'utf8',
1145
+ (err, data) => {
1146
+ if (err) return response.sendStatus(500);
1147
+
1148
+ return data;
1149
+ }
1150
+ );
1151
+
1152
+ textgenerationwebui_presets.push(file);
1153
+ textgenerationwebui_preset_names.push(item.replace(/\.[^/.]+$/, ''));
1154
+ });
1155
+
1156
+ // Theme files
1157
+ const themeFiles = fs
1158
+ .readdirSync(directories.themes)
1159
+ .filter(x => path.parse(x).ext == '.json')
1160
+ .sort();
1161
+
1162
+ themeFiles.forEach(item => {
1163
+ const file = fs.readFileSync(
1164
+ path.join(directories.themes, item),
1165
+ 'utf-8',
1166
+ (err, data) => {
1167
+ if (err) return response.sendStatus(500);
1168
+ return data;
1169
+ }
1170
+ );
1171
+
1172
+ try {
1173
+ themes.push(json5.parse(file));
1174
+ }
1175
+ catch {
1176
+ // skip
1177
+ }
1178
+ })
1179
+
1180
+ response.send({
1181
+ settings,
1182
+ koboldai_settings,
1183
+ koboldai_setting_names,
1184
+ world_names,
1185
+ novelai_settings,
1186
+ novelai_setting_names,
1187
+ openai_settings,
1188
+ openai_setting_names,
1189
+ textgenerationwebui_presets,
1190
+ textgenerationwebui_preset_names,
1191
+ themes,
1192
+ enable_extensions: enableExtensions,
1193
+ });
1194
+ });
1195
+
1196
+ app.post('/getworldinfo', jsonParser, (request, response) => {
1197
+ if (!request.body?.name) {
1198
+ return response.sendStatus(400);
1199
+ }
1200
+
1201
+ const file = readWorldInfoFile(request.body.name);
1202
+
1203
+ return response.send(file);
1204
+ });
1205
+
1206
+ app.post('/deleteworldinfo', jsonParser, (request, response) => {
1207
+ if (!request.body?.name) {
1208
+ return response.sendStatus(400);
1209
+ }
1210
+
1211
+ const worldInfoName = request.body.name;
1212
+ const filename = sanitize(`${worldInfoName}.json`);
1213
+ const pathToWorldInfo = path.join(directories.worlds, filename);
1214
+
1215
+ if (!fs.existsSync(pathToWorldInfo)) {
1216
+ throw new Error(`World info file ${filename} doesn't exist.`);
1217
+ }
1218
+
1219
+ fs.rmSync(pathToWorldInfo);
1220
+
1221
+ return response.sendStatus(200);
1222
+ });
1223
+
1224
+ app.post('/savetheme', jsonParser, (request, response) => {
1225
+ if (!request.body || !request.body.name) {
1226
+ return response.sendStatus(400);
1227
+ }
1228
+
1229
+ const filename = path.join(directories.themes, sanitize(request.body.name) + '.json');
1230
+ fs.writeFileSync(filename, JSON.stringify(request.body), 'utf8');
1231
+
1232
+ return response.sendStatus(200);
1233
+ });
1234
+
1235
+ function readWorldInfoFile(worldInfoName) {
1236
+ if (!worldInfoName) {
1237
+ return { entries: {} };
1238
+ }
1239
+
1240
+ const filename = `${worldInfoName}.json`;
1241
+ const pathToWorldInfo = path.join(directories.worlds, filename);
1242
+
1243
+ if (!fs.existsSync(pathToWorldInfo)) {
1244
+ throw new Error(`World info file ${filename} doesn't exist.`);
1245
+ }
1246
+
1247
+ const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8');
1248
+ const worldInfo = json5.parse(worldInfoText);
1249
+ return worldInfo;
1250
+ }
1251
+
1252
+
1253
+ function getImages(path) {
1254
+ return fs
1255
+ .readdirSync(path)
1256
+ .filter(file => {
1257
+ const type = mime.lookup(file);
1258
+ return type && type.startsWith('image/');
1259
+ })
1260
+ .sort(Intl.Collator().compare);
1261
+ }
1262
+
1263
+ //***********Novel.ai API
1264
+
1265
+ app.post("/getstatus_novelai", jsonParser, function (request, response_getstatus_novel = response) {
1266
+
1267
+ if (!request.body) return response_getstatus_novel.sendStatus(400);
1268
+ api_key_novel = request.body.key;
1269
+ var data = {};
1270
+ var args = {
1271
+ data: data,
1272
+
1273
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + api_key_novel }
1274
+ };
1275
+ client.get(api_novelai + "/user/subscription", args, function (data, response) {
1276
+ if (response.statusCode == 200) {
1277
+ //console.log(data);
1278
+ response_getstatus_novel.send(data);//data);
1279
+ }
1280
+ if (response.statusCode == 401) {
1281
+ console.log('Access Token is incorrect.');
1282
+ response_getstatus_novel.send({ error: true });
1283
+ }
1284
+ if (response.statusCode == 500 || response.statusCode == 501 || response.statusCode == 501 || response.statusCode == 503 || response.statusCode == 507) {
1285
+ console.log(data);
1286
+ response_getstatus_novel.send({ error: true });
1287
+ }
1288
+ }).on('error', function (err) {
1289
+ //console.log('');
1290
+ //console.log('something went wrong on the request', err.request.options);
1291
+ response_getstatus_novel.send({ error: true });
1292
+ });
1293
+ });
1294
+
1295
+ app.post("/generate_novelai", jsonParser, function (request, response_generate_novel = response) {
1296
+ if (!request.body) return response_generate_novel.sendStatus(400);
1297
+
1298
+ console.log(request.body);
1299
+ var data = {
1300
+ "input": request.body.input,
1301
+ "model": request.body.model,
1302
+ "parameters": {
1303
+ "use_string": request.body.use_string,
1304
+ "temperature": request.body.temperature,
1305
+ "max_length": request.body.max_length,
1306
+ "min_length": request.body.min_length,
1307
+ "tail_free_sampling": request.body.tail_free_sampling,
1308
+ "repetition_penalty": request.body.repetition_penalty,
1309
+ "repetition_penalty_range": request.body.repetition_penalty_range,
1310
+ "repetition_penalty_frequency": request.body.repetition_penalty_frequency,
1311
+ "repetition_penalty_presence": request.body.repetition_penalty_presence,
1312
+ //"stop_sequences": {{187}},
1313
+ //bad_words_ids = {{50256}, {0}, {1}};
1314
+ //generate_until_sentence = true;
1315
+ "use_cache": request.body.use_cache,
1316
+ //use_string = true;
1317
+ "return_full_text": request.body.return_full_text,
1318
+ "prefix": request.body.prefix,
1319
+ "order": request.body.order
1320
+ }
1321
+ };
1322
+
1323
+ var args = {
1324
+ data: data,
1325
+
1326
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + api_key_novel }
1327
+ };
1328
+ client.post(api_novelai + "/ai/generate", args, function (data, response) {
1329
+ if (response.statusCode == 201) {
1330
+ console.log(data);
1331
+ response_generate_novel.send(data);
1332
+ }
1333
+ if (response.statusCode == 400) {
1334
+ console.log('Validation error');
1335
+ response_generate_novel.send({ error: true });
1336
+ }
1337
+ if (response.statusCode == 401) {
1338
+ console.log('Access Token is incorrect');
1339
+ response_generate_novel.send({ error: true });
1340
+ }
1341
+ if (response.statusCode == 402) {
1342
+ console.log('An active subscription is required to access this endpoint');
1343
+ response_generate_novel.send({ error: true });
1344
+ }
1345
+ if (response.statusCode == 500 || response.statusCode == 409) {
1346
+ console.log(data);
1347
+ response_generate_novel.send({ error: true });
1348
+ }
1349
+ }).on('error', function (err) {
1350
+ //console.log('');
1351
+ //console.log('something went wrong on the request', err.request.options);
1352
+ response_getstatus.send({ error: true });
1353
+ });
1354
+ });
1355
+
1356
+ app.post("/getallchatsofcharacter", jsonParser, function (request, response) {
1357
+ if (!request.body) return response.sendStatus(400);
1358
+
1359
+ var char_dir = (request.body.avatar_url).replace('.png', '')
1360
+ fs.readdir(chatsPath + char_dir, (err, files) => {
1361
+ if (err) {
1362
+ console.log('found error in history loading');
1363
+ console.error(err);
1364
+ response.send({ error: true });
1365
+ return;
1366
+ }
1367
+
1368
+ // filter for JSON files
1369
+ console.log('looking for JSONL files');
1370
+ const jsonFiles = files.filter(file => path.extname(file) === '.jsonl');
1371
+
1372
+ // sort the files by name
1373
+ //jsonFiles.sort().reverse();
1374
+ // print the sorted file names
1375
+ var chatData = {};
1376
+ let ii = jsonFiles.length; //this is the number of files belonging to the character
1377
+ if (ii !== 0) {
1378
+ //console.log('found '+ii+' chat logs to load');
1379
+ for (let i = jsonFiles.length - 1; i >= 0; i--) {
1380
+ const file = jsonFiles[i];
1381
+ const fileStream = fs.createReadStream(chatsPath + char_dir + '/' + file);
1382
+ const rl = readline.createInterface({
1383
+ input: fileStream,
1384
+ crlfDelay: Infinity
1385
+ });
1386
+
1387
+ let lastLine;
1388
+ rl.on('line', (line) => {
1389
+ lastLine = line;
1390
+ });
1391
+ rl.on('close', () => {
1392
+ if (lastLine) {
1393
+ let jsonData = json5.parse(lastLine);
1394
+ if (jsonData.name !== undefined) {
1395
+ chatData[i] = {};
1396
+ chatData[i]['file_name'] = file;
1397
+ chatData[i]['mes'] = jsonData['mes'];
1398
+ ii--;
1399
+ if (ii === 0) {
1400
+ console.log('ii count went to zero, responding with chatData');
1401
+ response.send(chatData);
1402
+ }
1403
+ } else {
1404
+ console.log('just returning from getallchatsofcharacter');
1405
+ return;
1406
+ }
1407
+ }
1408
+ console.log('successfully closing getallchatsofcharacter');
1409
+ rl.close();
1410
+ });
1411
+ };
1412
+ } else {
1413
+ //console.log('Found No Chats. Exiting Load Routine.');
1414
+ response.send({ error: true });
1415
+ };
1416
+ })
1417
+ });
1418
+
1419
+ function getPngName(file) {
1420
+ let i = 1;
1421
+ let base_name = file;
1422
+ while (fs.existsSync(charactersPath + file + '.png')) {
1423
+ file = base_name + i;
1424
+ i++;
1425
+ }
1426
+ return file;
1427
+ }
1428
+
1429
+ app.post("/importcharacter", urlencodedParser, async function (request, response) {
1430
+
1431
+ if (!request.body) return response.sendStatus(400);
1432
+
1433
+ let png_name = '';
1434
+ let filedata = request.file;
1435
+ let uploadPath = path.join('./uploads', filedata.filename);
1436
+ var format = request.body.file_type;
1437
+ //console.log(format);
1438
+ if (filedata) {
1439
+ if (format == 'json') {
1440
+ fs.readFile(uploadPath, 'utf8', async (err, data) => {
1441
+ if (err) {
1442
+ console.log(err);
1443
+ response.send({ error: true });
1444
+ }
1445
+ const jsonData = json5.parse(data);
1446
+
1447
+ if (jsonData.name !== undefined) {
1448
+ jsonData.name = sanitize(jsonData.name);
1449
+
1450
+ png_name = getPngName(jsonData.name);
1451
+ let char = { "name": jsonData.name, "description": jsonData.description ?? '', "personality": jsonData.personality ?? '', "first_mes": jsonData.first_mes ?? '', "avatar": 'none', "chat": humanizedISO8601DateTime(), "mes_example": jsonData.mes_example ?? '', "scenario": jsonData.scenario ?? '', "create_date": humanizedISO8601DateTime(), "talkativeness": jsonData.talkativeness ?? 0.5 };
1452
+ char = JSON.stringify(char);
1453
+ charaWrite('./public/img/ai4.png', char, png_name, response, { file_name: png_name });
1454
+ } else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
1455
+ jsonData.char_name = sanitize(jsonData.char_name);
1456
+
1457
+ png_name = getPngName(jsonData.char_name);
1458
+ let char = { "name": jsonData.char_name, "description": jsonData.char_persona ?? '', "personality": '', "first_mes": jsonData.char_greeting ?? '', "avatar": 'none', "chat": humanizedISO8601DateTime(), "mes_example": jsonData.example_dialogue ?? '', "scenario": jsonData.world_scenario ?? '', "create_date": humanizedISO8601DateTime(), "talkativeness": jsonData.talkativeness ?? 0.5 };
1459
+ char = JSON.stringify(char);
1460
+ charaWrite('./public/img/ai4.png', char, png_name, response, { file_name: png_name });
1461
+ } else {
1462
+ console.log('Incorrect character format .json');
1463
+ response.send({ error: true });
1464
+ }
1465
+ });
1466
+ } else {
1467
+ try {
1468
+ var img_data = await charaRead(uploadPath, format);
1469
+ let jsonData = json5.parse(img_data);
1470
+ jsonData.name = sanitize(jsonData.name);
1471
+
1472
+ if (format == 'webp') {
1473
+ let convertedPath = path.join('./uploads', path.basename(uploadPath, ".webp") + ".png")
1474
+ await webp.dwebp(uploadPath, convertedPath, "-o");
1475
+ uploadPath = convertedPath;
1476
+ }
1477
+
1478
+ png_name = getPngName(jsonData.name);
1479
+
1480
+ if (jsonData.name !== undefined) {
1481
+ let char = { "name": jsonData.name, "description": jsonData.description ?? '', "personality": jsonData.personality ?? '', "first_mes": jsonData.first_mes ?? '', "avatar": 'none', "chat": humanizedISO8601DateTime(), "mes_example": jsonData.mes_example ?? '', "scenario": jsonData.scenario ?? '', "create_date": humanizedISO8601DateTime(), "talkativeness": jsonData.talkativeness ?? 0.5 };
1482
+ char = JSON.stringify(char);
1483
+ await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
1484
+ }
1485
+ } catch (err) {
1486
+ console.log(err);
1487
+ response.send({ error: true });
1488
+ }
1489
+ }
1490
+ }
1491
+ });
1492
+
1493
+ app.post("/exportcharacter", jsonParser, async function (request, response) {
1494
+ if (!request.body.format || !request.body.avatar_url) {
1495
+ return response.sendStatus(400);
1496
+ }
1497
+
1498
+ let filename = path.join(directories.characters, sanitize(request.body.avatar_url));
1499
+
1500
+ if (!fs.existsSync(filename)) {
1501
+ return response.sendStatus(404);
1502
+ }
1503
+
1504
+ switch (request.body.format) {
1505
+ case 'png':
1506
+ return response.sendFile(filename, { root: __dirname });
1507
+ case 'json': {
1508
+ try {
1509
+ let json = await charaRead(filename);
1510
+ let jsonObject = json5.parse(json);
1511
+ return response.type('json').send(jsonObject)
1512
+ }
1513
+ catch {
1514
+ return response.sendStatus(400);
1515
+ }
1516
+ }
1517
+ case 'webp': {
1518
+ try {
1519
+ let json = await charaRead(filename);
1520
+ let inputWebpPath = `./uploads/${Date.now()}_input.webp`;
1521
+ let outputWebpPath = `./uploads/${Date.now()}_output.webp`;
1522
+ let metadataPath = `./uploads/${Date.now()}_metadata.exif`;
1523
+ let metadata =
1524
+ {
1525
+ "Exif": {
1526
+ [exif.ExifIFD.UserComment]: json,
1527
+ },
1528
+ };
1529
+ const exifString = exif.dump(metadata);
1530
+ fs.writeFileSync(metadataPath, exifString, 'binary');
1531
+
1532
+ await webp.cwebp(filename, inputWebpPath, '-q 95');
1533
+ await webp.webpmux_add(inputWebpPath, outputWebpPath, metadataPath, 'exif');
1534
+
1535
+ response.sendFile(outputWebpPath, { root: __dirname });
1536
+
1537
+ fs.rmSync(inputWebpPath);
1538
+ fs.rmSync(metadataPath);
1539
+
1540
+ return;
1541
+ }
1542
+ catch (err) {
1543
+ console.log(err);
1544
+ return response.sendStatus(400);
1545
+ }
1546
+ }
1547
+ }
1548
+
1549
+ return response.sendStatus(400);
1550
+ });
1551
+
1552
+
1553
+ app.post("/importchat", urlencodedParser, function (request, response) {
1554
+ if (!request.body) return response.sendStatus(400);
1555
+
1556
+ var format = request.body.file_type;
1557
+ let filedata = request.file;
1558
+ let avatar_url = (request.body.avatar_url).replace('.png', '');
1559
+ let ch_name = request.body.character_name;
1560
+ if (filedata) {
1561
+
1562
+ if (format === 'json') {
1563
+ fs.readFile('./uploads/' + filedata.filename, 'utf8', (err, data) => {
1564
+
1565
+ if (err) {
1566
+ console.log(err);
1567
+ response.send({ error: true });
1568
+ }
1569
+
1570
+ const jsonData = json5.parse(data);
1571
+ if (jsonData.histories !== undefined) {
1572
+ //console.log('/importchat confirms JSON histories are defined');
1573
+ const chat = {
1574
+ from(history) {
1575
+ return [
1576
+ {
1577
+ user_name: 'You',
1578
+ character_name: ch_name,
1579
+ create_date: humanizedISO8601DateTime(),
1580
+
1581
+ },
1582
+ ...history.msgs.map(
1583
+ (message) => ({
1584
+ name: message.src.is_human ? 'You' : ch_name,
1585
+ is_user: message.src.is_human,
1586
+ is_name: true,
1587
+ send_date: humanizedISO8601DateTime(),
1588
+ mes: message.text,
1589
+ })
1590
+ )];
1591
+ }
1592
+ }
1593
+
1594
+ const newChats = [];
1595
+ (jsonData.histories.histories ?? []).forEach((history) => {
1596
+ newChats.push(chat.from(history));
1597
+ });
1598
+
1599
+ const errors = [];
1600
+ newChats.forEach(chat => fs.writeFile(
1601
+ chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl',
1602
+ chat.map(JSON.stringify).join('\n'),
1603
+ 'utf8',
1604
+ (err) => err ?? errors.push(err)
1605
+ )
1606
+ );
1607
+
1608
+ if (0 < errors.length) {
1609
+ response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
1610
+ }
1611
+
1612
+ response.send({ res: true });
1613
+ } else {
1614
+ response.send({ error: true });
1615
+ }
1616
+ });
1617
+ }
1618
+ if (format === 'jsonl') {
1619
+ //console.log(humanizedISO8601DateTime()+':imported chat format is JSONL');
1620
+ const fileStream = fs.createReadStream('./uploads/' + filedata.filename);
1621
+ const rl = readline.createInterface({
1622
+ input: fileStream,
1623
+ crlfDelay: Infinity
1624
+ });
1625
+
1626
+ rl.once('line', (line) => {
1627
+ let jsonData = json5.parse(line);
1628
+
1629
+ if (jsonData.user_name !== undefined) {
1630
+ //console.log(humanizedISO8601DateTime()+':/importchat copying chat as '+ch_name+' - '+humanizedISO8601DateTime()+'.jsonl');
1631
+ fs.copyFile('./uploads/' + filedata.filename, chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + '.jsonl', (err) => { //added character name and replaced Date.now() with humanizedISO8601DateTime
1632
+ if (err) {
1633
+ response.send({ error: true });
1634
+ return console.log(err);
1635
+ } else {
1636
+ response.send({ res: true });
1637
+ return;
1638
+ }
1639
+ });
1640
+ } else {
1641
+ //response.send({error:true});
1642
+ return;
1643
+ }
1644
+ rl.close();
1645
+ });
1646
+ }
1647
+
1648
+ }
1649
+
1650
+ });
1651
+
1652
+ app.post('/importworldinfo', urlencodedParser, (request, response) => {
1653
+ if (!request.file) return response.sendStatus(400);
1654
+
1655
+ const filename = sanitize(request.file.originalname);
1656
+
1657
+ if (path.parse(filename).ext.toLowerCase() !== '.json') {
1658
+ return response.status(400).send('Only JSON files are supported.')
1659
+ }
1660
+
1661
+ const pathToUpload = path.join('./uploads/', request.file.filename);
1662
+ const fileContents = fs.readFileSync(pathToUpload, 'utf8');
1663
+
1664
+ try {
1665
+ const worldContent = json5.parse(fileContents);
1666
+ if (!('entries' in worldContent)) {
1667
+ throw new Error('File must contain a world info entries list');
1668
+ }
1669
+ } catch (err) {
1670
+ return response.status(400).send('Is not a valid world info file');
1671
+ }
1672
+
1673
+ const pathToNewFile = path.join(directories.worlds, filename);
1674
+ const worldName = path.parse(pathToNewFile).name;
1675
+
1676
+ if (!worldName) {
1677
+ return response.status(400).send('World file must have a name');
1678
+ }
1679
+
1680
+ fs.writeFileSync(pathToNewFile, fileContents);
1681
+ return response.send({ name: worldName });
1682
+ });
1683
+
1684
+ app.post('/editworldinfo', jsonParser, (request, response) => {
1685
+ if (!request.body) {
1686
+ return response.sendStatus(400);
1687
+ }
1688
+
1689
+ if (!request.body.name) {
1690
+ return response.status(400).send('World file must have a name');
1691
+ }
1692
+
1693
+ try {
1694
+ if (!('entries' in request.body.data)) {
1695
+ throw new Error('World info must contain an entries list');
1696
+ }
1697
+ } catch (err) {
1698
+ return response.status(400).send('Is not a valid world info file');
1699
+ }
1700
+
1701
+ const filename = `${request.body.name}.json`;
1702
+ const pathToFile = path.join(directories.worlds, filename);
1703
+
1704
+ fs.writeFileSync(pathToFile, JSON.stringify(request.body.data));
1705
+
1706
+ return response.send({ ok: true });
1707
+ });
1708
+
1709
+ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
1710
+ if (!request.file) return response.sendStatus(400);
1711
+
1712
+ try {
1713
+ const pathToUpload = path.join('./uploads/' + request.file.filename);
1714
+ const rawImg = await jimp.read(pathToUpload);
1715
+ const image = await rawImg.cover(400, 400).getBufferAsync(jimp.MIME_PNG);
1716
+
1717
+ const filename = `${Date.now()}.png`;
1718
+ const pathToNewFile = path.join(directories.avatars, filename);
1719
+ fs.writeFileSync(pathToNewFile, image);
1720
+ fs.rmSync(pathToUpload);
1721
+ return response.send({ path: filename });
1722
+ } catch (err) {
1723
+ return response.status(400).send('Is not a valid image');
1724
+ }
1725
+ });
1726
+
1727
+ app.post('/getgroups', jsonParser, (_, response) => {
1728
+ const groups = [];
1729
+
1730
+ if (!fs.existsSync(directories.groups)) {
1731
+ fs.mkdirSync(directories.groups);
1732
+ }
1733
+
1734
+ const files = fs.readdirSync(directories.groups);
1735
+ files.forEach(function (file) {
1736
+ const fileContents = fs.readFileSync(path.join(directories.groups, file), 'utf8');
1737
+ const group = json5.parse(fileContents);
1738
+ groups.push(group);
1739
+ });
1740
+
1741
+ return response.send(groups);
1742
+ });
1743
+
1744
+ app.post('/creategroup', jsonParser, (request, response) => {
1745
+ if (!request.body) {
1746
+ return response.sendStatus(400);
1747
+ }
1748
+
1749
+ const id = Date.now();
1750
+ const chatMetadata = {
1751
+ id: id,
1752
+ name: request.body.name ?? 'New Group',
1753
+ members: request.body.members ?? [],
1754
+ avatar_url: request.body.avatar_url,
1755
+ allow_self_responses: !!request.body.allow_self_responses,
1756
+ activation_strategy: request.body.activation_strategy ?? 0,
1757
+ chat_metadata: request.body.chat_metadata ?? {},
1758
+ };
1759
+ const pathToFile = path.join(directories.groups, `${id}.json`);
1760
+ const fileData = JSON.stringify(chatMetadata);
1761
+
1762
+ if (!fs.existsSync(directories.groups)) {
1763
+ fs.mkdirSync(directories.groups);
1764
+ }
1765
+
1766
+ fs.writeFileSync(pathToFile, fileData);
1767
+ return response.send(chatMetadata);
1768
+ });
1769
+
1770
+ app.post('/editgroup', jsonParser, (request, response) => {
1771
+ if (!request.body || !request.body.id) {
1772
+ return response.sendStatus(400);
1773
+ }
1774
+
1775
+ const id = request.body.id;
1776
+ const pathToFile = path.join(directories.groups, `${id}.json`);
1777
+ const fileData = JSON.stringify(request.body);
1778
+
1779
+ fs.writeFileSync(pathToFile, fileData);
1780
+ return response.send({ ok: true });
1781
+ });
1782
+
1783
+ app.post('/getgroupchat', jsonParser, (request, response) => {
1784
+ if (!request.body || !request.body.id) {
1785
+ return response.sendStatus(400);
1786
+ }
1787
+
1788
+ const id = request.body.id;
1789
+ const pathToFile = path.join(directories.groupChats, `${id}.jsonl`);
1790
+
1791
+ if (fs.existsSync(pathToFile)) {
1792
+ const data = fs.readFileSync(pathToFile, 'utf8');
1793
+ const lines = data.split('\n');
1794
+
1795
+ // Iterate through the array of strings and parse each line as JSON
1796
+ const jsonData = lines.map(json5.parse);
1797
+ return response.send(jsonData);
1798
+ } else {
1799
+ return response.send([]);
1800
+ }
1801
+ });
1802
+
1803
+ app.post('/savegroupchat', jsonParser, (request, response) => {
1804
+ if (!request.body || !request.body.id) {
1805
+ return response.sendStatus(400);
1806
+ }
1807
+
1808
+ const id = request.body.id;
1809
+ const pathToFile = path.join(directories.groupChats, `${id}.jsonl`);
1810
+
1811
+ if (!fs.existsSync(directories.groupChats)) {
1812
+ fs.mkdirSync(directories.groupChats);
1813
+ }
1814
+
1815
+ let chat_data = request.body.chat;
1816
+ let jsonlData = chat_data.map(JSON.stringify).join('\n');
1817
+ fs.writeFileSync(pathToFile, jsonlData, 'utf8');
1818
+ return response.send({ ok: true });
1819
+ });
1820
+
1821
+ app.post('/deletegroup', jsonParser, async (request, response) => {
1822
+ if (!request.body || !request.body.id) {
1823
+ return response.sendStatus(400);
1824
+ }
1825
+
1826
+ const id = request.body.id;
1827
+ const pathToGroup = path.join(directories.groups, sanitize(`${id}.json`));
1828
+ const pathToChat = path.join(directories.groupChats, sanitize(`${id}.jsonl`));
1829
+
1830
+ if (fs.existsSync(pathToGroup)) {
1831
+ fs.rmSync(pathToGroup);
1832
+ }
1833
+
1834
+ if (fs.existsSync(pathToChat)) {
1835
+ fs.rmSync(pathToChat);
1836
+ }
1837
+
1838
+ return response.send({ ok: true });
1839
+ });
1840
+
1841
+ const POE_DEFAULT_BOT = 'a2';
1842
+
1843
+ async function getPoeClient(token, useCache = false) {
1844
+ let client = new poe.Client(false, useCache);
1845
+ await client.init(token);
1846
+ return client;
1847
+ }
1848
+
1849
+ app.post('/status_poe', jsonParser, async (request, response) => {
1850
+ if (!request.body.token) {
1851
+ return response.sendStatus(400);
1852
+ }
1853
+
1854
+ try {
1855
+ const client = await getPoeClient(request.body.token);
1856
+ const botNames = client.get_bot_names();
1857
+ client.disconnect_ws();
1858
+
1859
+ return response.send({ 'bot_names': botNames });
1860
+ }
1861
+ catch {
1862
+ return response.sendStatus(401);
1863
+ }
1864
+ });
1865
+
1866
+ app.post('/purge_poe', jsonParser, async (request, response) => {
1867
+ if (!request.body.token) {
1868
+ return response.sendStatus(400);
1869
+ }
1870
+
1871
+ const token = request.body.token;
1872
+ const bot = request.body.bot ?? POE_DEFAULT_BOT;
1873
+ const count = request.body.count ?? -1;
1874
+
1875
+ try {
1876
+ const client = await getPoeClient(token, true);
1877
+ await client.purge_conversation(bot, count);
1878
+ client.disconnect_ws();
1879
+
1880
+ return response.send({ "ok": true });
1881
+ }
1882
+ catch {
1883
+ return response.sendStatus(500);
1884
+ }
1885
+ });
1886
+
1887
+ app.post('/generate_poe', jsonParser, async (request, response) => {
1888
+ if (!request.body.token || !request.body.prompt) {
1889
+ return response.sendStatus(400);
1890
+ }
1891
+
1892
+ const token = request.body.token;
1893
+ const prompt = request.body.prompt;
1894
+ const bot = request.body.bot ?? POE_DEFAULT_BOT;
1895
+ const streaming = request.body.streaming ?? false;
1896
+
1897
+ let client;
1898
+
1899
+ try {
1900
+ client = await getPoeClient(token, true);
1901
+ }
1902
+ catch (error) {
1903
+ console.error(error);
1904
+ return response.sendStatus(500);
1905
+ }
1906
+
1907
+ if (streaming) {
1908
+ let isStreamingStopped = false;
1909
+ request.socket.on('close', function () {
1910
+ isStreamingStopped = true;
1911
+ client.abortController.abort();
1912
+ });
1913
+
1914
+ try {
1915
+ response.writeHead(200, {
1916
+ 'Content-Type': 'text/plain;charset=utf-8',
1917
+ 'Transfer-Encoding': 'chunked',
1918
+ 'Cache-Control': 'no-transform',
1919
+ });
1920
+
1921
+ let reply = '';
1922
+ for await (const mes of client.send_message(bot, prompt)) {
1923
+ if (isStreamingStopped) {
1924
+ console.error('Streaming stopped by user. Closing websocket...');
1925
+ break;
1926
+ }
1927
+
1928
+ let newText = mes.text.substring(reply.length);
1929
+ reply = mes.text;
1930
+ response.write(newText);
1931
+ }
1932
+ console.log(reply);
1933
+ }
1934
+ catch (err) {
1935
+ console.error(err);
1936
+ }
1937
+ finally {
1938
+ client.disconnect_ws();
1939
+ return response.end();
1940
+ }
1941
+ }
1942
+ else {
1943
+ try {
1944
+ let reply;
1945
+ for await (const mes of client.send_message(bot, prompt)) {
1946
+ reply = mes.text;
1947
+ }
1948
+ console.log(reply);
1949
+ client.disconnect_ws();
1950
+ return response.send({ 'reply': reply });
1951
+ }
1952
+ catch {
1953
+ client.disconnect_ws();
1954
+ return response.sendStatus(500);
1955
+ }
1956
+ }
1957
+ });
1958
+
1959
+ app.get('/discover_extensions', jsonParser, function (_, response) {
1960
+ const extensions = fs
1961
+ .readdirSync(directories.extensions)
1962
+ .filter(f => fs.statSync(path.join(directories.extensions, f)).isDirectory());
1963
+
1964
+ return response.send(extensions);
1965
+ });
1966
+
1967
+ app.get('/get_sprites', jsonParser, function (request, response) {
1968
+ const name = request.query.name;
1969
+ const spritesPath = path.join(directories.characters, name);
1970
+ let sprites = [];
1971
+
1972
+ try {
1973
+ if (fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) {
1974
+ sprites = fs.readdirSync(spritesPath)
1975
+ .filter(file => {
1976
+ const mimeType = mime.lookup(file);
1977
+ return mimeType && mimeType.startsWith('image/');
1978
+ })
1979
+ .map((file) => {
1980
+ const pathToSprite = path.join(spritesPath, file);
1981
+ return {
1982
+ label: path.parse(pathToSprite).name.toLowerCase(),
1983
+ path: `/characters/${name}/${file}`,
1984
+ };
1985
+ });
1986
+ }
1987
+ }
1988
+ catch (err) {
1989
+ console.log(err);
1990
+ }
1991
+ finally {
1992
+ return response.send(sprites);
1993
+ }
1994
+ });
1995
+
1996
+ function getThumbnailFolder(type) {
1997
+ let thumbnailFolder;
1998
+
1999
+ switch (type) {
2000
+ case 'bg':
2001
+ thumbnailFolder = directories.thumbnailsBg;
2002
+ break;
2003
+ case 'avatar':
2004
+ thumbnailFolder = directories.thumbnailsAvatar;
2005
+ break;
2006
+ }
2007
+
2008
+ return thumbnailFolder;
2009
+ }
2010
+
2011
+ function getOriginalFolder(type) {
2012
+ let originalFolder;
2013
+
2014
+ switch (type) {
2015
+ case 'bg':
2016
+ originalFolder = directories.backgrounds;
2017
+ break;
2018
+ case 'avatar':
2019
+ originalFolder = directories.characters;
2020
+ break;
2021
+ }
2022
+
2023
+ return originalFolder;
2024
+ }
2025
+
2026
+ function invalidateThumbnail(type, file) {
2027
+ const folder = getThumbnailFolder(type);
2028
+ const pathToThumbnail = path.join(folder, file);
2029
+
2030
+ if (fs.existsSync(pathToThumbnail)) {
2031
+ fs.rmSync(pathToThumbnail);
2032
+ }
2033
+ }
2034
+
2035
+ async function ensureThumbnailCache() {
2036
+ const cacheFiles = fs.readdirSync(directories.thumbnailsBg);
2037
+
2038
+ // files exist, all ok
2039
+ if (cacheFiles.length) {
2040
+ return;
2041
+ }
2042
+
2043
+ console.log('Generating thumbnails cache. Please wait...');
2044
+
2045
+ const bgFiles = fs.readdirSync(directories.backgrounds);
2046
+ const tasks = [];
2047
+
2048
+ for (const file of bgFiles) {
2049
+ tasks.push(generateThumbnail('bg', file));
2050
+ }
2051
+
2052
+ await Promise.all(tasks);
2053
+ console.log(`Done! Generated: ${bgFiles.length} preview images`);
2054
+ }
2055
+
2056
+ async function generateThumbnail(type, file) {
2057
+ const pathToCachedFile = path.join(getThumbnailFolder(type), file);
2058
+ const pathToOriginalFile = path.join(getOriginalFolder(type), file);
2059
+
2060
+ const cachedFileExists = fs.existsSync(pathToCachedFile);
2061
+ const originalFileExists = fs.existsSync(pathToOriginalFile);
2062
+
2063
+ // to handle cases when original image was updated after thumb creation
2064
+ let shouldRegenerate = false;
2065
+
2066
+ if (cachedFileExists && originalFileExists) {
2067
+ const originalStat = fs.statSync(pathToOriginalFile);
2068
+ const cachedStat = fs.statSync(pathToCachedFile);
2069
+
2070
+ if (originalStat.mtimeMs > cachedStat.ctimeMs) {
2071
+ //console.log('Original file changed. Regenerating thumbnail...');
2072
+ shouldRegenerate = true;
2073
+ }
2074
+ }
2075
+
2076
+ if (cachedFileExists && !shouldRegenerate) {
2077
+ return pathToCachedFile;
2078
+ }
2079
+
2080
+ if (!originalFileExists) {
2081
+ return null;
2082
+ }
2083
+
2084
+ const imageSizes = { 'bg': [160, 90], 'avatar': [96, 144] };
2085
+ const mySize = imageSizes[type];
2086
+
2087
+ try {
2088
+ const image = await jimp.read(pathToOriginalFile);
2089
+ const buffer = await image.cover(mySize[0], mySize[1]).quality(95).getBufferAsync(mime.lookup('jpg'));
2090
+ fs.writeFileSync(pathToCachedFile, buffer);
2091
+ }
2092
+ catch (err) {
2093
+ return null;
2094
+ }
2095
+
2096
+ return pathToCachedFile;
2097
+ }
2098
+
2099
+ app.get('/thumbnail', jsonParser, async function (request, response) {
2100
+ const type = request.query.type;
2101
+ const file = sanitize(request.query.file);
2102
+
2103
+ if (!type || !file) {
2104
+ return response.sendStatus(400);
2105
+ }
2106
+
2107
+ if (!(type == 'bg' || type == 'avatar')) {
2108
+ return response.sendStatus(400);
2109
+ }
2110
+
2111
+ if (sanitize(file) !== file) {
2112
+ console.error('Malicious filename prevented');
2113
+ return response.sendStatus(403);
2114
+ }
2115
+
2116
+ const pathToCachedFile = await generateThumbnail(type, file);
2117
+
2118
+ if (!pathToCachedFile) {
2119
+ return response.sendStatus(404);
2120
+ }
2121
+
2122
+ return response.sendFile(pathToCachedFile, { root: __dirname });
2123
+ });
2124
+
2125
+ /* OpenAI */
2126
+ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_openai = response) {
2127
+ if (!request.body) return response_getstatus_openai.sendStatus(400);
2128
+ api_key_openai = request.body.key;
2129
+ const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
2130
+ const args = {
2131
+ headers: { "Authorization": "Bearer " + api_key_openai }
2132
+ };
2133
+ client.get(api_url + "/models", args, function (data, response) {
2134
+ if (response.statusCode == 200) {
2135
+ console.log(data);
2136
+ response_getstatus_openai.send(data);//data);
2137
+ }
2138
+ if (response.statusCode == 401) {
2139
+ console.log('Access Token is incorrect.');
2140
+ response_getstatus_openai.send({ error: true });
2141
+ }
2142
+ if (response.statusCode == 404) {
2143
+ console.log('Endpoint not found.');
2144
+ response_getstatus_openai.send({ error: true });
2145
+ }
2146
+ if (response.statusCode == 500 || response.statusCode == 501 || response.statusCode == 501 || response.statusCode == 503 || response.statusCode == 507) {
2147
+ console.log(data);
2148
+ response_getstatus_openai.send({ error: true });
2149
+ }
2150
+ }).on('error', function (err) {
2151
+ response_getstatus_openai.send({ error: true });
2152
+ });
2153
+ });
2154
+
2155
+ // Shamelessly stolen from Agnai
2156
+ app.post("/openai_usage", jsonParser, async function (_, response) {
2157
+ if (!request.body) return response.sendStatus(400);
2158
+ const key = request.body.key;
2159
+ const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
2160
+
2161
+ const headers = {
2162
+ 'Content-Type': 'application/json',
2163
+ Authorization: `Bearer ${key}`,
2164
+ };
2165
+
2166
+ const date = new Date();
2167
+ date.setDate(1);
2168
+ const start_date = date.toISOString().slice(0, 10);
2169
+
2170
+ date.setMonth(date.getMonth() + 1);
2171
+ const end_date = date.toISOString().slice(0, 10);
2172
+
2173
+ try {
2174
+ const res = await getAsync(
2175
+ `${api_url}/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`,
2176
+ { headers },
2177
+ );
2178
+ return response.send(res);
2179
+ }
2180
+ catch {
2181
+ return response.sendStatus(400);
2182
+ }
2183
+ });
2184
+
2185
+ app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
2186
+ if (!request.body) return response_generate_openai.sendStatus(400);
2187
+ const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
2188
+
2189
+ const controller = new AbortController();
2190
+ request.socket.on('close', function () {
2191
+ controller.abort();
2192
+ });
2193
+
2194
+ console.log(request.body);
2195
+ const config = {
2196
+ method: 'post',
2197
+ url: api_url + '/chat/completions',
2198
+ headers: {
2199
+ 'Content-Type': 'application/json',
2200
+ 'Authorization': 'Bearer ' + api_key_openai
2201
+ },
2202
+ data: {
2203
+ "messages": request.body.messages,
2204
+ "model": request.body.model,
2205
+ "temperature": request.body.temperature,
2206
+ "max_tokens": request.body.max_tokens,
2207
+ "stream": request.body.stream,
2208
+ "presence_penalty": request.body.presence_penalty,
2209
+ "frequency_penalty": request.body.frequency_penalty,
2210
+ "stop": request.body.stop,
2211
+ "logit_bias": request.body.logit_bias
2212
+ },
2213
+ signal: controller.signal,
2214
+ };
2215
+
2216
+ if (request.body.stream)
2217
+ config.responseType = 'stream';
2218
+
2219
+ axios(config)
2220
+ .then(function (response) {
2221
+ if (response.status <= 299) {
2222
+ if (request.body.stream) {
2223
+ console.log("Streaming request in progress")
2224
+ response.data.pipe(response_generate_openai);
2225
+ response.data.on('end', function () {
2226
+ console.log("Streaming request finished");
2227
+ response_generate_openai.end();
2228
+ });
2229
+ } else {
2230
+ response_generate_openai.send(response.data);
2231
+ console.log(response.data);
2232
+ console.log(response.data?.choices[0]?.message);
2233
+ }
2234
+ } else if (response.status == 400) {
2235
+ console.log('Validation error');
2236
+ response_generate_openai.send({ error: true });
2237
+ } else if (response.status == 401) {
2238
+ console.log('Access Token is incorrect');
2239
+ response_generate_openai.send({ error: true });
2240
+ } else if (response.status == 402) {
2241
+ console.log('An active subscription is required to access this endpoint');
2242
+ response_generate_openai.send({ error: true });
2243
+ } else if (response.status == 500 || response.status == 409) {
2244
+ if (request.body.stream) {
2245
+ response.data.on('data', chunk => {
2246
+ console.log(chunk.toString());
2247
+ });
2248
+ } else {
2249
+ console.log(response.data);
2250
+ }
2251
+ response_generate_openai.send({ error: true });
2252
+ }
2253
+ })
2254
+ .catch(function (error) {
2255
+ if (error.response) {
2256
+ if (request.body.stream) {
2257
+ error.response.data.on('data', chunk => {
2258
+ console.log(chunk.toString());
2259
+ });
2260
+ } else {
2261
+ console.log(error.response.data);
2262
+ }
2263
+ }
2264
+ response_generate_openai.send({ error: true });
2265
+ });
2266
+ });
2267
+
2268
+ app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_openai = response) {
2269
+ if (!request.body) return response_tokenize_openai.sendStatus(400);
2270
+
2271
+ const tokensPerName = request.query.model.includes('gpt-4') ? 1 : -1;
2272
+ const tokensPerMessage = request.query.model.includes('gpt-4') ? 3 : 4;
2273
+ const tokensPadding = 3;
2274
+
2275
+ const tokenizer = tiktoken.encoding_for_model(request.query.model);
2276
+
2277
+ let num_tokens = 0;
2278
+ for (const msg of request.body) {
2279
+ num_tokens += tokensPerMessage;
2280
+ for (const [key, value] of Object.entries(msg)) {
2281
+ num_tokens += tokenizer.encode(value).length;
2282
+ if (key == "name") {
2283
+ num_tokens += tokensPerName;
2284
+ }
2285
+ }
2286
+ }
2287
+ num_tokens += tokensPadding;
2288
+
2289
+ tokenizer.free();
2290
+
2291
+ response_tokenize_openai.send({ "token_count": num_tokens });
2292
+ });
2293
+
2294
+ app.post("/savepreset_openai", jsonParser, function (request, response) {
2295
+ const name = sanitize(request.query.name);
2296
+ if (!request.body || !name) {
2297
+ return response.sendStatus(400);
2298
+ }
2299
+
2300
+ const filename = `${name}.settings`;
2301
+ const fullpath = path.join(directories.openAI_Settings, filename);
2302
+ fs.writeFileSync(fullpath, JSON.stringify(request.body), 'utf-8');
2303
+ return response.send({ name });
2304
+ });
2305
+
2306
+ // ** REST CLIENT ASYNC WRAPPERS **
2307
+ function deleteAsync(url, args) {
2308
+ return new Promise((resolve, reject) => {
2309
+ client.delete(url, args, (data, response) => {
2310
+ if (response.statusCode >= 400) {
2311
+ reject(data);
2312
+ }
2313
+ resolve(data);
2314
+ }).on('error', e => reject(e));
2315
+ })
2316
+ }
2317
+
2318
+ function putAsync(url, args) {
2319
+ return new Promise((resolve, reject) => {
2320
+ client.put(url, args, (data, response) => {
2321
+ if (response.statusCode >= 400) {
2322
+ reject(data);
2323
+ }
2324
+ resolve(data);
2325
+ }).on('error', e => reject(e));
2326
+ })
2327
+ }
2328
+
2329
+ function postAsync(url, args) {
2330
+ return new Promise((resolve, reject) => {
2331
+ client.post(url, args, (data, response) => {
2332
+ if (response.statusCode >= 400) {
2333
+ reject([data, response]);
2334
+ }
2335
+ resolve(data);
2336
+ }).on('error', e => reject(e));
2337
+ })
2338
+ }
2339
+
2340
+ function getAsync(url, args) {
2341
+ return new Promise((resolve, reject) => {
2342
+ client.get(url, args, (data, response) => {
2343
+ if (response.statusCode >= 400) {
2344
+ reject(data);
2345
+ }
2346
+ resolve(data);
2347
+ }).on('error', e => reject(e));
2348
+ })
2349
+ }
2350
+ // ** END **
2351
+
2352
+ app.listen(server_port, (listen ? '0.0.0.0' : '127.0.0.1'), async function () {
2353
+ ensurePublicDirectoriesExist();
2354
+ await ensureThumbnailCache();
2355
+
2356
+ // Colab users could run the embedded tool
2357
+ if (!is_colab) {
2358
+ await convertWebp();
2359
+ }
2360
+
2361
+ console.log('Launching...');
2362
+ if (autorun) open('http://127.0.0.1:' + server_port);
2363
+ console.log('SillyTavern started: http://127.0.0.1:' + server_port);
2364
+ if (fs.existsSync('public/characters/update.txt') && !is_colab) {
2365
+ convertStage1();
2366
+ }
2367
+
2368
+ });
2369
+
2370
+ //#####################CONVERTING IN NEW FORMAT########################
2371
+
2372
+ var charactersB = {};//B - Backup
2373
+ var character_ib = 0;
2374
+
2375
+ var directoriesB = {};
2376
+
2377
+
2378
+ function convertStage1() {
2379
+ //if (!fs.existsSync('public/charactersBackup')) {
2380
+ //fs.mkdirSync('public/charactersBackup');
2381
+ //copyFolder('public/characters/', 'public/charactersBackup');
2382
+ //}
2383
+
2384
+ var directories = getDirectories2("public/characters");
2385
+ //console.log(directories[0]);
2386
+ charactersB = {};
2387
+ character_ib = 0;
2388
+ var folderForDel = {};
2389
+ getCharacterFile2(directories, 0);
2390
+ }
2391
+ function convertStage2() {
2392
+ var mes = true;
2393
+ for (const key in directoriesB) {
2394
+ if (mes) {
2395
+ console.log('***');
2396
+ console.log('The update of the character format has begun...');
2397
+ console.log('***');
2398
+ mes = false;
2399
+ }
2400
+
2401
+ var char = JSON.parse(charactersB[key]);
2402
+ char.create_date = humanizedISO8601DateTime();
2403
+ charactersB[key] = JSON.stringify(char);
2404
+ var avatar = 'public/img/ai4.png';
2405
+ if (char.avatar !== 'none') {
2406
+ avatar = 'public/characters/' + char.name + '/avatars/' + char.avatar;
2407
+ }
2408
+
2409
+ charaWrite(avatar, charactersB[key], directoriesB[key]);
2410
+
2411
+ const files = fs.readdirSync('public/characters/' + directoriesB[key] + '/chats');
2412
+ if (!fs.existsSync(chatsPath + char.name)) {
2413
+ fs.mkdirSync(chatsPath + char.name);
2414
+ }
2415
+ files.forEach(function (file) {
2416
+ // Read the contents of the file
2417
+
2418
+ const fileContents = fs.readFileSync('public/characters/' + directoriesB[key] + '/chats/' + file, 'utf8');
2419
+
2420
+
2421
+ // Iterate through the array of strings and parse each line as JSON
2422
+ let chat_data = JSON.parse(fileContents);
2423
+ let new_chat_data = [];
2424
+ let this_chat_user_name = 'You';
2425
+ let is_pass_0 = false;
2426
+ if (chat_data[0].indexOf('<username-holder>') !== -1) {
2427
+ this_chat_user_name = chat_data[0].substr('<username-holder>'.length, chat_data[0].length);
2428
+ is_pass_0 = true;
2429
+ }
2430
+ let i = 0;
2431
+ let ii = 0;
2432
+ new_chat_data[i] = { user_name: 'You', character_name: char.name, create_date: humanizedISO8601DateTime() };
2433
+ i++;
2434
+ ii++;
2435
+ chat_data.forEach(function (mes) {
2436
+ if (!(i === 1 && is_pass_0)) {
2437
+ if (mes.indexOf('<username-holder>') === -1 && mes.indexOf('<username-idkey>') === -1) {
2438
+ new_chat_data[ii] = {};
2439
+ let is_name = false;
2440
+ if (mes.trim().indexOf(this_chat_user_name + ':') !== 0) {
2441
+ if (mes.trim().indexOf(char.name + ':') === 0) {
2442
+ mes = mes.replace(char.name + ':', '');
2443
+ is_name = true;
2444
+ }
2445
+ new_chat_data[ii]['name'] = char.name;
2446
+ new_chat_data[ii]['is_user'] = false;
2447
+ new_chat_data[ii]['is_name'] = is_name;
2448
+ new_chat_data[ii]['send_date'] = humanizedISO8601DateTime(); //Date.now();
2449
+
2450
+ } else {
2451
+ mes = mes.replace(this_chat_user_name + ':', '');
2452
+ new_chat_data[ii]['name'] = 'You';
2453
+ new_chat_data[ii]['is_user'] = true;
2454
+ new_chat_data[ii]['is_name'] = true;
2455
+ new_chat_data[ii]['send_date'] = humanizedISO8601DateTime(); //Date.now();
2456
+
2457
+ }
2458
+ new_chat_data[ii]['mes'] = mes.trim();
2459
+ ii++;
2460
+ }
2461
+ }
2462
+ i++;
2463
+
2464
+ });
2465
+ const jsonlData = new_chat_data.map(JSON.stringify).join('\n');
2466
+ // Write the contents to the destination folder
2467
+ //console.log('convertstage2 writing a file: '+chatsPath+char.name+'/' + file+'l');
2468
+ fs.writeFileSync(chatsPath + char.name + '/' + file + 'l', jsonlData);
2469
+ });
2470
+ //fs.rmSync('public/characters/'+directoriesB[key],{ recursive: true });
2471
+ console.log(char.name + ' update!');
2472
+ }
2473
+ //removeFolders('public/characters');
2474
+ fs.unlinkSync('public/characters/update.txt');
2475
+ if (mes == false) {
2476
+ console.log('***');
2477
+ console.log('Сharacter format update completed successfully!');
2478
+ console.log('***');
2479
+ console.log('Now you can delete these folders, they are no longer used by TavernAI:');
2480
+ }
2481
+ for (const key in directoriesB) {
2482
+ console.log('public/characters/' + directoriesB[key]);
2483
+ }
2484
+ }
2485
+ function removeFolders(folder) {
2486
+ const files = fs.readdirSync(folder);
2487
+ files.forEach(function (file) {
2488
+ const filePath = folder + '/' + file;
2489
+ const stat = fs.statSync(filePath);
2490
+ if (stat.isDirectory()) {
2491
+ removeFolders(filePath);
2492
+ fs.rmdirSync(filePath);
2493
+ }
2494
+ });
2495
+ }
2496
+
2497
+ function copyFolder(src, dest) {
2498
+ const files = fs.readdirSync(src);
2499
+ files.forEach(function (file) {
2500
+ const filePath = src + '/' + file;
2501
+ const stat = fs.statSync(filePath);
2502
+ if (stat.isFile()) {
2503
+ fs.copyFileSync(filePath, dest + '/' + file);
2504
+ } else if (stat.isDirectory()) {
2505
+ fs.mkdirSync(dest + '/' + file);
2506
+ copyFolder(filePath, dest + '/' + file);
2507
+ }
2508
+ });
2509
+ }
2510
+
2511
+
2512
+ function getDirectories2(path) {
2513
+ return fs.readdirSync(path)
2514
+ .filter(function (file) {
2515
+ return fs.statSync(path + '/' + file).isDirectory();
2516
+ })
2517
+ .sort(function (a, b) {
2518
+ return new Date(fs.statSync(path + '/' + a).mtime) - new Date(fs.statSync(path + '/' + b).mtime);
2519
+ })
2520
+ .reverse();
2521
+ }
2522
+ function getCharacterFile2(directories, i) {
2523
+ if (directories.length > i) {
2524
+ fs.stat('public/characters/' + directories[i] + '/' + directories[i] + ".json", function (err, stat) {
2525
+ if (err == null) {
2526
+ fs.readFile('public/characters/' + directories[i] + '/' + directories[i] + ".json", 'utf8', (err, data) => {
2527
+ if (err) {
2528
+ console.error(err);
2529
+ return;
2530
+ }
2531
+ //console.log(data);
2532
+ if (!fs.existsSync('public/characters/' + directories[i] + '.png')) {
2533
+ charactersB[character_ib] = {};
2534
+ charactersB[character_ib] = data;
2535
+ directoriesB[character_ib] = directories[i];
2536
+ character_ib++;
2537
+ }
2538
+ i++;
2539
+ getCharacterFile2(directories, i);
2540
+ });
2541
+ } else {
2542
+ i++;
2543
+ getCharacterFile2(directories, i);
2544
+ }
2545
+ });
2546
+ } else {
2547
+ convertStage2();
2548
+ }
2549
+ }
2550
+
2551
+ async function convertWebp() {
2552
+ const files = fs.readdirSync(directories.characters).filter(e => e.endsWith(".webp"));
2553
+
2554
+ if (!files.length) {
2555
+ return;
2556
+ }
2557
+
2558
+ console.log(`${files.length} WEBP files will be automatically converted.`);
2559
+
2560
+ for (const file of files) {
2561
+ try {
2562
+ const source = path.join(directories.characters, file);
2563
+ const dest = path.join(directories.characters, path.basename(file, ".webp") + ".png");
2564
+
2565
+ if (fs.existsSync(dest)) {
2566
+ console.log(`${dest} already exists. Delete ${source} manually`);
2567
+ continue;
2568
+ }
2569
+
2570
+ console.log(`Read... ${source}`);
2571
+ const data = await charaRead(source);
2572
+
2573
+ console.log(`Convert... ${source} -> ${dest}`);
2574
+ await webp.dwebp(source, dest, "-o");
2575
+
2576
+ console.log(`Write... ${dest}`);
2577
+ await charaWrite(dest, data, path.parse(dest).name);
2578
+
2579
+ console.log(`Remove... ${source}`);
2580
+ fs.rmSync(source);
2581
+ } catch (err) {
2582
+ console.log(err);
2583
+ }
2584
+ }
2585
+ }
2586
+
2587
+ function ensurePublicDirectoriesExist() {
2588
+ for (const dir of Object.values(directories)) {
2589
+ if (!fs.existsSync(dir)) {
2590
+ fs.mkdirSync(dir, { recursive: true });
2591
+ }
2592
+ }
2593
+ }