Merge branch 'staging' into rename-presets
| @@ -16,6 +16,18 @@ module.exports = { | |||
| 16 | env: { | 16 | env: { |
| 17 | node: true, | 17 | node: true, |
| 18 | }, | 18 | }, |
| 19 | parserOptions: { | ||
| 20 | sourceType: 'module', | ||
| 21 | }, | ||
| 22 | }, | ||
| 23 | { | ||
| 24 | files: ['*.cjs'], | ||
| 25 | parserOptions: { | ||
| 26 | sourceType: 'commonjs', | ||
| 27 | }, | ||
| 28 | env: { | ||
| 29 | node: true, | ||
| 30 | }, | ||
| 19 | }, | 31 | }, |
| 20 | { | 32 | { |
| 21 | files: ['src/**/*.mjs'], | 33 | files: ['src/**/*.mjs'], |
| @@ -71,6 +83,7 @@ module.exports = { | |||
| 71 | 'docker/**', | 83 | 'docker/**', |
| 72 | 'plugins/**', | 84 | 'plugins/**', |
| 73 | '**/*.min.js', | 85 | '**/*.min.js', |
| 86 | 'public/scripts/extensions/quick-reply/lib/**', | ||
| 74 | ], | 87 | ], |
| 75 | rules: { | 88 | rules: { |
| 76 | 'no-unused-vars': ['error', { args: 'none' }], | 89 | 'no-unused-vars': ['error', { args: 'none' }], |
| @@ -2,7 +2,7 @@ | |||
| 2 | "compilerOptions": { | 2 | "compilerOptions": { |
| 3 | "module": "ESNext", | 3 | "module": "ESNext", |
| 4 | "target": "ESNext", | 4 | "target": "ESNext", |
| 5 | "moduleResolution": "node", | 5 | "moduleResolution": "Node", |
| 6 | "strictNullChecks": true, | 6 | "strictNullChecks": true, |
| 7 | "strictFunctionTypes": true, | 7 | "strictFunctionTypes": true, |
| 8 | "checkJs": true, | 8 | "checkJs": true, |
| @@ -24,9 +24,9 @@ | |||
| 24 | "csrf-csrf": "^2.2.3", | 24 | "csrf-csrf": "^2.2.3", |
| 25 | "express": "^4.21.0", | 25 | "express": "^4.21.0", |
| 26 | "form-data": "^4.0.0", | 26 | "form-data": "^4.0.0", |
| 27 | "google-translate-api-browser": "^3.0.1", | 27 | "google-translate-api-x": "^10.7.1", |
| 28 | "he": "^1.2.0", | ||
| 29 | "helmet": "^7.1.0", | 28 | "helmet": "^7.1.0", |
| 29 | "html-entities": "^2.5.2", | ||
| 30 | "iconv-lite": "^0.6.3", | 30 | "iconv-lite": "^0.6.3", |
| 31 | "ip-matching": "^2.1.2", | 31 | "ip-matching": "^2.1.2", |
| 32 | "ipaddr.js": "^2.0.1", | 32 | "ipaddr.js": "^2.0.1", |
| @@ -34,7 +34,7 @@ | |||
| 34 | "lodash": "^4.17.21", | 34 | "lodash": "^4.17.21", |
| 35 | "mime-types": "^2.1.35", | 35 | "mime-types": "^2.1.35", |
| 36 | "multer": "^1.4.5-lts.1", | 36 | "multer": "^1.4.5-lts.1", |
| 37 | "node-fetch": "^2.6.11", | 37 | "node-fetch": "^3.3.2", |
| 38 | "node-persist": "^4.0.1", | 38 | "node-persist": "^4.0.1", |
| 39 | "open": "^8.4.2", | 39 | "open": "^8.4.2", |
| 40 | "png-chunk-text": "^1.0.0", | 40 | "png-chunk-text": "^1.0.0", |
| @@ -59,9 +59,28 @@ | |||
| 59 | "sillytavern": "server.js" | 59 | "sillytavern": "server.js" |
| 60 | }, | 60 | }, |
| 61 | "devDependencies": { | 61 | "devDependencies": { |
| 62 | "@types/archiver": "^6.0.2", | ||
| 63 | "@types/command-exists": "^1.2.3", | ||
| 64 | "@types/compression": "^1.7.5", | ||
| 65 | "@types/cookie-parser": "^1.4.7", | ||
| 66 | "@types/cookie-session": "^2.0.49", | ||
| 67 | "@types/cors": "^2.8.17", | ||
| 62 | "@types/dompurify": "^3.0.5", | 68 | "@types/dompurify": "^3.0.5", |
| 69 | "@types/express": "^4.17.21", | ||
| 63 | "@types/jquery": "^3.5.29", | 70 | "@types/jquery": "^3.5.29", |
| 71 | "@types/lodash": "^4.17.10", | ||
| 72 | "@types/mime-types": "^2.1.4", | ||
| 73 | "@types/multer": "^1.4.12", | ||
| 74 | "@types/node": "^18.19.55", | ||
| 75 | "@types/node-persist": "^3.1.8", | ||
| 76 | "@types/png-chunk-text": "^1.0.3", | ||
| 77 | "@types/png-chunks-encode": "^1.0.2", | ||
| 78 | "@types/png-chunks-extract": "^1.0.2", | ||
| 79 | "@types/response-time": "^2.3.8", | ||
| 64 | "@types/toastr": "^2.1.43", | 80 | "@types/toastr": "^2.1.43", |
| 81 | "@types/write-file-atomic": "^4.0.3", | ||
| 82 | "@types/yargs": "^17.0.33", | ||
| 83 | "@types/yauzl": "^2.10.3", | ||
| 65 | "eslint": "^8.57.0" | 84 | "eslint": "^8.57.0" |
| 66 | }, | 85 | }, |
| 67 | "engines": { | 86 | "engines": { |
| @@ -949,6 +968,27 @@ | |||
| 949 | "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", | 968 | "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", |
| 950 | "license": "MIT" | 969 | "license": "MIT" |
| 951 | }, | 970 | }, |
| 971 | "node_modules/@types/archiver": { | ||
| 972 | "version": "6.0.2", | ||
| 973 | "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz", | ||
| 974 | "integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==", | ||
| 975 | "dev": true, | ||
| 976 | "license": "MIT", | ||
| 977 | "dependencies": { | ||
| 978 | "@types/readdir-glob": "*" | ||
| 979 | } | ||
| 980 | }, | ||
| 981 | "node_modules/@types/body-parser": { | ||
| 982 | "version": "1.19.5", | ||
| 983 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", | ||
| 984 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", | ||
| 985 | "dev": true, | ||
| 986 | "license": "MIT", | ||
| 987 | "dependencies": { | ||
| 988 | "@types/connect": "*", | ||
| 989 | "@types/node": "*" | ||
| 990 | } | ||
| 991 | }, | ||
| 952 | "node_modules/@types/cacheable-request": { | 992 | "node_modules/@types/cacheable-request": { |
| 953 | "version": "6.0.3", | 993 | "version": "6.0.3", |
| 954 | "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", | 994 | "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", |
| @@ -961,6 +1001,64 @@ | |||
| 961 | "@types/responselike": "^1.0.0" | 1001 | "@types/responselike": "^1.0.0" |
| 962 | } | 1002 | } |
| 963 | }, | 1003 | }, |
| 1004 | "node_modules/@types/command-exists": { | ||
| 1005 | "version": "1.2.3", | ||
| 1006 | "resolved": "https://registry.npmjs.org/@types/command-exists/-/command-exists-1.2.3.tgz", | ||
| 1007 | "integrity": "sha512-PpbaE2XWLaWYboXD6k70TcXO/OdOyyRFq5TVpmlUELNxdkkmXU9fkImNosmXU1DtsNrqdUgWd/nJQYXgwmtdXQ==", | ||
| 1008 | "dev": true, | ||
| 1009 | "license": "MIT" | ||
| 1010 | }, | ||
| 1011 | "node_modules/@types/compression": { | ||
| 1012 | "version": "1.7.5", | ||
| 1013 | "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.5.tgz", | ||
| 1014 | "integrity": "sha512-AAQvK5pxMpaT+nDvhHrsBhLSYG5yQdtkaJE1WYieSNY2mVFKAgmU4ks65rkZD5oqnGCFLyQpUr1CqI4DmUMyDg==", | ||
| 1015 | "dev": true, | ||
| 1016 | "license": "MIT", | ||
| 1017 | "dependencies": { | ||
| 1018 | "@types/express": "*" | ||
| 1019 | } | ||
| 1020 | }, | ||
| 1021 | "node_modules/@types/connect": { | ||
| 1022 | "version": "3.4.38", | ||
| 1023 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", | ||
| 1024 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", | ||
| 1025 | "dev": true, | ||
| 1026 | "license": "MIT", | ||
| 1027 | "dependencies": { | ||
| 1028 | "@types/node": "*" | ||
| 1029 | } | ||
| 1030 | }, | ||
| 1031 | "node_modules/@types/cookie-parser": { | ||
| 1032 | "version": "1.4.7", | ||
| 1033 | "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz", | ||
| 1034 | "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==", | ||
| 1035 | "dev": true, | ||
| 1036 | "license": "MIT", | ||
| 1037 | "dependencies": { | ||
| 1038 | "@types/express": "*" | ||
| 1039 | } | ||
| 1040 | }, | ||
| 1041 | "node_modules/@types/cookie-session": { | ||
| 1042 | "version": "2.0.49", | ||
| 1043 | "resolved": "https://registry.npmjs.org/@types/cookie-session/-/cookie-session-2.0.49.tgz", | ||
| 1044 | "integrity": "sha512-4E/bBjlqLhU5l4iGPR+NkVJH593hpNsT4dC3DJDr+ODm6Qpe13kZQVkezRIb+TYDXaBMemS3yLQ+0leba3jlkQ==", | ||
| 1045 | "dev": true, | ||
| 1046 | "license": "MIT", | ||
| 1047 | "dependencies": { | ||
| 1048 | "@types/express": "*", | ||
| 1049 | "@types/keygrip": "*" | ||
| 1050 | } | ||
| 1051 | }, | ||
| 1052 | "node_modules/@types/cors": { | ||
| 1053 | "version": "2.8.17", | ||
| 1054 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", | ||
| 1055 | "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", | ||
| 1056 | "dev": true, | ||
| 1057 | "license": "MIT", | ||
| 1058 | "dependencies": { | ||
| 1059 | "@types/node": "*" | ||
| 1060 | } | ||
| 1061 | }, | ||
| 964 | "node_modules/@types/dompurify": { | 1062 | "node_modules/@types/dompurify": { |
| 965 | "version": "3.0.5", | 1063 | "version": "3.0.5", |
| 966 | "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", | 1064 | "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", |
| @@ -971,12 +1069,43 @@ | |||
| 971 | "@types/trusted-types": "*" | 1069 | "@types/trusted-types": "*" |
| 972 | } | 1070 | } |
| 973 | }, | 1071 | }, |
| 1072 | "node_modules/@types/express": { | ||
| 1073 | "version": "4.17.21", | ||
| 1074 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", | ||
| 1075 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", | ||
| 1076 | "dev": true, | ||
| 1077 | "dependencies": { | ||
| 1078 | "@types/body-parser": "*", | ||
| 1079 | "@types/express-serve-static-core": "^4.17.33", | ||
| 1080 | "@types/qs": "*", | ||
| 1081 | "@types/serve-static": "*" | ||
| 1082 | } | ||
| 1083 | }, | ||
| 1084 | "node_modules/@types/express-serve-static-core": { | ||
| 1085 | "version": "4.19.6", | ||
| 1086 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", | ||
| 1087 | "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", | ||
| 1088 | "dev": true, | ||
| 1089 | "dependencies": { | ||
| 1090 | "@types/node": "*", | ||
| 1091 | "@types/qs": "*", | ||
| 1092 | "@types/range-parser": "*", | ||
| 1093 | "@types/send": "*" | ||
| 1094 | } | ||
| 1095 | }, | ||
| 974 | "node_modules/@types/http-cache-semantics": { | 1096 | "node_modules/@types/http-cache-semantics": { |
| 975 | "version": "4.0.2", | 1097 | "version": "4.0.2", |
| 976 | "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz", | 1098 | "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz", |
| 977 | "integrity": "sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==", | 1099 | "integrity": "sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==", |
| 978 | "license": "MIT" | 1100 | "license": "MIT" |
| 979 | }, | 1101 | }, |
| 1102 | "node_modules/@types/http-errors": { | ||
| 1103 | "version": "2.0.4", | ||
| 1104 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", | ||
| 1105 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", | ||
| 1106 | "dev": true, | ||
| 1107 | "license": "MIT" | ||
| 1108 | }, | ||
| 980 | "node_modules/@types/jquery": { | 1109 | "node_modules/@types/jquery": { |
| 981 | "version": "3.5.31", | 1110 | "version": "3.5.31", |
| 982 | "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.31.tgz", | 1111 | "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.31.tgz", |
| @@ -987,6 +1116,13 @@ | |||
| 987 | "@types/sizzle": "*" | 1116 | "@types/sizzle": "*" |
| 988 | } | 1117 | } |
| 989 | }, | 1118 | }, |
| 1119 | "node_modules/@types/keygrip": { | ||
| 1120 | "version": "1.0.6", | ||
| 1121 | "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", | ||
| 1122 | "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", | ||
| 1123 | "dev": true, | ||
| 1124 | "license": "MIT" | ||
| 1125 | }, | ||
| 990 | "node_modules/@types/keyv": { | 1126 | "node_modules/@types/keyv": { |
| 991 | "version": "3.1.4", | 1127 | "version": "3.1.4", |
| 992 | "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", | 1128 | "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", |
| @@ -996,28 +1132,126 @@ | |||
| 996 | "@types/node": "*" | 1132 | "@types/node": "*" |
| 997 | } | 1133 | } |
| 998 | }, | 1134 | }, |
| 1135 | "node_modules/@types/lodash": { | ||
| 1136 | "version": "4.17.10", | ||
| 1137 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz", | ||
| 1138 | "integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==", | ||
| 1139 | "dev": true, | ||
| 1140 | "license": "MIT" | ||
| 1141 | }, | ||
| 999 | "node_modules/@types/long": { | 1142 | "node_modules/@types/long": { |
| 1000 | "version": "4.0.2", | 1143 | "version": "4.0.2", |
| 1001 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", | 1144 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", |
| 1002 | "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", | 1145 | "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", |
| 1003 | "license": "MIT" | 1146 | "license": "MIT" |
| 1004 | }, | 1147 | }, |
| 1005 | "node_modules/@types/node": { | 1148 | "node_modules/@types/mime": { |
| 1006 | "version": "16.9.1", | 1149 | "version": "1.3.5", |
| 1007 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", | 1150 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", |
| 1008 | "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", | 1151 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", |
| 1152 | "dev": true, | ||
| 1153 | "license": "MIT" | ||
| 1154 | }, | ||
| 1155 | "node_modules/@types/mime-types": { | ||
| 1156 | "version": "2.1.4", | ||
| 1157 | "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", | ||
| 1158 | "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", | ||
| 1159 | "dev": true, | ||
| 1009 | "license": "MIT" | 1160 | "license": "MIT" |
| 1010 | }, | 1161 | }, |
| 1162 | "node_modules/@types/multer": { | ||
| 1163 | "version": "1.4.12", | ||
| 1164 | "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", | ||
| 1165 | "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", | ||
| 1166 | "dev": true, | ||
| 1167 | "license": "MIT", | ||
| 1168 | "dependencies": { | ||
| 1169 | "@types/express": "*" | ||
| 1170 | } | ||
| 1171 | }, | ||
| 1172 | "node_modules/@types/node": { | ||
| 1173 | "version": "18.19.55", | ||
| 1174 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.55.tgz", | ||
| 1175 | "integrity": "sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw==", | ||
| 1176 | "license": "MIT", | ||
| 1177 | "dependencies": { | ||
| 1178 | "undici-types": "~5.26.4" | ||
| 1179 | } | ||
| 1180 | }, | ||
| 1011 | "node_modules/@types/node-fetch": { | 1181 | "node_modules/@types/node-fetch": { |
| 1012 | "version": "2.6.9", | 1182 | "version": "2.6.11", |
| 1013 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", | 1183 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", |
| 1014 | "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", | 1184 | "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", |
| 1015 | "license": "MIT", | 1185 | "license": "MIT", |
| 1016 | "dependencies": { | 1186 | "dependencies": { |
| 1017 | "@types/node": "*", | 1187 | "@types/node": "*", |
| 1018 | "form-data": "^4.0.0" | 1188 | "form-data": "^4.0.0" |
| 1019 | } | 1189 | } |
| 1020 | }, | 1190 | }, |
| 1191 | "node_modules/@types/node-persist": { | ||
| 1192 | "version": "3.1.8", | ||
| 1193 | "resolved": "https://registry.npmjs.org/@types/node-persist/-/node-persist-3.1.8.tgz", | ||
| 1194 | "integrity": "sha512-QLidg6/SadZYPrTKxtxL1A85XBoQlG40bhoMdhu6DH6+eNCMr2j+RGfFZ9I9+IY8W/PDwQonJ+iBWD62jZjMfg==", | ||
| 1195 | "dev": true, | ||
| 1196 | "license": "MIT", | ||
| 1197 | "dependencies": { | ||
| 1198 | "@types/node": "*" | ||
| 1199 | } | ||
| 1200 | }, | ||
| 1201 | "node_modules/@types/png-chunk-text": { | ||
| 1202 | "version": "1.0.3", | ||
| 1203 | "resolved": "https://registry.npmjs.org/@types/png-chunk-text/-/png-chunk-text-1.0.3.tgz", | ||
| 1204 | "integrity": "sha512-7keEFz73uNJ9Ar1XMCNnHEXT9pICJnouMQCCYgBEmHMgdkXaQzSTmSvr6tUDSqgdEgmlRAxZd97wprgliyZoCg==", | ||
| 1205 | "dev": true, | ||
| 1206 | "license": "MIT" | ||
| 1207 | }, | ||
| 1208 | "node_modules/@types/png-chunks-encode": { | ||
| 1209 | "version": "1.0.2", | ||
| 1210 | "resolved": "https://registry.npmjs.org/@types/png-chunks-encode/-/png-chunks-encode-1.0.2.tgz", | ||
| 1211 | "integrity": "sha512-Dxn0aXEcSg1wVeHjvNlygm/+fKBDzWMCdxJYhjGUTeefFW/jYxWcrg+W7ppLBfH44iJMqeVBHtHBwtYQUeYvgw==", | ||
| 1212 | "dev": true, | ||
| 1213 | "license": "MIT" | ||
| 1214 | }, | ||
| 1215 | "node_modules/@types/png-chunks-extract": { | ||
| 1216 | "version": "1.0.2", | ||
| 1217 | "resolved": "https://registry.npmjs.org/@types/png-chunks-extract/-/png-chunks-extract-1.0.2.tgz", | ||
| 1218 | "integrity": "sha512-z6djfFIbrrddtunoMJBOPlyZrnmeuG1kkvHUNi2QfpOb+JMMLuLliHHTmMyRi7k7LiTAut0HbdGCF6ibDtQAHQ==", | ||
| 1219 | "dev": true, | ||
| 1220 | "license": "MIT" | ||
| 1221 | }, | ||
| 1222 | "node_modules/@types/qs": { | ||
| 1223 | "version": "6.9.16", | ||
| 1224 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", | ||
| 1225 | "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", | ||
| 1226 | "dev": true | ||
| 1227 | }, | ||
| 1228 | "node_modules/@types/range-parser": { | ||
| 1229 | "version": "1.2.7", | ||
| 1230 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", | ||
| 1231 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", | ||
| 1232 | "dev": true | ||
| 1233 | }, | ||
| 1234 | "node_modules/@types/readdir-glob": { | ||
| 1235 | "version": "1.1.5", | ||
| 1236 | "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", | ||
| 1237 | "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", | ||
| 1238 | "dev": true, | ||
| 1239 | "license": "MIT", | ||
| 1240 | "dependencies": { | ||
| 1241 | "@types/node": "*" | ||
| 1242 | } | ||
| 1243 | }, | ||
| 1244 | "node_modules/@types/response-time": { | ||
| 1245 | "version": "2.3.8", | ||
| 1246 | "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.8.tgz", | ||
| 1247 | "integrity": "sha512-7qGaNYvdxc0zRab8oHpYx7AW17qj+G0xuag1eCrw3M2VWPJQ/HyKaaghWygiaOUl0y9x7QGQwppDpqLJ5V9pzw==", | ||
| 1248 | "dev": true, | ||
| 1249 | "license": "MIT", | ||
| 1250 | "dependencies": { | ||
| 1251 | "@types/express": "*", | ||
| 1252 | "@types/node": "*" | ||
| 1253 | } | ||
| 1254 | }, | ||
| 1021 | "node_modules/@types/responselike": { | 1255 | "node_modules/@types/responselike": { |
| 1022 | "version": "1.0.1", | 1256 | "version": "1.0.1", |
| 1023 | "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.1.tgz", | 1257 | "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.1.tgz", |
| @@ -1027,6 +1261,29 @@ | |||
| 1027 | "@types/node": "*" | 1261 | "@types/node": "*" |
| 1028 | } | 1262 | } |
| 1029 | }, | 1263 | }, |
| 1264 | "node_modules/@types/send": { | ||
| 1265 | "version": "0.17.4", | ||
| 1266 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", | ||
| 1267 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", | ||
| 1268 | "dev": true, | ||
| 1269 | "license": "MIT", | ||
| 1270 | "dependencies": { | ||
| 1271 | "@types/mime": "^1", | ||
| 1272 | "@types/node": "*" | ||
| 1273 | } | ||
| 1274 | }, | ||
| 1275 | "node_modules/@types/serve-static": { | ||
| 1276 | "version": "1.15.7", | ||
| 1277 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", | ||
| 1278 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", | ||
| 1279 | "dev": true, | ||
| 1280 | "license": "MIT", | ||
| 1281 | "dependencies": { | ||
| 1282 | "@types/http-errors": "*", | ||
| 1283 | "@types/node": "*", | ||
| 1284 | "@types/send": "*" | ||
| 1285 | } | ||
| 1286 | }, | ||
| 1030 | "node_modules/@types/sizzle": { | 1287 | "node_modules/@types/sizzle": { |
| 1031 | "version": "2.3.8", | 1288 | "version": "2.3.8", |
| 1032 | "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", | 1289 | "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", |
| @@ -1050,6 +1307,43 @@ | |||
| 1050 | "dev": true, | 1307 | "dev": true, |
| 1051 | "license": "MIT" | 1308 | "license": "MIT" |
| 1052 | }, | 1309 | }, |
| 1310 | "node_modules/@types/write-file-atomic": { | ||
| 1311 | "version": "4.0.3", | ||
| 1312 | "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz", | ||
| 1313 | "integrity": "sha512-qdo+vZRchyJIHNeuI1nrpsLw+hnkgqP/8mlaN6Wle/NKhydHmUN9l4p3ZE8yP90AJNJW4uB8HQhedb4f1vNayQ==", | ||
| 1314 | "dev": true, | ||
| 1315 | "license": "MIT", | ||
| 1316 | "dependencies": { | ||
| 1317 | "@types/node": "*" | ||
| 1318 | } | ||
| 1319 | }, | ||
| 1320 | "node_modules/@types/yargs": { | ||
| 1321 | "version": "17.0.33", | ||
| 1322 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", | ||
| 1323 | "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", | ||
| 1324 | "dev": true, | ||
| 1325 | "license": "MIT", | ||
| 1326 | "dependencies": { | ||
| 1327 | "@types/yargs-parser": "*" | ||
| 1328 | } | ||
| 1329 | }, | ||
| 1330 | "node_modules/@types/yargs-parser": { | ||
| 1331 | "version": "21.0.3", | ||
| 1332 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", | ||
| 1333 | "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", | ||
| 1334 | "dev": true, | ||
| 1335 | "license": "MIT" | ||
| 1336 | }, | ||
| 1337 | "node_modules/@types/yauzl": { | ||
| 1338 | "version": "2.10.3", | ||
| 1339 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", | ||
| 1340 | "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", | ||
| 1341 | "dev": true, | ||
| 1342 | "license": "MIT", | ||
| 1343 | "dependencies": { | ||
| 1344 | "@types/node": "*" | ||
| 1345 | } | ||
| 1346 | }, | ||
| 1053 | "node_modules/@ungap/structured-clone": { | 1347 | "node_modules/@ungap/structured-clone": { |
| 1054 | "version": "1.2.0", | 1348 | "version": "1.2.0", |
| 1055 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", | 1349 | "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", |
| @@ -3009,6 +3303,29 @@ | |||
| 3009 | "pend": "~1.2.0" | 3303 | "pend": "~1.2.0" |
| 3010 | } | 3304 | } |
| 3011 | }, | 3305 | }, |
| 3306 | "node_modules/fetch-blob": { | ||
| 3307 | "version": "3.2.0", | ||
| 3308 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", | ||
| 3309 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", | ||
| 3310 | "funding": [ | ||
| 3311 | { | ||
| 3312 | "type": "github", | ||
| 3313 | "url": "https://github.com/sponsors/jimmywarting" | ||
| 3314 | }, | ||
| 3315 | { | ||
| 3316 | "type": "paypal", | ||
| 3317 | "url": "https://paypal.me/jimmywarting" | ||
| 3318 | } | ||
| 3319 | ], | ||
| 3320 | "license": "MIT", | ||
| 3321 | "dependencies": { | ||
| 3322 | "node-domexception": "^1.0.0", | ||
| 3323 | "web-streams-polyfill": "^3.0.3" | ||
| 3324 | }, | ||
| 3325 | "engines": { | ||
| 3326 | "node": "^12.20 || >= 14.13" | ||
| 3327 | } | ||
| 3328 | }, | ||
| 3012 | "node_modules/file-entry-cache": { | 3329 | "node_modules/file-entry-cache": { |
| 3013 | "version": "6.0.1", | 3330 | "version": "6.0.1", |
| 3014 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", | 3331 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", |
| @@ -3178,6 +3495,18 @@ | |||
| 3178 | "node": ">= 14" | 3495 | "node": ">= 14" |
| 3179 | } | 3496 | } |
| 3180 | }, | 3497 | }, |
| 3498 | "node_modules/formdata-polyfill": { | ||
| 3499 | "version": "4.0.10", | ||
| 3500 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", | ||
| 3501 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", | ||
| 3502 | "license": "MIT", | ||
| 3503 | "dependencies": { | ||
| 3504 | "fetch-blob": "^3.1.2" | ||
| 3505 | }, | ||
| 3506 | "engines": { | ||
| 3507 | "node": ">=12.20.0" | ||
| 3508 | } | ||
| 3509 | }, | ||
| 3181 | "node_modules/forwarded": { | 3510 | "node_modules/forwarded": { |
| 3182 | "version": "0.2.0", | 3511 | "version": "0.2.0", |
| 3183 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", | 3512 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", |
| @@ -3364,11 +3693,18 @@ | |||
| 3364 | "url": "https://github.com/sponsors/sindresorhus" | 3693 | "url": "https://github.com/sponsors/sindresorhus" |
| 3365 | } | 3694 | } |
| 3366 | }, | 3695 | }, |
| 3367 | "node_modules/google-translate-api-browser": { | 3696 | "node_modules/google-translate-api-x": { |
| 3368 | "version": "3.0.1", | 3697 | "version": "10.7.1", |
| 3369 | "resolved": "https://registry.npmjs.org/google-translate-api-browser/-/google-translate-api-browser-3.0.1.tgz", | 3698 | "resolved": "https://registry.npmjs.org/google-translate-api-x/-/google-translate-api-x-10.7.1.tgz", |
| 3370 | "integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A==", | 3699 | "integrity": "sha512-OdZDS6jRWzn1woOk62aOKQ5OyVaJSA+eyc6CktOWxo36IWfstOjwG/dkvnGl3Z2Sbpmk1A+jc2WwrBiRjqaY2A==", |
| 3371 | "license": "MIT" | 3700 | "license": "MIT", |
| 3701 | "engines": { | ||
| 3702 | "node": ">=14.0.0" | ||
| 3703 | }, | ||
| 3704 | "funding": { | ||
| 3705 | "type": "github", | ||
| 3706 | "url": "https://github.com/sponsors/AidanWelch" | ||
| 3707 | } | ||
| 3372 | }, | 3708 | }, |
| 3373 | "node_modules/gopd": { | 3709 | "node_modules/gopd": { |
| 3374 | "version": "1.0.1", | 3710 | "version": "1.0.1", |
| @@ -3489,14 +3825,6 @@ | |||
| 3489 | "node": ">= 0.4" | 3825 | "node": ">= 0.4" |
| 3490 | } | 3826 | } |
| 3491 | }, | 3827 | }, |
| 3492 | "node_modules/he": { | ||
| 3493 | "version": "1.2.0", | ||
| 3494 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", | ||
| 3495 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", | ||
| 3496 | "bin": { | ||
| 3497 | "he": "bin/he" | ||
| 3498 | } | ||
| 3499 | }, | ||
| 3500 | "node_modules/helmet": { | 3828 | "node_modules/helmet": { |
| 3501 | "version": "7.1.0", | 3829 | "version": "7.1.0", |
| 3502 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", | 3830 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz", |
| @@ -3505,6 +3833,22 @@ | |||
| 3505 | "node": ">=16.0.0" | 3833 | "node": ">=16.0.0" |
| 3506 | } | 3834 | } |
| 3507 | }, | 3835 | }, |
| 3836 | "node_modules/html-entities": { | ||
| 3837 | "version": "2.5.2", | ||
| 3838 | "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", | ||
| 3839 | "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", | ||
| 3840 | "funding": [ | ||
| 3841 | { | ||
| 3842 | "type": "github", | ||
| 3843 | "url": "https://github.com/sponsors/mdevils" | ||
| 3844 | }, | ||
| 3845 | { | ||
| 3846 | "type": "patreon", | ||
| 3847 | "url": "https://patreon.com/mdevils" | ||
| 3848 | } | ||
| 3849 | ], | ||
| 3850 | "license": "MIT" | ||
| 3851 | }, | ||
| 3508 | "node_modules/htmlparser2": { | 3852 | "node_modules/htmlparser2": { |
| 3509 | "version": "8.0.2", | 3853 | "version": "8.0.2", |
| 3510 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", | 3854 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", |
| @@ -3691,6 +4035,12 @@ | |||
| 3691 | "@types/node": "16.9.1" | 4035 | "@types/node": "16.9.1" |
| 3692 | } | 4036 | } |
| 3693 | }, | 4037 | }, |
| 4038 | "node_modules/image-q/node_modules/@types/node": { | ||
| 4039 | "version": "16.9.1", | ||
| 4040 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", | ||
| 4041 | "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", | ||
| 4042 | "license": "MIT" | ||
| 4043 | }, | ||
| 3694 | "node_modules/import-fresh": { | 4044 | "node_modules/import-fresh": { |
| 3695 | "version": "3.3.0", | 4045 | "version": "3.3.0", |
| 3696 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", | 4046 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", |
| @@ -3876,6 +4226,26 @@ | |||
| 3876 | "whatwg-fetch": "^3.4.1" | 4226 | "whatwg-fetch": "^3.4.1" |
| 3877 | } | 4227 | } |
| 3878 | }, | 4228 | }, |
| 4229 | "node_modules/isomorphic-fetch/node_modules/node-fetch": { | ||
| 4230 | "version": "2.7.0", | ||
| 4231 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", | ||
| 4232 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", | ||
| 4233 | "license": "MIT", | ||
| 4234 | "dependencies": { | ||
| 4235 | "whatwg-url": "^5.0.0" | ||
| 4236 | }, | ||
| 4237 | "engines": { | ||
| 4238 | "node": "4.x || >=6.0.0" | ||
| 4239 | }, | ||
| 4240 | "peerDependencies": { | ||
| 4241 | "encoding": "^0.1.0" | ||
| 4242 | }, | ||
| 4243 | "peerDependenciesMeta": { | ||
| 4244 | "encoding": { | ||
| 4245 | "optional": true | ||
| 4246 | } | ||
| 4247 | } | ||
| 4248 | }, | ||
| 3879 | "node_modules/jackspeak": { | 4249 | "node_modules/jackspeak": { |
| 3880 | "version": "2.3.6", | 4250 | "version": "2.3.6", |
| 3881 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", | 4251 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", |
| @@ -4355,23 +4725,30 @@ | |||
| 4355 | } | 4725 | } |
| 4356 | }, | 4726 | }, |
| 4357 | "node_modules/node-fetch": { | 4727 | "node_modules/node-fetch": { |
| 4358 | "version": "2.6.12", | 4728 | "version": "3.3.2", |
| 4359 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", | 4729 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", |
| 4360 | "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", | 4730 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", |
| 4361 | "license": "MIT", | 4731 | "license": "MIT", |
| 4362 | "dependencies": { | 4732 | "dependencies": { |
| 4363 | "whatwg-url": "^5.0.0" | 4733 | "data-uri-to-buffer": "^4.0.0", |
| 4734 | "fetch-blob": "^3.1.4", | ||
| 4735 | "formdata-polyfill": "^4.0.10" | ||
| 4364 | }, | 4736 | }, |
| 4365 | "engines": { | 4737 | "engines": { |
| 4366 | "node": "4.x || >=6.0.0" | 4738 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" |
| 4367 | }, | 4739 | }, |
| 4368 | "peerDependencies": { | 4740 | "funding": { |
| 4369 | "encoding": "^0.1.0" | 4741 | "type": "opencollective", |
| 4370 | }, | 4742 | "url": "https://opencollective.com/node-fetch" |
| 4371 | "peerDependenciesMeta": { | 4743 | } |
| 4372 | "encoding": { | 4744 | }, |
| 4373 | "optional": true | 4745 | "node_modules/node-fetch/node_modules/data-uri-to-buffer": { |
| 4374 | } | 4746 | "version": "4.0.1", |
| 4747 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", | ||
| 4748 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", | ||
| 4749 | "license": "MIT", | ||
| 4750 | "engines": { | ||
| 4751 | "node": ">= 12" | ||
| 4375 | } | 4752 | } |
| 4376 | }, | 4753 | }, |
| 4377 | "node_modules/node-persist": { | 4754 | "node_modules/node-persist": { |
| @@ -4537,13 +4914,24 @@ | |||
| 4537 | "openai": "bin/cli" | 4914 | "openai": "bin/cli" |
| 4538 | } | 4915 | } |
| 4539 | }, | 4916 | }, |
| 4540 | "node_modules/openai/node_modules/@types/node": { | 4917 | "node_modules/openai/node_modules/node-fetch": { |
| 4541 | "version": "18.18.9", | 4918 | "version": "2.7.0", |
| 4542 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", | 4919 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", |
| 4543 | "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", | 4920 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", |
| 4544 | "license": "MIT", | 4921 | "license": "MIT", |
| 4545 | "dependencies": { | 4922 | "dependencies": { |
| 4546 | "undici-types": "~5.26.4" | 4923 | "whatwg-url": "^5.0.0" |
| 4924 | }, | ||
| 4925 | "engines": { | ||
| 4926 | "node": "4.x || >=6.0.0" | ||
| 4927 | }, | ||
| 4928 | "peerDependencies": { | ||
| 4929 | "encoding": "^0.1.0" | ||
| 4930 | }, | ||
| 4931 | "peerDependenciesMeta": { | ||
| 4932 | "encoding": { | ||
| 4933 | "optional": true | ||
| 4934 | } | ||
| 4547 | } | 4935 | } |
| 4548 | }, | 4936 | }, |
| 4549 | "node_modules/optionator": { | 4937 | "node_modules/optionator": { |
| @@ -5808,6 +6196,7 @@ | |||
| 5808 | "version": "5.0.0", | 6196 | "version": "5.0.0", |
| 5809 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", | 6197 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", |
| 5810 | "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", | 6198 | "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", |
| 6199 | "license": "MIT", | ||
| 5811 | "dependencies": { | 6200 | "dependencies": { |
| 5812 | "punycode": "^2.3.1" | 6201 | "punycode": "^2.3.1" |
| 5813 | }, | 6202 | }, |
| @@ -6012,6 +6401,7 @@ | |||
| 6012 | "version": "7.0.0", | 6401 | "version": "7.0.0", |
| 6013 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", | 6402 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", |
| 6014 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", | 6403 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", |
| 6404 | "license": "BSD-2-Clause", | ||
| 6015 | "engines": { | 6405 | "engines": { |
| 6016 | "node": ">=12" | 6406 | "node": ">=12" |
| 6017 | } | 6407 | } |
| @@ -6026,6 +6416,7 @@ | |||
| 6026 | "version": "14.0.0", | 6416 | "version": "14.0.0", |
| 6027 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", | 6417 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", |
| 6028 | "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", | 6418 | "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", |
| 6419 | "license": "MIT", | ||
| 6029 | "dependencies": { | 6420 | "dependencies": { |
| 6030 | "tr46": "^5.0.0", | 6421 | "tr46": "^5.0.0", |
| 6031 | "webidl-conversions": "^7.0.0" | 6422 | "webidl-conversions": "^7.0.0" |
| @@ -14,9 +14,9 @@ | |||
| 14 | "csrf-csrf": "^2.2.3", | 14 | "csrf-csrf": "^2.2.3", |
| 15 | "express": "^4.21.0", | 15 | "express": "^4.21.0", |
| 16 | "form-data": "^4.0.0", | 16 | "form-data": "^4.0.0", |
| 17 | "google-translate-api-browser": "^3.0.1", | 17 | "google-translate-api-x": "^10.7.1", |
| 18 | "he": "^1.2.0", | ||
| 19 | "helmet": "^7.1.0", | 18 | "helmet": "^7.1.0", |
| 19 | "html-entities": "^2.5.2", | ||
| 20 | "iconv-lite": "^0.6.3", | 20 | "iconv-lite": "^0.6.3", |
| 21 | "ip-matching": "^2.1.2", | 21 | "ip-matching": "^2.1.2", |
| 22 | "ipaddr.js": "^2.0.1", | 22 | "ipaddr.js": "^2.0.1", |
| @@ -24,7 +24,7 @@ | |||
| 24 | "lodash": "^4.17.21", | 24 | "lodash": "^4.17.21", |
| 25 | "mime-types": "^2.1.35", | 25 | "mime-types": "^2.1.35", |
| 26 | "multer": "^1.4.5-lts.1", | 26 | "multer": "^1.4.5-lts.1", |
| 27 | "node-fetch": "^2.6.11", | 27 | "node-fetch": "^3.3.2", |
| 28 | "node-persist": "^4.0.1", | 28 | "node-persist": "^4.0.1", |
| 29 | "open": "^8.4.2", | 29 | "open": "^8.4.2", |
| 30 | "png-chunk-text": "^1.0.0", | 30 | "png-chunk-text": "^1.0.0", |
| @@ -60,7 +60,7 @@ | |||
| 60 | } | 60 | } |
| 61 | }, | 61 | }, |
| 62 | "name": "sillytavern", | 62 | "name": "sillytavern", |
| 63 | "type": "commonjs", | 63 | "type": "module", |
| 64 | "license": "AGPL-3.0", | 64 | "license": "AGPL-3.0", |
| 65 | "repository": { | 65 | "repository": { |
| 66 | "type": "git", | 66 | "type": "git", |
| @@ -85,9 +85,28 @@ | |||
| 85 | }, | 85 | }, |
| 86 | "main": "server.js", | 86 | "main": "server.js", |
| 87 | "devDependencies": { | 87 | "devDependencies": { |
| 88 | "@types/archiver": "^6.0.2", | ||
| 89 | "@types/command-exists": "^1.2.3", | ||
| 90 | "@types/compression": "^1.7.5", | ||
| 91 | "@types/cookie-parser": "^1.4.7", | ||
| 92 | "@types/cookie-session": "^2.0.49", | ||
| 93 | "@types/cors": "^2.8.17", | ||
| 88 | "@types/dompurify": "^3.0.5", | 94 | "@types/dompurify": "^3.0.5", |
| 95 | "@types/express": "^4.17.21", | ||
| 89 | "@types/jquery": "^3.5.29", | 96 | "@types/jquery": "^3.5.29", |
| 97 | "@types/lodash": "^4.17.10", | ||
| 98 | "@types/mime-types": "^2.1.4", | ||
| 99 | "@types/multer": "^1.4.12", | ||
| 100 | "@types/node": "^18.19.55", | ||
| 101 | "@types/node-persist": "^3.1.8", | ||
| 102 | "@types/png-chunk-text": "^1.0.3", | ||
| 103 | "@types/png-chunks-encode": "^1.0.2", | ||
| 104 | "@types/png-chunks-extract": "^1.0.2", | ||
| 105 | "@types/response-time": "^2.3.8", | ||
| 90 | "@types/toastr": "^2.1.43", | 106 | "@types/toastr": "^2.1.43", |
| 107 | "@types/write-file-atomic": "^4.0.3", | ||
| 108 | "@types/yargs": "^17.0.33", | ||
| 109 | "@types/yauzl": "^2.10.3", | ||
| 91 | "eslint": "^8.57.0" | 110 | "eslint": "^8.57.0" |
| 92 | } | 111 | } |
| 93 | } | 112 | } |
| @@ -3,16 +3,28 @@ | |||
| 3 | // 1. node plugins.js update | 3 | // 1. node plugins.js update |
| 4 | // 2. node plugins.js install <plugin-git-url> | 4 | // 2. node plugins.js install <plugin-git-url> |
| 5 | // More operations coming soon. | 5 | // More operations coming soon. |
| 6 | const { default: git } = require('simple-git'); | 6 | import fs from 'node:fs'; |
| 7 | const fs = require('fs'); | 7 | import path from 'node:path'; |
| 8 | const path = require('path'); | 8 | import process from 'node:process'; |
| 9 | const { color } = require('./src/util'); | 9 | import { fileURLToPath } from 'node:url'; |
| 10 | 10 | ||
| 11 | import { default as git } from 'simple-git'; | ||
| 12 | import { color } from './src/util.js'; | ||
| 13 | |||
| 14 | const __dirname = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url)); | ||
| 11 | process.chdir(__dirname); | 15 | process.chdir(__dirname); |
| 12 | const pluginsPath = './plugins'; | 16 | const pluginsPath = './plugins'; |
| 13 | 17 | ||
| 14 | const command = process.argv[2]; | 18 | const command = process.argv[2]; |
| 15 | 19 | ||
| 20 | if (!command) { | ||
| 21 | console.log('Usage: node plugins.js <command>'); | ||
| 22 | console.log('Commands:'); | ||
| 23 | console.log(' update - Update all installed plugins'); | ||
| 24 | console.log(' install <plugin-git-url> - Install plugin from a Git URL'); | ||
| 25 | process.exit(1); | ||
| 26 | } | ||
| 27 | |||
| 16 | if (command === 'update') { | 28 | if (command === 'update') { |
| 17 | console.log(color.magenta('Updating all plugins')); | 29 | console.log(color.magenta('Updating all plugins')); |
| 18 | updatePlugins(); | 30 | updatePlugins(); |
| @@ -0,0 +1,4 @@ | |||
| 1 | { | ||
| 2 | "name": "sillytavern-plugins", | ||
| 3 | "type": "commonjs" | ||
| 4 | } | ||
| @@ -1,11 +1,13 @@ | |||
| 1 | /** | 1 | /** |
| 2 | * Scripts to be done before starting the server for the first time. | 2 | * Scripts to be done before starting the server for the first time. |
| 3 | */ | 3 | */ |
| 4 | const fs = require('fs'); | 4 | import fs from 'node:fs'; |
| 5 | const path = require('path'); | 5 | import path from 'node:path'; |
| 6 | const crypto = require('crypto'); | 6 | import crypto from 'node:crypto'; |
| 7 | const yaml = require('yaml'); | 7 | import process from 'node:process'; |
| 8 | const _ = require('lodash'); | 8 | import yaml from 'yaml'; |
| 9 | import _ from 'lodash'; | ||
| 10 | import { createRequire } from 'node:module'; | ||
| 9 | 11 | ||
| 10 | /** | 12 | /** |
| 11 | * Colorizes console output. | 13 | * Colorizes console output. |
| @@ -59,13 +61,15 @@ function convertConfig() { | |||
| 59 | 61 | ||
| 60 | try { | 62 | try { |
| 61 | console.log(color.blue('Converting config.conf to config.yaml. Your old config.conf will be renamed to config.conf.bak')); | 63 | console.log(color.blue('Converting config.conf to config.yaml. Your old config.conf will be renamed to config.conf.bak')); |
| 62 | const config = require(path.join(process.cwd(), './config.conf')); | 64 | fs.renameSync('./config.conf', './config.conf.cjs'); // Force loading as CommonJS |
| 63 | fs.copyFileSync('./config.conf', './config.conf.bak'); | 65 | const require = createRequire(import.meta.url); |
| 64 | fs.rmSync('./config.conf'); | 66 | const config = require(path.join(process.cwd(), './config.conf.cjs')); |
| 67 | fs.copyFileSync('./config.conf.cjs', './config.conf.bak'); | ||
| 68 | fs.rmSync('./config.conf.cjs'); | ||
| 65 | fs.writeFileSync('./config.yaml', yaml.stringify(config)); | 69 | fs.writeFileSync('./config.yaml', yaml.stringify(config)); |
| 66 | console.log(color.green('Conversion successful. Please check your config.yaml and fix it if necessary.')); | 70 | console.log(color.green('Conversion successful. Please check your config.yaml and fix it if necessary.')); |
| 67 | } catch (error) { | 71 | } catch (error) { |
| 68 | console.error(color.red('FATAL: Config conversion failed. Please check your config.conf file and try again.')); | 72 | console.error(color.red('FATAL: Config conversion failed. Please check your config.conf file and try again.'), error); |
| 69 | return; | 73 | return; |
| 70 | } | 74 | } |
| 71 | } | 75 | } |
| @@ -75,7 +79,7 @@ function convertConfig() { | |||
| 75 | * Compares the current config.yaml with the default config.yaml and adds any missing values. | 79 | * Compares the current config.yaml with the default config.yaml and adds any missing values. |
| 76 | */ | 80 | */ |
| 77 | function addMissingConfigValues() { | 81 | function addMissingConfigValues() { |
| 78 | try { | 82 | try { |
| 79 | const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8')); | 83 | const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8')); |
| 80 | let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); | 84 | let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); |
| 81 | 85 | ||
| @@ -132,7 +136,7 @@ function createDefaultFiles() { | |||
| 132 | function getMd5Hash(data) { | 136 | function getMd5Hash(data) { |
| 133 | return crypto | 137 | return crypto |
| 134 | .createHash('md5') | 138 | .createHash('md5') |
| 135 | .update(data) | 139 | .update(new Uint8Array(data)) |
| 136 | .digest('hex'); | 140 | .digest('hex'); |
| 137 | } | 141 | } |
| 138 | 142 | ||
| @@ -340,9 +340,9 @@ body.no-blur * { | |||
| 340 | backdrop-filter: unset !important; | 340 | backdrop-filter: unset !important; |
| 341 | } | 341 | } |
| 342 | 342 | ||
| 343 | body.no-blur #send_form.no-connection { | 343 | /* body.no-blur #send_form.no-connection { |
| 344 | background-color: rgba(100, 0, 0, 0.9) !important; | 344 | background-color: rgba(100, 0, 0, 0.9) !important; |
| 345 | } | 345 | } */ |
| 346 | 346 | ||
| 347 | body.no-blur #bg1, | 347 | body.no-blur #bg1, |
| 348 | body.no-blur #bg_custom { | 348 | body.no-blur #bg_custom { |
| @@ -12,7 +12,8 @@ | |||
| 12 | "**/dist/**", | 12 | "**/dist/**", |
| 13 | "**/.git/**", | 13 | "**/.git/**", |
| 14 | "lib/**", | 14 | "lib/**", |
| 15 | "**/*.min.js" | 15 | "**/*.min.js", |
| 16 | "scripts/extensions/quick-reply/lib/**" | ||
| 16 | ], | 17 | ], |
| 17 | "typeAcquisition": { | 18 | "typeAcquisition": { |
| 18 | "include": [ | 19 | "include": [ |
| @@ -7,7 +7,6 @@ import { | |||
| 7 | event_types, | 7 | event_types, |
| 8 | eventSource, | 8 | eventSource, |
| 9 | getCharacters, | 9 | getCharacters, |
| 10 | getPastCharacterChats, | ||
| 11 | getRequestHeaders, | 10 | getRequestHeaders, |
| 12 | buildAvatarList, | 11 | buildAvatarList, |
| 13 | characterToEntity, | 12 | characterToEntity, |
| @@ -315,7 +315,7 @@ class PromptManager { | |||
| 315 | */ | 315 | */ |
| 316 | init(moduleConfiguration, serviceSettings) { | 316 | init(moduleConfiguration, serviceSettings) { |
| 317 | this.configuration = Object.assign(this.configuration, moduleConfiguration); | 317 | this.configuration = Object.assign(this.configuration, moduleConfiguration); |
| 318 | this.tokenHandler = this.tokenHandler || new TokenHandler(); | 318 | this.tokenHandler = this.tokenHandler || new TokenHandler(() => { throw new Error('Token handler not set'); }); |
| 319 | this.serviceSettings = serviceSettings; | 319 | this.serviceSettings = serviceSettings; |
| 320 | this.containerElement = document.getElementById(this.configuration.containerIdentifier); | 320 | this.containerElement = document.getElementById(this.configuration.containerIdentifier); |
| 321 | 321 | ||
| @@ -314,7 +314,7 @@ function RA_checkOnlineStatus() { | |||
| 314 | if (online_status == 'no_connection') { | 314 | if (online_status == 'no_connection') { |
| 315 | const send_textarea = $('#send_textarea'); | 315 | const send_textarea = $('#send_textarea'); |
| 316 | send_textarea.attr('placeholder', send_textarea.attr('no_connection_text')); //Input bar placeholder tells users they are not connected | 316 | send_textarea.attr('placeholder', send_textarea.attr('no_connection_text')); //Input bar placeholder tells users they are not connected |
| 317 | $('#send_form').addClass('no-connection'); //entire input form area is red when not connected | 317 | //$('#send_form').addClass('no-connection'); //entire input form area is red when not connected |
| 318 | $('#send_but').addClass('displayNone'); //send button is hidden when not connected; | 318 | $('#send_but').addClass('displayNone'); //send button is hidden when not connected; |
| 319 | $('#mes_continue').addClass('displayNone'); //continue button is hidden when not connected; | 319 | $('#mes_continue').addClass('displayNone'); //continue button is hidden when not connected; |
| 320 | $('#mes_impersonate').addClass('displayNone'); //continue button is hidden when not connected; | 320 | $('#mes_impersonate').addClass('displayNone'); //continue button is hidden when not connected; |
| @@ -325,7 +325,7 @@ function RA_checkOnlineStatus() { | |||
| 325 | if (online_status !== undefined && online_status !== 'no_connection') { | 325 | if (online_status !== undefined && online_status !== 'no_connection') { |
| 326 | const send_textarea = $('#send_textarea'); | 326 | const send_textarea = $('#send_textarea'); |
| 327 | send_textarea.attr('placeholder', send_textarea.attr('connected_text')); //on connect, placeholder tells user to type message | 327 | send_textarea.attr('placeholder', send_textarea.attr('connected_text')); //on connect, placeholder tells user to type message |
| 328 | $('#send_form').removeClass('no-connection'); | 328 | //$('#send_form').removeClass('no-connection'); |
| 329 | $('#API-status-top').removeClass('fa-plug-circle-exclamation redOverlayGlow'); | 329 | $('#API-status-top').removeClass('fa-plug-circle-exclamation redOverlayGlow'); |
| 330 | $('#API-status-top').addClass('fa-plug'); | 330 | $('#API-status-top').addClass('fa-plug'); |
| 331 | connection_made = true; | 331 | connection_made = true; |
| @@ -148,7 +148,7 @@ export function initDynamicStyles() { | |||
| 148 | // Start observing the head for any new added stylesheets | 148 | // Start observing the head for any new added stylesheets |
| 149 | observer.observe(document.head, { | 149 | observer.observe(document.head, { |
| 150 | childList: true, | 150 | childList: true, |
| 151 | subtree: true | 151 | subtree: true, |
| 152 | }); | 152 | }); |
| 153 | 153 | ||
| 154 | // Process all stylesheets on initial load | 154 | // Process all stylesheets on initial load |
| @@ -1,6 +1,6 @@ | |||
| 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; | 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, main_api, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; | 3 | import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; |
| 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; | 5 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 6 | import { getMultimodalCaption } from '../shared.js'; | 6 | import { getMultimodalCaption } from '../shared.js'; |
| @@ -12,7 +12,7 @@ | |||
| 12 | <input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span> | 12 | <input type="checkbox" id="qr--isCombined"><span data-i18n="Combine Quick Replies">Combine Quick Replies</span> |
| 13 | </label> | 13 | </label> |
| 14 | <label class="flex-container"> | 14 | <label class="flex-container"> |
| 15 | <input type="checkbox" id="qr--showPopoutButton"><span data-i18n="Show Popout Button">Show Popout Button</span> | 15 | <input type="checkbox" id="qr--showPopoutButton"><span data-i18n="Show Popout Button">Show Popout Button (on Desktop)</span> |
| 16 | </label> | 16 | </label> |
| 17 | 17 | ||
| 18 | <hr> | 18 | <hr> |
| @@ -34,6 +34,14 @@ | |||
| 34 | right: 0.25em; | 34 | right: 0.25em; |
| 35 | top: 0; | 35 | top: 0; |
| 36 | } | 36 | } |
| 37 | |||
| 38 | /*hide QR popout for mobile*/ | ||
| 39 | @media screen and (max-width: 1000px) { | ||
| 40 | #qr--bar > #qr--popoutTrigger { | ||
| 41 | display:none; | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 37 | #qr--bar.popoutVisible { | 45 | #qr--bar.popoutVisible { |
| 38 | padding-right: 2.5em; | 46 | padding-right: 2.5em; |
| 39 | } | 47 | } |
| @@ -55,6 +63,7 @@ | |||
| 55 | opacity: 0.5; | 63 | opacity: 0.5; |
| 56 | transition: all 250ms; | 64 | transition: all 250ms; |
| 57 | } | 65 | } |
| 66 | |||
| 58 | #qr--popout > .qr--body { | 67 | #qr--popout > .qr--body { |
| 59 | overflow-y: auto; | 68 | overflow-y: auto; |
| 60 | } | 69 | } |
| @@ -1,8 +1,6 @@ | |||
| 1 | import { | 1 | import { |
| 2 | saveSettingsDebounced, | 2 | saveSettingsDebounced, |
| 3 | systemUserName, | 3 | systemUserName, |
| 4 | hideSwipeButtons, | ||
| 5 | showSwipeButtons, | ||
| 6 | getRequestHeaders, | 4 | getRequestHeaders, |
| 7 | event_types, | 5 | event_types, |
| 8 | eventSource, | 6 | eventSource, |
| @@ -2208,10 +2206,9 @@ function processReply(str) { | |||
| 2208 | 2206 | ||
| 2209 | str = str.replaceAll('"', ''); | 2207 | str = str.replaceAll('"', ''); |
| 2210 | str = str.replaceAll('“', ''); | 2208 | str = str.replaceAll('“', ''); |
| 2211 | str = str.replaceAll('.', ','); | ||
| 2212 | str = str.replaceAll('\n', ', '); | 2209 | str = str.replaceAll('\n', ', '); |
| 2213 | str = str.normalize('NFD'); | 2210 | str = str.normalize('NFD'); |
| 2214 | str = str.replace(/[^a-zA-Z0-9,:_(){}<>[\]\-']+/g, ' '); | 2211 | str = str.replace(/[^a-zA-Z0-9.,:_(){}<>[\]\-']+/g, ' '); |
| 2215 | str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one | 2212 | str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one |
| 2216 | str = str.trim(); | 2213 | str = str.trim(); |
| 2217 | 2214 | ||
| @@ -2675,8 +2672,7 @@ async function generateTogetherAIImage(prompt, negativePrompt, signal) { | |||
| 2675 | }); | 2672 | }); |
| 2676 | 2673 | ||
| 2677 | if (result.ok) { | 2674 | if (result.ok) { |
| 2678 | const data = await result.json(); | 2675 | return await result.json(); |
| 2679 | return { format: 'jpg', data: data?.output?.choices?.[0]?.image_base64 }; | ||
| 2680 | } else { | 2676 | } else { |
| 2681 | const text = await result.text(); | 2677 | const text = await result.text(); |
| 2682 | throw new Error(text); | 2678 | throw new Error(text); |
| @@ -0,0 +1,140 @@ | |||
| 1 | import { getRequestHeaders } from '../../../script.js'; | ||
| 2 | import { splitRecursive } from '../../utils.js'; | ||
| 3 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; | ||
| 4 | export { GoogleTranslateTtsProvider }; | ||
| 5 | |||
| 6 | class GoogleTranslateTtsProvider { | ||
| 7 | settings; | ||
| 8 | voices = []; | ||
| 9 | separator = ' . '; | ||
| 10 | audioElement = document.createElement('audio'); | ||
| 11 | |||
| 12 | defaultSettings = { | ||
| 13 | region: '', | ||
| 14 | voiceMap: {}, | ||
| 15 | }; | ||
| 16 | |||
| 17 | get settingsHtml() { | ||
| 18 | return ''; | ||
| 19 | } | ||
| 20 | |||
| 21 | onSettingsChange() { | ||
| 22 | this.voices = []; | ||
| 23 | saveTtsProviderSettings(); | ||
| 24 | } | ||
| 25 | |||
| 26 | async loadSettings(settings) { | ||
| 27 | // Populate Provider UI given input settings | ||
| 28 | if (Object.keys(settings).length == 0) { | ||
| 29 | console.info('Using default TTS Provider settings'); | ||
| 30 | } | ||
| 31 | |||
| 32 | // Only accept keys defined in defaultSettings | ||
| 33 | this.settings = this.defaultSettings; | ||
| 34 | |||
| 35 | for (const key in settings) { | ||
| 36 | if (key in this.settings) { | ||
| 37 | this.settings[key] = settings[key]; | ||
| 38 | } else { | ||
| 39 | throw `Invalid setting passed to TTS Provider: ${key}`; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | try { | ||
| 44 | await this.checkReady(); | ||
| 45 | console.debug('Google Translate TTS: Settings loaded'); | ||
| 46 | } catch { | ||
| 47 | console.debug('Google Translate TTS: Settings loaded, but not ready'); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | // Perform a simple readiness check by trying to fetch voiceIds | ||
| 52 | async checkReady() { | ||
| 53 | await this.fetchTtsVoiceObjects(); | ||
| 54 | } | ||
| 55 | |||
| 56 | async onRefreshClick() { | ||
| 57 | await this.checkReady(); | ||
| 58 | } | ||
| 59 | |||
| 60 | //#################// | ||
| 61 | // TTS Interfaces // | ||
| 62 | //#################// | ||
| 63 | |||
| 64 | async getVoice(voiceName) { | ||
| 65 | if (this.voices.length == 0) { | ||
| 66 | this.voices = await this.fetchTtsVoiceObjects(); | ||
| 67 | } | ||
| 68 | const match = this.voices.filter( | ||
| 69 | voice => voice.name == voiceName || voice.voice_id == voiceName, | ||
| 70 | )[0]; | ||
| 71 | if (!match) { | ||
| 72 | throw `TTS Voice name ${voiceName} not found`; | ||
| 73 | } | ||
| 74 | return match; | ||
| 75 | } | ||
| 76 | |||
| 77 | async generateTts(text, voiceId) { | ||
| 78 | const response = await this.fetchTtsGeneration(text, voiceId); | ||
| 79 | return response; | ||
| 80 | } | ||
| 81 | |||
| 82 | //###########// | ||
| 83 | // API CALLS // | ||
| 84 | //###########// | ||
| 85 | async fetchTtsVoiceObjects() { | ||
| 86 | const response = await fetch('/api/google/list-voices', { | ||
| 87 | method: 'POST', | ||
| 88 | headers: getRequestHeaders(), | ||
| 89 | body: JSON.stringify({}), | ||
| 90 | }); | ||
| 91 | |||
| 92 | if (!response.ok) { | ||
| 93 | throw new Error(`HTTP ${response.status}: ${await response.text()}`); | ||
| 94 | } | ||
| 95 | let responseJson = await response.json(); | ||
| 96 | responseJson = Object.entries(responseJson) | ||
| 97 | .sort((a, b) => a[1].localeCompare(b[1])) | ||
| 98 | .map(x => ({ name: x[1], voice_id: x[0], preview_url: false, lang: x[0] })); | ||
| 99 | return responseJson; | ||
| 100 | } | ||
| 101 | |||
| 102 | /** | ||
| 103 | * Preview TTS for a given voice ID. | ||
| 104 | * @param {string} id Voice ID | ||
| 105 | */ | ||
| 106 | async previewTtsVoice(id) { | ||
| 107 | this.audioElement.pause(); | ||
| 108 | this.audioElement.currentTime = 0; | ||
| 109 | const voice = await this.getVoice(id); | ||
| 110 | const text = getPreviewString(voice.lang); | ||
| 111 | const response = await this.fetchTtsGeneration(text, id); | ||
| 112 | if (!response.ok) { | ||
| 113 | throw new Error(`HTTP ${response.status}: ${await response.text()}`); | ||
| 114 | } | ||
| 115 | |||
| 116 | const audio = await response.blob(); | ||
| 117 | const url = URL.createObjectURL(audio); | ||
| 118 | this.audioElement.src = url; | ||
| 119 | this.audioElement.play(); | ||
| 120 | this.audioElement.onended = () => URL.revokeObjectURL(url); | ||
| 121 | } | ||
| 122 | |||
| 123 | async fetchTtsGeneration(text, voiceId) { | ||
| 124 | const response = await fetch('/api/google/generate-voice', { | ||
| 125 | method: 'POST', | ||
| 126 | headers: getRequestHeaders(), | ||
| 127 | body: JSON.stringify({ | ||
| 128 | text: splitRecursive(text, 200), | ||
| 129 | voice: voiceId, | ||
| 130 | }), | ||
| 131 | }); | ||
| 132 | |||
| 133 | if (!response.ok) { | ||
| 134 | toastr.error(response.statusText, 'TTS Generation Failed'); | ||
| 135 | throw new Error(`HTTP ${response.status}: ${await response.text()}`); | ||
| 136 | } | ||
| 137 | |||
| 138 | return response; | ||
| 139 | } | ||
| 140 | } | ||
| @@ -26,6 +26,7 @@ import { debounce_timeout } from '../../constants.js'; | |||
| 26 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; | 26 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 27 | import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 27 | import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 28 | import { POPUP_TYPE, callGenericPopup } from '../../popup.js'; | 28 | import { POPUP_TYPE, callGenericPopup } from '../../popup.js'; |
| 29 | import { GoogleTranslateTtsProvider } from './google-translate.js'; | ||
| 29 | export { talkingAnimation }; | 30 | export { talkingAnimation }; |
| 30 | 31 | ||
| 31 | const UPDATE_INTERVAL = 1000; | 32 | const UPDATE_INTERVAL = 1000; |
| @@ -91,6 +92,7 @@ const ttsProviders = { | |||
| 91 | 'CosyVoice (Unofficial)': CosyVoiceProvider, | 92 | 'CosyVoice (Unofficial)': CosyVoiceProvider, |
| 92 | Edge: EdgeTtsProvider, | 93 | Edge: EdgeTtsProvider, |
| 93 | ElevenLabs: ElevenLabsTtsProvider, | 94 | ElevenLabs: ElevenLabsTtsProvider, |
| 95 | 'Google Translate': GoogleTranslateTtsProvider, | ||
| 94 | GSVI: GSVITtsProvider, | 96 | GSVI: GSVITtsProvider, |
| 95 | 'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider, | 97 | 'GPT-SoVITS-V2 (Unofficial)': GptSovitsV2Provider, |
| 96 | Novel: NovelTtsProvider, | 98 | Novel: NovelTtsProvider, |
| @@ -183,9 +183,9 @@ function redirectToHome() { | |||
| 183 | // After a login theres no need to preserve the | 183 | // After a login theres no need to preserve the |
| 184 | // noauto (if present) | 184 | // noauto (if present) |
| 185 | const urlParams = new URLSearchParams(window.location.search); | 185 | const urlParams = new URLSearchParams(window.location.search); |
| 186 | 186 | ||
| 187 | urlParams.delete('noauto'); | 187 | urlParams.delete('noauto'); |
| 188 | 188 | ||
| 189 | window.location.href = '/' + urlParams.toString(); | 189 | window.location.href = '/' + urlParams.toString(); |
| 190 | } | 190 | } |
| 191 | 191 | ||
| @@ -464,7 +464,7 @@ export function evaluateMacros(content, env) { | |||
| 464 | content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? '')); | 464 | content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? '')); |
| 465 | content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? '')); | 465 | content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? '')); |
| 466 | content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? '')); | 466 | content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? '')); |
| 467 | content = content.replace(/{{reverse\:(.+?)}}/gi, (_, str) => Array.from(str).reverse().join('')); | 467 | content = content.replace(/{{reverse:(.+?)}}/gi, (_, str) => Array.from(str).reverse().join('')); |
| 468 | 468 | ||
| 469 | content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); | 469 | content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); |
| 470 | 470 | ||
| @@ -60,7 +60,7 @@ import { | |||
| 60 | resetScrollHeight, | 60 | resetScrollHeight, |
| 61 | stringFormat, | 61 | stringFormat, |
| 62 | } from './utils.js'; | 62 | } from './utils.js'; |
| 63 | import { countTokensOpenAI, getTokenizerModel } from './tokenizers.js'; | 63 | import { countTokensOpenAIAsync, getTokenizerModel } from './tokenizers.js'; |
| 64 | import { isMobile } from './RossAscends-mods.js'; | 64 | import { isMobile } from './RossAscends-mods.js'; |
| 65 | import { saveLogprobsForActiveMessage } from './logprobs.js'; | 65 | import { saveLogprobsForActiveMessage } from './logprobs.js'; |
| 66 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 66 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| @@ -671,14 +671,14 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 671 | 671 | ||
| 672 | // Reserve budget for new chat message | 672 | // Reserve budget for new chat message |
| 673 | const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt; | 673 | const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt; |
| 674 | const newChatMessage = new Message('system', substituteParams(newChat), 'newMainChat'); | 674 | const newChatMessage = await Message.createAsync('system', substituteParams(newChat), 'newMainChat'); |
| 675 | chatCompletion.reserveBudget(newChatMessage); | 675 | chatCompletion.reserveBudget(newChatMessage); |
| 676 | 676 | ||
| 677 | // Reserve budget for group nudge | 677 | // Reserve budget for group nudge |
| 678 | let groupNudgeMessage = null; | 678 | let groupNudgeMessage = null; |
| 679 | const noGroupNudgeTypes = ['impersonate']; | 679 | const noGroupNudgeTypes = ['impersonate']; |
| 680 | if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) { | 680 | if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) { |
| 681 | groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge')); | 681 | groupNudgeMessage = await Message.fromPromptAsync(prompts.get('groupNudge')); |
| 682 | chatCompletion.reserveBudget(groupNudgeMessage); | 682 | chatCompletion.reserveBudget(groupNudgeMessage); |
| 683 | } | 683 | } |
| 684 | 684 | ||
| @@ -693,12 +693,12 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 693 | }; | 693 | }; |
| 694 | const continuePrompt = new Prompt(promptObject); | 694 | const continuePrompt = new Prompt(promptObject); |
| 695 | const preparedPrompt = promptManager.preparePrompt(continuePrompt); | 695 | const preparedPrompt = promptManager.preparePrompt(continuePrompt); |
| 696 | continueMessage = Message.fromPrompt(preparedPrompt); | 696 | continueMessage = await Message.fromPromptAsync(preparedPrompt); |
| 697 | chatCompletion.reserveBudget(continueMessage); | 697 | chatCompletion.reserveBudget(continueMessage); |
| 698 | } | 698 | } |
| 699 | 699 | ||
| 700 | const lastChatPrompt = messages[messages.length - 1]; | 700 | const lastChatPrompt = messages[messages.length - 1]; |
| 701 | const message = new Message('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement'); | 701 | const message = await Message.createAsync('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement'); |
| 702 | if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) { | 702 | if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) { |
| 703 | chatCompletion.insert(message, 'chatHistory'); | 703 | chatCompletion.insert(message, 'chatHistory'); |
| 704 | } | 704 | } |
| @@ -715,11 +715,11 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 715 | // We do not want to mutate the prompt | 715 | // We do not want to mutate the prompt |
| 716 | const prompt = new Prompt(chatPrompt); | 716 | const prompt = new Prompt(chatPrompt); |
| 717 | prompt.identifier = `chatHistory-${messages.length - index}`; | 717 | prompt.identifier = `chatHistory-${messages.length - index}`; |
| 718 | const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt)); | 718 | const chatMessage = await Message.fromPromptAsync(promptManager.preparePrompt(prompt)); |
| 719 | 719 | ||
| 720 | if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) { | 720 | if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) { |
| 721 | const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name); | 721 | const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name); |
| 722 | chatMessage.setName(messageName); | 722 | await chatMessage.setName(messageName); |
| 723 | } | 723 | } |
| 724 | 724 | ||
| 725 | if (imageInlining && chatPrompt.image) { | 725 | if (imageInlining && chatPrompt.image) { |
| @@ -729,9 +729,9 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 729 | if (canUseTools && Array.isArray(chatPrompt.invocations)) { | 729 | if (canUseTools && Array.isArray(chatPrompt.invocations)) { |
| 730 | /** @type {import('./tool-calling.js').ToolInvocation[]} */ | 730 | /** @type {import('./tool-calling.js').ToolInvocation[]} */ |
| 731 | const invocations = chatPrompt.invocations; | 731 | const invocations = chatPrompt.invocations; |
| 732 | const toolCallMessage = new Message(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier); | 732 | const toolCallMessage = await Message.createAsync(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier); |
| 733 | const toolResultMessages = invocations.slice().reverse().map((invocation) => new Message('tool', invocation.result || '[No content]', invocation.id)); | 733 | const toolResultMessages = await Promise.all(invocations.slice().reverse().map((invocation) => Message.createAsync('tool', invocation.result || '[No content]', invocation.id))); |
| 734 | toolCallMessage.setToolCalls(invocations); | 734 | await toolCallMessage.setToolCalls(invocations); |
| 735 | if (chatCompletion.canAffordAll([toolCallMessage, ...toolResultMessages])) { | 735 | if (chatCompletion.canAffordAll([toolCallMessage, ...toolResultMessages])) { |
| 736 | for (const resultMessage of toolResultMessages) { | 736 | for (const resultMessage of toolResultMessages) { |
| 737 | chatCompletion.insertAtStart(resultMessage, 'chatHistory'); | 737 | chatCompletion.insertAtStart(resultMessage, 'chatHistory'); |
| @@ -748,7 +748,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 748 | if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) { | 748 | if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) { |
| 749 | // in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message | 749 | // in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message |
| 750 | if (chatPrompt.role === 'assistant') { | 750 | if (chatPrompt.role === 'assistant') { |
| 751 | const collection = new MessageCollection('continuePrefill', new Message(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier)); | 751 | const continueMessage = await Message.createAsync(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier); |
| 752 | const collection = new MessageCollection('continuePrefill', continueMessage); | ||
| 752 | chatCompletion.add(collection, -1); | 753 | chatCompletion.add(collection, -1); |
| 753 | continue; | 754 | continue; |
| 754 | } | 755 | } |
| @@ -787,18 +788,17 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 787 | * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts. | 788 | * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts. |
| 788 | * @param {Object[]} messageExamples - Array containing all message examples. | 789 | * @param {Object[]} messageExamples - Array containing all message examples. |
| 789 | */ | 790 | */ |
| 790 | function populateDialogueExamples(prompts, chatCompletion, messageExamples) { | 791 | async function populateDialogueExamples(prompts, chatCompletion, messageExamples) { |
| 791 | if (!prompts.has('dialogueExamples')) { | 792 | if (!prompts.has('dialogueExamples')) { |
| 792 | return; | 793 | return; |
| 793 | } | 794 | } |
| 794 | 795 | ||
| 795 | chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples')); | 796 | chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples')); |
| 796 | if (Array.isArray(messageExamples) && messageExamples.length) { | 797 | if (Array.isArray(messageExamples) && messageExamples.length) { |
| 797 | const newExampleChat = new Message('system', substituteParams(oai_settings.new_example_chat_prompt), 'newChat'); | 798 | const newExampleChat = await Message.createAsync('system', substituteParams(oai_settings.new_example_chat_prompt), 'newChat'); |
| 798 | [...messageExamples].forEach((dialogue, dialogueIndex) => { | 799 | for (const dialogue of [...messageExamples]) { |
| 799 | let examplesAdded = 0; | 800 | const dialogueIndex = messageExamples.indexOf(dialogue); |
| 800 | 801 | const chatMessages = []; | |
| 801 | if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples'); | ||
| 802 | 802 | ||
| 803 | for (let promptIndex = 0; promptIndex < dialogue.length; promptIndex++) { | 803 | for (let promptIndex = 0; promptIndex < dialogue.length; promptIndex++) { |
| 804 | const prompt = dialogue[promptIndex]; | 804 | const prompt = dialogue[promptIndex]; |
| @@ -806,19 +806,20 @@ function populateDialogueExamples(prompts, chatCompletion, messageExamples) { | |||
| 806 | const content = prompt.content || ''; | 806 | const content = prompt.content || ''; |
| 807 | const identifier = `dialogueExamples ${dialogueIndex}-${promptIndex}`; | 807 | const identifier = `dialogueExamples ${dialogueIndex}-${promptIndex}`; |
| 808 | 808 | ||
| 809 | const chatMessage = new Message(role, content, identifier); | 809 | const chatMessage = await Message.createAsync(role, content, identifier); |
| 810 | chatMessage.setName(prompt.name); | 810 | await chatMessage.setName(prompt.name); |
| 811 | if (!chatCompletion.canAfford(chatMessage)) { | 811 | chatMessages.push(chatMessage); |
| 812 | break; | ||
| 813 | } | ||
| 814 | chatCompletion.insert(chatMessage, 'dialogueExamples'); | ||
| 815 | examplesAdded++; | ||
| 816 | } | 812 | } |
| 817 | 813 | ||
| 818 | if (0 === examplesAdded) { | 814 | if (!chatCompletion.canAffordAll([newExampleChat, ...chatMessages])) { |
| 819 | chatCompletion.removeLastFrom('dialogueExamples'); | 815 | break; |
| 820 | } | 816 | } |
| 821 | }); | 817 | |
| 818 | chatCompletion.insert(newExampleChat, 'dialogueExamples'); | ||
| 819 | for (const chatMessage of chatMessages) { | ||
| 820 | chatCompletion.insert(chatMessage, 'dialogueExamples'); | ||
| 821 | } | ||
| 822 | } | ||
| 822 | } | 823 | } |
| 823 | } | 824 | } |
| 824 | 825 | ||
| @@ -873,7 +874,7 @@ function getPromptRole(role) { | |||
| 873 | */ | 874 | */ |
| 874 | async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }) { | 875 | async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }) { |
| 875 | // Helper function for preparing a prompt, that already exists within the prompt collection, for completion | 876 | // Helper function for preparing a prompt, that already exists within the prompt collection, for completion |
| 876 | const addToChatCompletion = (source, target = null) => { | 877 | const addToChatCompletion = async (source, target = null) => { |
| 877 | // We need the prompts array to determine a position for the source. | 878 | // We need the prompts array to determine a position for the source. |
| 878 | if (false === prompts.has(source)) return; | 879 | if (false === prompts.has(source)) return; |
| 879 | 880 | ||
| @@ -891,30 +892,31 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 891 | 892 | ||
| 892 | const index = target ? prompts.index(target) : prompts.index(source); | 893 | const index = target ? prompts.index(target) : prompts.index(source); |
| 893 | const collection = new MessageCollection(source); | 894 | const collection = new MessageCollection(source); |
| 894 | collection.add(Message.fromPrompt(prompt)); | 895 | const message = await Message.fromPromptAsync(prompt); |
| 896 | collection.add(message); | ||
| 895 | chatCompletion.add(collection, index); | 897 | chatCompletion.add(collection, index); |
| 896 | }; | 898 | }; |
| 897 | 899 | ||
| 898 | chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|> | 900 | chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|> |
| 899 | // Character and world information | 901 | // Character and world information |
| 900 | addToChatCompletion('worldInfoBefore'); | 902 | await addToChatCompletion('worldInfoBefore'); |
| 901 | addToChatCompletion('main'); | 903 | await addToChatCompletion('main'); |
| 902 | addToChatCompletion('worldInfoAfter'); | 904 | await addToChatCompletion('worldInfoAfter'); |
| 903 | addToChatCompletion('charDescription'); | 905 | await addToChatCompletion('charDescription'); |
| 904 | addToChatCompletion('charPersonality'); | 906 | await addToChatCompletion('charPersonality'); |
| 905 | addToChatCompletion('scenario'); | 907 | await addToChatCompletion('scenario'); |
| 906 | addToChatCompletion('personaDescription'); | 908 | await addToChatCompletion('personaDescription'); |
| 907 | 909 | ||
| 908 | // Collection of control prompts that will always be positioned last | 910 | // Collection of control prompts that will always be positioned last |
| 909 | chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts); | 911 | chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts); |
| 910 | const controlPrompts = new MessageCollection('controlPrompts'); | 912 | const controlPrompts = new MessageCollection('controlPrompts'); |
| 911 | 913 | ||
| 912 | const impersonateMessage = Message.fromPrompt(prompts.get('impersonate')) ?? null; | 914 | const impersonateMessage = await Message.fromPromptAsync(prompts.get('impersonate')) ?? null; |
| 913 | if (type === 'impersonate') controlPrompts.add(impersonateMessage); | 915 | if (type === 'impersonate') controlPrompts.add(impersonateMessage); |
| 914 | 916 | ||
| 915 | // Add quiet prompt to control prompts | 917 | // Add quiet prompt to control prompts |
| 916 | // This should always be last, even in control prompts. Add all further control prompts BEFORE this prompt | 918 | // This should always be last, even in control prompts. Add all further control prompts BEFORE this prompt |
| 917 | const quietPromptMessage = Message.fromPrompt(prompts.get('quietPrompt')) ?? null; | 919 | const quietPromptMessage = await Message.fromPromptAsync(prompts.get('quietPrompt')) ?? null; |
| 918 | if (quietPromptMessage && quietPromptMessage.content) { | 920 | if (quietPromptMessage && quietPromptMessage.content) { |
| 919 | if (isImageInliningSupported() && quietImage) { | 921 | if (isImageInliningSupported() && quietImage) { |
| 920 | await quietPromptMessage.addImage(quietImage); | 922 | await quietPromptMessage.addImage(quietImage); |
| @@ -940,20 +942,23 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 940 | return acc; | 942 | return acc; |
| 941 | }, []); | 943 | }, []); |
| 942 | 944 | ||
| 943 | [...systemPrompts, ...userRelativePrompts].forEach(identifier => addToChatCompletion(identifier)); | 945 | for (const identifier of [...systemPrompts, ...userRelativePrompts]) { |
| 946 | await addToChatCompletion(identifier); | ||
| 947 | } | ||
| 944 | 948 | ||
| 945 | // Add enhance definition instruction | 949 | // Add enhance definition instruction |
| 946 | if (prompts.has('enhanceDefinitions')) addToChatCompletion('enhanceDefinitions'); | 950 | if (prompts.has('enhanceDefinitions')) await addToChatCompletion('enhanceDefinitions'); |
| 947 | 951 | ||
| 948 | // Bias | 952 | // Bias |
| 949 | if (bias && bias.trim().length) addToChatCompletion('bias'); | 953 | if (bias && bias.trim().length) await addToChatCompletion('bias'); |
| 950 | 954 | ||
| 951 | // Tavern Extras - Summary | 955 | // Tavern Extras - Summary |
| 952 | if (prompts.has('summary')) { | 956 | if (prompts.has('summary')) { |
| 953 | const summary = prompts.get('summary'); | 957 | const summary = prompts.get('summary'); |
| 954 | 958 | ||
| 955 | if (summary.position) { | 959 | if (summary.position) { |
| 956 | chatCompletion.insert(Message.fromPrompt(summary), 'main', summary.position); | 960 | const message = await Message.fromPromptAsync(summary); |
| 961 | chatCompletion.insert(message, 'main', summary.position); | ||
| 957 | } | 962 | } |
| 958 | } | 963 | } |
| 959 | 964 | ||
| @@ -962,7 +967,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 962 | const authorsNote = prompts.get('authorsNote'); | 967 | const authorsNote = prompts.get('authorsNote'); |
| 963 | 968 | ||
| 964 | if (authorsNote.position) { | 969 | if (authorsNote.position) { |
| 965 | chatCompletion.insert(Message.fromPrompt(authorsNote), 'main', authorsNote.position); | 970 | const message = await Message.fromPromptAsync(authorsNote); |
| 971 | chatCompletion.insert(message, 'main', authorsNote.position); | ||
| 966 | } | 972 | } |
| 967 | } | 973 | } |
| 968 | 974 | ||
| @@ -971,7 +977,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 971 | const vectorsMemory = prompts.get('vectorsMemory'); | 977 | const vectorsMemory = prompts.get('vectorsMemory'); |
| 972 | 978 | ||
| 973 | if (vectorsMemory.position) { | 979 | if (vectorsMemory.position) { |
| 974 | chatCompletion.insert(Message.fromPrompt(vectorsMemory), 'main', vectorsMemory.position); | 980 | const message = await Message.fromPromptAsync(vectorsMemory); |
| 981 | chatCompletion.insert(message, 'main', vectorsMemory.position); | ||
| 975 | } | 982 | } |
| 976 | } | 983 | } |
| 977 | 984 | ||
| @@ -980,7 +987,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 980 | const vectorsDataBank = prompts.get('vectorsDataBank'); | 987 | const vectorsDataBank = prompts.get('vectorsDataBank'); |
| 981 | 988 | ||
| 982 | if (vectorsDataBank.position) { | 989 | if (vectorsDataBank.position) { |
| 983 | chatCompletion.insert(Message.fromPrompt(vectorsDataBank), 'main', vectorsDataBank.position); | 990 | const message = await Message.fromPromptAsync(vectorsDataBank); |
| 991 | chatCompletion.insert(message, 'main', vectorsDataBank.position); | ||
| 984 | } | 992 | } |
| 985 | } | 993 | } |
| 986 | 994 | ||
| @@ -989,13 +997,24 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 989 | const smartContext = prompts.get('smartContext'); | 997 | const smartContext = prompts.get('smartContext'); |
| 990 | 998 | ||
| 991 | if (smartContext.position) { | 999 | if (smartContext.position) { |
| 992 | chatCompletion.insert(Message.fromPrompt(smartContext), 'main', smartContext.position); | 1000 | const message = await Message.fromPromptAsync(smartContext); |
| 1001 | chatCompletion.insert(message, 'main', smartContext.position); | ||
| 993 | } | 1002 | } |
| 994 | } | 1003 | } |
| 995 | 1004 | ||
| 996 | // Other relative extension prompts | 1005 | // Other relative extension prompts |
| 997 | for (const prompt of prompts.collection.filter(p => p.extension && p.position)) { | 1006 | for (const prompt of prompts.collection.filter(p => p.extension && p.position)) { |
| 998 | chatCompletion.insert(Message.fromPrompt(prompt), 'main', prompt.position); | 1007 | const message = await Message.fromPromptAsync(prompt); |
| 1008 | chatCompletion.insert(message, 'main', prompt.position); | ||
| 1009 | } | ||
| 1010 | |||
| 1011 | // Pre-allocation of tokens for tool data | ||
| 1012 | if (ToolManager.canPerformToolCalls(type)) { | ||
| 1013 | const toolData = {}; | ||
| 1014 | await ToolManager.registerFunctionToolsOpenAI(toolData); | ||
| 1015 | const toolMessage = [{ role: 'user', content: JSON.stringify(toolData) }]; | ||
| 1016 | const toolTokens = await tokenHandler.countAsync(toolMessage); | ||
| 1017 | chatCompletion.reserveBudget(toolTokens); | ||
| 999 | } | 1018 | } |
| 1000 | 1019 | ||
| 1001 | // Add in-chat injections | 1020 | // Add in-chat injections |
| @@ -1003,11 +1022,11 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 1003 | 1022 | ||
| 1004 | // Decide whether dialogue examples should always be added | 1023 | // Decide whether dialogue examples should always be added |
| 1005 | if (power_user.pin_examples) { | 1024 | if (power_user.pin_examples) { |
| 1006 | populateDialogueExamples(prompts, chatCompletion, messageExamples); | 1025 | await populateDialogueExamples(prompts, chatCompletion, messageExamples); |
| 1007 | await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt); | 1026 | await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt); |
| 1008 | } else { | 1027 | } else { |
| 1009 | await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt); | 1028 | await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt); |
| 1010 | populateDialogueExamples(prompts, chatCompletion, messageExamples); | 1029 | await populateDialogueExamples(prompts, chatCompletion, messageExamples); |
| 1011 | } | 1030 | } |
| 1012 | 1031 | ||
| 1013 | chatCompletion.freeBudget(controlPrompts); | 1032 | chatCompletion.freeBudget(controlPrompts); |
| @@ -1272,7 +1291,7 @@ export async function prepareOpenAIMessages({ | |||
| 1272 | promptManager.setChatCompletion(chatCompletion); | 1291 | promptManager.setChatCompletion(chatCompletion); |
| 1273 | 1292 | ||
| 1274 | if (oai_settings.squash_system_messages && dryRun == false) { | 1293 | if (oai_settings.squash_system_messages && dryRun == false) { |
| 1275 | chatCompletion.squashSystemMessages(); | 1294 | await chatCompletion.squashSystemMessages(); |
| 1276 | } | 1295 | } |
| 1277 | 1296 | ||
| 1278 | // All information is up-to-date, render. | 1297 | // All information is up-to-date, render. |
| @@ -2118,8 +2137,11 @@ async function calculateLogitBias() { | |||
| 2118 | } | 2137 | } |
| 2119 | 2138 | ||
| 2120 | class TokenHandler { | 2139 | class TokenHandler { |
| 2121 | constructor(countTokenFn) { | 2140 | /** |
| 2122 | this.countTokenFn = countTokenFn; | 2141 | * @param {(messages: object[] | object, full?: boolean) => Promise<number>} countTokenAsyncFn Function to count tokens |
| 2142 | */ | ||
| 2143 | constructor(countTokenAsyncFn) { | ||
| 2144 | this.countTokenAsyncFn = countTokenAsyncFn; | ||
| 2123 | this.counts = { | 2145 | this.counts = { |
| 2124 | 'start_chat': 0, | 2146 | 'start_chat': 0, |
| 2125 | 'prompt': 0, | 2147 | 'prompt': 0, |
| @@ -2148,8 +2170,15 @@ class TokenHandler { | |||
| 2148 | this.counts[type] -= value; | 2170 | this.counts[type] -= value; |
| 2149 | } | 2171 | } |
| 2150 | 2172 | ||
| 2151 | count(messages, full, type) { | 2173 | /** |
| 2152 | const token_count = this.countTokenFn(messages, full); | 2174 | * Count tokens for a message or messages. |
| 2175 | * @param {object|any[]} messages Messages to count tokens for | ||
| 2176 | * @param {boolean} [full] Count full tokens | ||
| 2177 | * @param {string} [type] Identifier for the token count | ||
| 2178 | * @returns {Promise<number>} The token count | ||
| 2179 | */ | ||
| 2180 | async countAsync(messages, full, type) { | ||
| 2181 | const token_count = await this.countTokenAsyncFn(messages, full); | ||
| 2153 | this.counts[type] += token_count; | 2182 | this.counts[type] += token_count; |
| 2154 | 2183 | ||
| 2155 | return token_count; | 2184 | return token_count; |
| @@ -2169,7 +2198,7 @@ class TokenHandler { | |||
| 2169 | } | 2198 | } |
| 2170 | 2199 | ||
| 2171 | 2200 | ||
| 2172 | const tokenHandler = new TokenHandler(countTokensOpenAI); | 2201 | const tokenHandler = new TokenHandler(countTokensOpenAIAsync); |
| 2173 | 2202 | ||
| 2174 | // Thrown by ChatCompletion when a requested prompt couldn't be found. | 2203 | // Thrown by ChatCompletion when a requested prompt couldn't be found. |
| 2175 | class IdentifierNotFoundError extends Error { | 2204 | class IdentifierNotFoundError extends Error { |
| @@ -2219,6 +2248,7 @@ class Message { | |||
| 2219 | * @param {string} role - The role of the entity creating the message. | 2248 | * @param {string} role - The role of the entity creating the message. |
| 2220 | * @param {string} content - The actual content of the message. | 2249 | * @param {string} content - The actual content of the message. |
| 2221 | * @param {string} identifier - A unique identifier for the message. | 2250 | * @param {string} identifier - A unique identifier for the message. |
| 2251 | * @private Don't use this constructor directly. Use createAsync instead. | ||
| 2222 | */ | 2252 | */ |
| 2223 | constructor(role, content, identifier) { | 2253 | constructor(role, content, identifier) { |
| 2224 | this.identifier = identifier; | 2254 | this.identifier = identifier; |
| @@ -2230,18 +2260,32 @@ class Message { | |||
| 2230 | this.role = 'system'; | 2260 | this.role = 'system'; |
| 2231 | } | 2261 | } |
| 2232 | 2262 | ||
| 2233 | if (typeof this.content === 'string' && this.content.length > 0) { | 2263 | this.tokens = 0; |
| 2234 | this.tokens = tokenHandler.count({ role: this.role, content: this.content }); | 2264 | } |
| 2235 | } else { | 2265 | |
| 2236 | this.tokens = 0; | 2266 | /** |
| 2267 | * Create a new Message instance. | ||
| 2268 | * @param {string} role | ||
| 2269 | * @param {string} content | ||
| 2270 | * @param {string} identifier | ||
| 2271 | * @returns {Promise<Message>} Message instance | ||
| 2272 | */ | ||
| 2273 | static async createAsync(role, content, identifier) { | ||
| 2274 | const message = new Message(role, content, identifier); | ||
| 2275 | |||
| 2276 | if (typeof message.content === 'string' && message.content.length > 0) { | ||
| 2277 | message.tokens = await tokenHandler.countAsync({ role: message.role, content: message.content }); | ||
| 2237 | } | 2278 | } |
| 2279 | |||
| 2280 | return message; | ||
| 2238 | } | 2281 | } |
| 2239 | 2282 | ||
| 2240 | /** | 2283 | /** |
| 2241 | * Reconstruct the message from a tool invocation. | 2284 | * Reconstruct the message from a tool invocation. |
| 2242 | * @param {import('./tool-calling.js').ToolInvocation[]} invocations | 2285 | * @param {import('./tool-calling.js').ToolInvocation[]} invocations - The tool invocations to reconstruct the message from. |
| 2286 | * @returns {Promise<void>} | ||
| 2243 | */ | 2287 | */ |
| 2244 | setToolCalls(invocations) { | 2288 | async setToolCalls(invocations) { |
| 2245 | this.tool_calls = invocations.map(i => ({ | 2289 | this.tool_calls = invocations.map(i => ({ |
| 2246 | id: i.id, | 2290 | id: i.id, |
| 2247 | type: 'function', | 2291 | type: 'function', |
| @@ -2250,14 +2294,24 @@ class Message { | |||
| 2250 | name: i.name, | 2294 | name: i.name, |
| 2251 | }, | 2295 | }, |
| 2252 | })); | 2296 | })); |
| 2253 | this.tokens = tokenHandler.count({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) }); | 2297 | this.tokens = await tokenHandler.countAsync({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) }); |
| 2254 | } | 2298 | } |
| 2255 | 2299 | ||
| 2256 | setName(name) { | 2300 | /** |
| 2301 | * Add a name to the message. | ||
| 2302 | * @param {string} name Name to set for the message. | ||
| 2303 | * @returns {Promise<void>} | ||
| 2304 | */ | ||
| 2305 | async setName(name) { | ||
| 2257 | this.name = name; | 2306 | this.name = name; |
| 2258 | this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name }); | 2307 | this.tokens = await tokenHandler.countAsync({ role: this.role, content: this.content, name: this.name }); |
| 2259 | } | 2308 | } |
| 2260 | 2309 | ||
| 2310 | /** | ||
| 2311 | * Adds an image to the message. | ||
| 2312 | * @param {string} image Image URL or Data URL. | ||
| 2313 | * @returns {Promise<void>} | ||
| 2314 | */ | ||
| 2261 | async addImage(image) { | 2315 | async addImage(image) { |
| 2262 | const textContent = this.content; | 2316 | const textContent = this.content; |
| 2263 | const isDataUrl = isDataURL(image); | 2317 | const isDataUrl = isDataURL(image); |
| @@ -2347,13 +2401,13 @@ class Message { | |||
| 2347 | } | 2401 | } |
| 2348 | 2402 | ||
| 2349 | /** | 2403 | /** |
| 2350 | * Create a new Message instance from a prompt. | 2404 | * Create a new Message instance from a prompt asynchronously. |
| 2351 | * @static | 2405 | * @static |
| 2352 | * @param {Object} prompt - The prompt object. | 2406 | * @param {Object} prompt - The prompt object. |
| 2353 | * @returns {Message} A new instance of Message. | 2407 | * @returns {Promise<Message>} A new instance of Message. |
| 2354 | */ | 2408 | */ |
| 2355 | static fromPrompt(prompt) { | 2409 | static fromPromptAsync(prompt) { |
| 2356 | return new Message(prompt.role, prompt.content, prompt.identifier); | 2410 | return Message.createAsync(prompt.role, prompt.content, prompt.identifier); |
| 2357 | } | 2411 | } |
| 2358 | 2412 | ||
| 2359 | /** | 2413 | /** |
| @@ -2479,8 +2533,9 @@ export class ChatCompletion { | |||
| 2479 | 2533 | ||
| 2480 | /** | 2534 | /** |
| 2481 | * Combines consecutive system messages into one if they have no name attached. | 2535 | * Combines consecutive system messages into one if they have no name attached. |
| 2536 | * @returns {Promise<void>} | ||
| 2482 | */ | 2537 | */ |
| 2483 | squashSystemMessages() { | 2538 | async squashSystemMessages() { |
| 2484 | const excludeList = ['newMainChat', 'newChat', 'groupNudge']; | 2539 | const excludeList = ['newMainChat', 'newChat', 'groupNudge']; |
| 2485 | this.messages.collection = this.messages.flatten(); | 2540 | this.messages.collection = this.messages.flatten(); |
| 2486 | 2541 | ||
| @@ -2500,7 +2555,7 @@ export class ChatCompletion { | |||
| 2500 | if (shouldSquash(message)) { | 2555 | if (shouldSquash(message)) { |
| 2501 | if (lastMessage && shouldSquash(lastMessage)) { | 2556 | if (lastMessage && shouldSquash(lastMessage)) { |
| 2502 | lastMessage.content += '\n' + message.content; | 2557 | lastMessage.content += '\n' + message.content; |
| 2503 | lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content }); | 2558 | lastMessage.tokens = await tokenHandler.countAsync({ role: lastMessage.role, content: lastMessage.content }); |
| 2504 | } | 2559 | } |
| 2505 | else { | 2560 | else { |
| 2506 | squashedMessages.push(message); | 2561 | squashedMessages.push(message); |
| @@ -182,6 +182,14 @@ export class Popup { | |||
| 182 | if (!this.dlg.showModal) { | 182 | if (!this.dlg.showModal) { |
| 183 | this.dlg.classList.add('poly_dialog'); | 183 | this.dlg.classList.add('poly_dialog'); |
| 184 | dialogPolyfill.registerDialog(this.dlg); | 184 | dialogPolyfill.registerDialog(this.dlg); |
| 185 | // Force a vertical reposition after the content | ||
| 186 | // (like crop image) has been set | ||
| 187 | const resizeObserver = new ResizeObserver((entries) => { | ||
| 188 | for (const entry of entries) { | ||
| 189 | dialogPolyfill.reposition(entry.target); | ||
| 190 | } | ||
| 191 | }); | ||
| 192 | resizeObserver.observe(this.dlg); | ||
| 185 | } | 193 | } |
| 186 | this.body = this.dlg.querySelector('.popup-body'); | 194 | this.body = this.dlg.querySelector('.popup-body'); |
| 187 | this.content = this.dlg.querySelector('.popup-content'); | 195 | this.content = this.dlg.querySelector('.popup-content'); |
| @@ -4,7 +4,6 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from | |||
| 4 | import { tokenizers } from './tokenizers.js'; | 4 | import { tokenizers } from './tokenizers.js'; |
| 5 | import { renderTemplateAsync } from './templates.js'; | 5 | import { renderTemplateAsync } from './templates.js'; |
| 6 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 6 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 7 | import { PAGINATION_TEMPLATE } from './utils.js'; | ||
| 8 | 7 | ||
| 9 | let mancerModels = []; | 8 | let mancerModels = []; |
| 10 | let togetherModels = []; | 9 | let togetherModels = []; |
| @@ -1,6 +1,5 @@ | |||
| 1 | import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js'; | 1 | import { chat_metadata, getCurrentChatId, saveSettingsDebounced } from '../script.js'; |
| 2 | import { extension_settings, saveMetadataDebounced } from './extensions.js'; | 2 | import { extension_settings, saveMetadataDebounced } from './extensions.js'; |
| 3 | import { callGenericPopup, POPUP_TYPE } from './popup.js'; | ||
| 4 | import { executeSlashCommandsWithOptions } from './slash-commands.js'; | 3 | import { executeSlashCommandsWithOptions } from './slash-commands.js'; |
| 5 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 4 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| 6 | import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; | 5 | import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; |
| @@ -514,7 +513,8 @@ export function parseBooleanOperands(args) { | |||
| 514 | return ''; | 513 | return ''; |
| 515 | } | 514 | } |
| 516 | 515 | ||
| 517 | const operandNumber = Number(operand); | 516 | // parseFloat will return NaN for spaces. |
| 517 | const operandNumber = parseFloat(operand); | ||
| 518 | 518 | ||
| 519 | if (!isNaN(operandNumber)) { | 519 | if (!isNaN(operandNumber)) { |
| 520 | return operandNumber; | 520 | return operandNumber; |
| @@ -836,6 +836,40 @@ function randValuesCallback(from, to, args) { | |||
| 836 | return value; | 836 | return value; |
| 837 | } | 837 | } |
| 838 | 838 | ||
| 839 | function customSortComparitor(a, b) { | ||
| 840 | if (typeof a != typeof b) { | ||
| 841 | a = typeof a; | ||
| 842 | b = typeof b; | ||
| 843 | } | ||
| 844 | return a > b ? 1 : a < b ? -1 : 0; | ||
| 845 | } | ||
| 846 | |||
| 847 | function sortArrayObjectCallback(args, value) { | ||
| 848 | let parsedValue; | ||
| 849 | if (typeof value == 'string') { | ||
| 850 | try { | ||
| 851 | parsedValue = JSON.parse(value); | ||
| 852 | } catch { | ||
| 853 | // return the original input if it was invalid | ||
| 854 | return value; | ||
| 855 | } | ||
| 856 | } else { | ||
| 857 | parsedValue = value; | ||
| 858 | } | ||
| 859 | if (Array.isArray(parsedValue)) { | ||
| 860 | // always sort lists by value | ||
| 861 | parsedValue.sort(customSortComparitor); | ||
| 862 | } else if (typeof parsedValue == 'object') { | ||
| 863 | let keysort = args.keysort; | ||
| 864 | if (isFalseBoolean(keysort)) { | ||
| 865 | parsedValue = Object.keys(parsedValue).sort(function (a, b) { return customSortComparitor(parsedValue[a], parsedValue[b]); }); | ||
| 866 | } else { | ||
| 867 | parsedValue = Object.keys(parsedValue).sort(customSortComparitor); | ||
| 868 | } | ||
| 869 | } | ||
| 870 | return JSON.stringify(parsedValue); | ||
| 871 | } | ||
| 872 | |||
| 839 | /** | 873 | /** |
| 840 | * Declare a new variable in the current scope. | 874 | * Declare a new variable in the current scope. |
| 841 | * @param {NamedArguments} args Named arguments. | 875 | * @param {NamedArguments} args Named arguments. |
| @@ -2110,6 +2144,51 @@ export function registerVariableCommands() { | |||
| 2110 | `, | 2144 | `, |
| 2111 | })); | 2145 | })); |
| 2112 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 2146 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 2147 | name: 'sort', | ||
| 2148 | callback: sortArrayObjectCallback, | ||
| 2149 | returns: 'the sorted list or dictionary keys', | ||
| 2150 | namedArgumentList: [ | ||
| 2151 | SlashCommandNamedArgument.fromProps({ name: 'keysort', | ||
| 2152 | description: 'whether to sort by key or value; ignored for lists', | ||
| 2153 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 2154 | enumList: ['true', 'false'], | ||
| 2155 | defaultValue: 'true', | ||
| 2156 | }), | ||
| 2157 | ], | ||
| 2158 | unnamedArgumentList: [ | ||
| 2159 | SlashCommandArgument.fromProps({ | ||
| 2160 | description: 'value', | ||
| 2161 | typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], | ||
| 2162 | isRequired: true, | ||
| 2163 | forceEnum: false, | ||
| 2164 | }), | ||
| 2165 | ], | ||
| 2166 | helpString: ` | ||
| 2167 | <div> | ||
| 2168 | Sorts a list or dictionary in ascending order and passes the result down the pipe. | ||
| 2169 | <ul> | ||
| 2170 | <li> | ||
| 2171 | For lists, returns the list sorted by value. | ||
| 2172 | </li> | ||
| 2173 | <li> | ||
| 2174 | For dictionaries, returns the ordered list of keys after sorting. Setting keysort=false means keys are sorted by associated value. | ||
| 2175 | </li> | ||
| 2176 | </ul> | ||
| 2177 | </div> | ||
| 2178 | <div> | ||
| 2179 | <strong>Examples:</strong> | ||
| 2180 | <ul> | ||
| 2181 | <li> | ||
| 2182 | <pre><code class="language-stscript">/sort [5,3,4,1,2] | /echo</code></pre> | ||
| 2183 | </li> | ||
| 2184 | <li> | ||
| 2185 | <pre><code class="language-stscript">/sort keysort=false {"a": 1, "d": 3, "c": 2, "b": 5} | /echo</code></pre> | ||
| 2186 | </li> | ||
| 2187 | </ul> | ||
| 2188 | </div> | ||
| 2189 | `, | ||
| 2190 | })); | ||
| 2191 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 2113 | name: 'rand', | 2192 | name: 'rand', |
| 2114 | callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)), | 2193 | callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)), |
| 2115 | returns: 'random number', | 2194 | returns: 'random number', |
| @@ -130,9 +130,9 @@ const KNOWN_DECORATORS = ['@@activate', '@@dont_activate']; | |||
| 130 | */ | 130 | */ |
| 131 | class WorldInfoBuffer { | 131 | class WorldInfoBuffer { |
| 132 | /** | 132 | /** |
| 133 | * @type {object[]} Array of entries that need to be activated no matter what | 133 | * @type {Map<string, object>} Map of entries that need to be activated no matter what |
| 134 | */ | 134 | */ |
| 135 | static externalActivations = []; | 135 | static externalActivations = new Map(); |
| 136 | 136 | ||
| 137 | /** | 137 | /** |
| 138 | * @type {string[]} Array of messages sorted by ascending depth | 138 | * @type {string[]} Array of messages sorted by ascending depth |
| @@ -311,20 +311,19 @@ class WorldInfoBuffer { | |||
| 311 | } | 311 | } |
| 312 | 312 | ||
| 313 | /** | 313 | /** |
| 314 | * Check if the current entry is externally activated. | 314 | * Get the externally activated version of the entry, if there is one. |
| 315 | * @param {object} entry WI entry to check | 315 | * @param {object} entry WI entry to check |
| 316 | * @returns {boolean} True if the entry is forcefully activated | 316 | * @returns {object|undefined} the external version if the entry is forcefully activated, undefined otherwise |
| 317 | */ | 317 | */ |
| 318 | isExternallyActivated(entry) { | 318 | getExternallyActivated(entry) { |
| 319 | // Entries could be copied with structuredClone, so we need to compare them by string representation | 319 | return WorldInfoBuffer.externalActivations.get(`${entry.world}.${entry.uid}`); |
| 320 | return WorldInfoBuffer.externalActivations.some(x => JSON.stringify(x) === JSON.stringify(entry)); | ||
| 321 | } | 320 | } |
| 322 | 321 | ||
| 323 | /** | 322 | /** |
| 324 | * Clean-up the external effects for entries. | 323 | * Clean-up the external effects for entries. |
| 325 | */ | 324 | */ |
| 326 | resetExternalEffects() { | 325 | resetExternalEffects() { |
| 327 | WorldInfoBuffer.externalActivations.splice(0, WorldInfoBuffer.externalActivations.length); | 326 | WorldInfoBuffer.externalActivations = new Map(); |
| 328 | } | 327 | } |
| 329 | 328 | ||
| 330 | /** | 329 | /** |
| @@ -751,7 +750,7 @@ export async function getWorldInfoPrompt(chat, maxContext, isDryRun) { | |||
| 751 | worldInfoString = worldInfoBefore + worldInfoAfter; | 750 | worldInfoString = worldInfoBefore + worldInfoAfter; |
| 752 | 751 | ||
| 753 | if (!isDryRun && activatedWorldInfo.allActivatedEntries && activatedWorldInfo.allActivatedEntries.size > 0) { | 752 | if (!isDryRun && activatedWorldInfo.allActivatedEntries && activatedWorldInfo.allActivatedEntries.size > 0) { |
| 754 | const arg = Array.from(activatedWorldInfo.allActivatedEntries); | 753 | const arg = Array.from(activatedWorldInfo.allActivatedEntries.values()); |
| 755 | await eventSource.emit(event_types.WORLD_INFO_ACTIVATED, arg); | 754 | await eventSource.emit(event_types.WORLD_INFO_ACTIVATED, arg); |
| 756 | } | 755 | } |
| 757 | 756 | ||
| @@ -868,7 +867,14 @@ export function setWorldInfoSettings(settings, data) { | |||
| 868 | }); | 867 | }); |
| 869 | 868 | ||
| 870 | eventSource.on(event_types.WORLDINFO_FORCE_ACTIVATE, (entries) => { | 869 | eventSource.on(event_types.WORLDINFO_FORCE_ACTIVATE, (entries) => { |
| 871 | WorldInfoBuffer.externalActivations.push(...entries); | 870 | for (const entry of entries) { |
| 871 | if (!Object.hasOwn(entry, 'world') || !Object.hasOwn(entry, 'uid')) { | ||
| 872 | console.error('[WI] WORLDINFO_FORCE_ACTIVATE requires all entries to have both world and uid fields, entry IGNORED', entry); | ||
| 873 | } else { | ||
| 874 | WorldInfoBuffer.externalActivations.set(`${entry.world}.${entry.uid}`, entry); | ||
| 875 | console.log('[WI] WORLDINFO_FORCE_ACTIVATE added entry', entry); | ||
| 876 | } | ||
| 877 | } | ||
| 872 | }); | 878 | }); |
| 873 | 879 | ||
| 874 | // Add slash commands | 880 | // Add slash commands |
| @@ -1680,8 +1686,8 @@ export function sortWorldInfoEntries(data, { customSort = null } = {}) { | |||
| 1680 | } else if (sortRule === 'priority') { | 1686 | } else if (sortRule === 'priority') { |
| 1681 | // First constant, then normal, then disabled. | 1687 | // First constant, then normal, then disabled. |
| 1682 | primarySort = (a, b) => { | 1688 | primarySort = (a, b) => { |
| 1683 | const aValue = a.constant ? 0 : a.disable ? 2 : 1; | 1689 | const aValue = a.disable ? 2 : a.constant ? 0 : 1; |
| 1684 | const bValue = b.constant ? 0 : b.disable ? 2 : 1; | 1690 | const bValue = b.disable ? 2 : b.constant ? 0 : 1; |
| 1685 | return aValue - bValue; | 1691 | return aValue - bValue; |
| 1686 | }; | 1692 | }; |
| 1687 | } else { | 1693 | } else { |
| @@ -3724,7 +3730,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 3724 | let scanState = scan_state.INITIAL; | 3730 | let scanState = scan_state.INITIAL; |
| 3725 | let token_budget_overflowed = false; | 3731 | let token_budget_overflowed = false; |
| 3726 | let count = 0; | 3732 | let count = 0; |
| 3727 | let allActivatedEntries = new Set(); | 3733 | let allActivatedEntries = new Map(); |
| 3728 | let failedProbabilityChecks = new Set(); | 3734 | let failedProbabilityChecks = new Set(); |
| 3729 | let allActivatedText = ''; | 3735 | let allActivatedText = ''; |
| 3730 | 3736 | ||
| @@ -3789,7 +3795,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 3789 | } | 3795 | } |
| 3790 | 3796 | ||
| 3791 | // Already processed, considered and then skipped entries should still be skipped | 3797 | // Already processed, considered and then skipped entries should still be skipped |
| 3792 | if (failedProbabilityChecks.has(entry) || allActivatedEntries.has(entry)) { | 3798 | if (failedProbabilityChecks.has(entry) || allActivatedEntries.has(`${entry.world}.${entry.uid}`)) { |
| 3793 | continue; | 3799 | continue; |
| 3794 | } | 3800 | } |
| 3795 | 3801 | ||
| @@ -3869,15 +3875,15 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 3869 | continue; | 3875 | continue; |
| 3870 | } | 3876 | } |
| 3871 | 3877 | ||
| 3872 | // Now do checks for immediate activations | 3878 | if (buffer.getExternallyActivated(entry)) { |
| 3873 | if (entry.constant) { | 3879 | log('externally activated'); |
| 3874 | log('activated because of constant'); | 3880 | activatedNow.add(buffer.getExternallyActivated(entry)); |
| 3875 | activatedNow.add(entry); | ||
| 3876 | continue; | 3881 | continue; |
| 3877 | } | 3882 | } |
| 3878 | 3883 | ||
| 3879 | if (buffer.isExternallyActivated(entry)) { | 3884 | // Now do checks for immediate activations |
| 3880 | log('externally activated'); | 3885 | if (entry.constant) { |
| 3886 | log('activated because of constant'); | ||
| 3881 | activatedNow.add(entry); | 3887 | activatedNow.add(entry); |
| 3882 | continue; | 3888 | continue; |
| 3883 | } | 3889 | } |
| @@ -4039,7 +4045,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 4039 | break; | 4045 | break; |
| 4040 | } | 4046 | } |
| 4041 | 4047 | ||
| 4042 | allActivatedEntries.add(entry); | 4048 | allActivatedEntries.set(`${entry.world}.${entry.uid}`, entry); |
| 4043 | console.debug(`[WI] Entry ${entry.uid} activation successful, adding to prompt`, entry); | 4049 | console.debug(`[WI] Entry ${entry.uid} activation successful, adding to prompt`, entry); |
| 4044 | } | 4050 | } |
| 4045 | 4051 | ||
| @@ -4123,7 +4129,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 4123 | 4129 | ||
| 4124 | // Appends from insertion order 999 to 1. Use unshift for this purpose | 4130 | // Appends from insertion order 999 to 1. Use unshift for this purpose |
| 4125 | // TODO (kingbri): Change to use WI Anchor positioning instead of separate top/bottom arrays | 4131 | // TODO (kingbri): Change to use WI Anchor positioning instead of separate top/bottom arrays |
| 4126 | [...allActivatedEntries].sort(sortFn).forEach((entry) => { | 4132 | [...allActivatedEntries.values()].sort(sortFn).forEach((entry) => { |
| 4127 | const regexDepth = entry.position === world_info_position.atDepth ? (entry.depth ?? DEFAULT_DEPTH) : null; | 4133 | const regexDepth = entry.position === world_info_position.atDepth ? (entry.depth ?? DEFAULT_DEPTH) : null; |
| 4128 | const content = getRegexedString(entry.content, regex_placement.WORLD_INFO, { depth: regexDepth, isMarkdown: false, isPrompt: true }); | 4134 | const content = getRegexedString(entry.content, regex_placement.WORLD_INFO, { depth: regexDepth, isMarkdown: false, isPrompt: true }); |
| 4129 | 4135 | ||
| @@ -4182,14 +4188,14 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 4182 | context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]); | 4188 | context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]); |
| 4183 | } | 4189 | } |
| 4184 | 4190 | ||
| 4185 | !isDryRun && timedEffects.setTimedEffects(Array.from(allActivatedEntries)); | 4191 | !isDryRun && timedEffects.setTimedEffects(Array.from(allActivatedEntries.values())); |
| 4186 | buffer.resetExternalEffects(); | 4192 | buffer.resetExternalEffects(); |
| 4187 | timedEffects.cleanUp(); | 4193 | timedEffects.cleanUp(); |
| 4188 | 4194 | ||
| 4189 | console.log(`[WI] Adding ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries)); | 4195 | console.log(`[WI] Adding ${allActivatedEntries.size} entries to prompt`, Array.from(allActivatedEntries.values())); |
| 4190 | console.debug('[WI] --- DONE ---'); | 4196 | console.debug('[WI] --- DONE ---'); |
| 4191 | 4197 | ||
| 4192 | return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries }; | 4198 | return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries: new Set(allActivatedEntries.values()) }; |
| 4193 | } | 4199 | } |
| 4194 | 4200 | ||
| 4195 | /** | 4201 | /** |
| @@ -4291,7 +4297,7 @@ function filterGroupsByTimedEffects(groups, timedEffects, removeEntry) { | |||
| 4291 | /** | 4297 | /** |
| 4292 | * Filters entries by inclusion groups. | 4298 | * Filters entries by inclusion groups. |
| 4293 | * @param {object[]} newEntries Entries activated on current recursion level | 4299 | * @param {object[]} newEntries Entries activated on current recursion level |
| 4294 | * @param {Set<object>} allActivatedEntries Set of all activated entries | 4300 | * @param {Map<string, object>} allActivatedEntries Map of all activated entries |
| 4295 | * @param {WorldInfoBuffer} buffer The buffer to use for scanning | 4301 | * @param {WorldInfoBuffer} buffer The buffer to use for scanning |
| 4296 | * @param {number} scanState The current scan state | 4302 | * @param {number} scanState The current scan state |
| 4297 | * @param {WorldInfoTimedEffects} timedEffects The timed effects currently active | 4303 | * @param {WorldInfoTimedEffects} timedEffects The timed effects currently active |
| @@ -4339,7 +4345,7 @@ function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanSt | |||
| 4339 | continue; | 4345 | continue; |
| 4340 | } | 4346 | } |
| 4341 | 4347 | ||
| 4342 | if (Array.from(allActivatedEntries).some(x => x.group === key)) { | 4348 | if (Array.from(allActivatedEntries.values()).some(x => x.group === key)) { |
| 4343 | console.debug(`[WI] Skipping inclusion group check, group '${key}' was already activated`); | 4349 | console.debug(`[WI] Skipping inclusion group check, group '${key}' was already activated`); |
| 4344 | // We need to forcefully deactivate all other entries in the group | 4350 | // We need to forcefully deactivate all other entries in the group |
| 4345 | removeAllBut(group, null, false); | 4351 | removeAllBut(group, null, false); |
| @@ -4559,6 +4565,7 @@ function convertCharacterBook(characterBook) { | |||
| 4559 | sticky: entry.extensions?.sticky ?? null, | 4565 | sticky: entry.extensions?.sticky ?? null, |
| 4560 | cooldown: entry.extensions?.cooldown ?? null, | 4566 | cooldown: entry.extensions?.cooldown ?? null, |
| 4561 | delay: entry.extensions?.delay ?? null, | 4567 | delay: entry.extensions?.delay ?? null, |
| 4568 | extensions: entry.extensions ?? {}, | ||
| 4562 | }; | 4569 | }; |
| 4563 | }); | 4570 | }); |
| 4564 | 4571 | ||
| @@ -613,6 +613,15 @@ body .panelControlBar { | |||
| 613 | min-width: 55px; | 613 | min-width: 55px; |
| 614 | justify-content: flex-end; | 614 | justify-content: flex-end; |
| 615 | gap: 0px; | 615 | gap: 0px; |
| 616 | transition: all 250ms; | ||
| 617 | } | ||
| 618 | |||
| 619 | .zoomed_avatar .panelControlBar { | ||
| 620 | opacity:0; | ||
| 621 | } | ||
| 622 | |||
| 623 | .zoomed_avatar:hover .panelControlBar { | ||
| 624 | opacity:1; | ||
| 616 | } | 625 | } |
| 617 | 626 | ||
| 618 | .panelControlBar .drag-grabber { | 627 | .panelControlBar .drag-grabber { |
| @@ -681,9 +690,9 @@ body .panelControlBar { | |||
| 681 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); | 690 | backdrop-filter: blur(var(--SmartThemeBlurStrength)); |
| 682 | } | 691 | } |
| 683 | 692 | ||
| 684 | #send_form.no-connection { | 693 | /* #send_form.no-connection { |
| 685 | background-color: var(--crimson70a) !important; | 694 | background-color: var(--crimson70a) !important; |
| 686 | } | 695 | } */ |
| 687 | 696 | ||
| 688 | #nonQRFormItems { | 697 | #nonQRFormItems { |
| 689 | padding: 0; | 698 | padding: 0; |
| @@ -2953,10 +2962,11 @@ input[type=search]:focus::-webkit-search-cancel-button { | |||
| 2953 | bottom: 0; | 2962 | bottom: 0; |
| 2954 | position: relative; | 2963 | position: relative; |
| 2955 | word-break: break-word; | 2964 | word-break: break-word; |
| 2956 | background-color: var(--black50a); | 2965 | background-color: var(--SmartThemeBlurTintColor); |
| 2957 | font-size: calc(var(--fontScale) * 0.9em); | 2966 | font-size: calc(var(--fontScale) * 0.9em); |
| 2958 | max-height: 50%; | 2967 | max-height: 50%; |
| 2959 | overflow-y: clip; | 2968 | overflow-y: clip; |
| 2969 | border-radius: 0 0 7px 7px; | ||
| 2960 | } | 2970 | } |
| 2961 | 2971 | ||
| 2962 | .bg_example[custom="true"] .BGSampleTitle { | 2972 | .bg_example[custom="true"] .BGSampleTitle { |
| @@ -3423,7 +3433,7 @@ grammarly-extension { | |||
| 3423 | 3433 | ||
| 3424 | .menu_button { | 3434 | .menu_button { |
| 3425 | color: var(--SmartThemeBodyColor); | 3435 | color: var(--SmartThemeBodyColor); |
| 3426 | background-color: var(--black50a); | 3436 | background-color: var(--SmartThemeBlurTintColor); |
| 3427 | border: 1px solid var(--SmartThemeBorderColor); | 3437 | border: 1px solid var(--SmartThemeBorderColor); |
| 3428 | border-radius: 5px; | 3438 | border-radius: 5px; |
| 3429 | padding: 3px 5px; | 3439 | padding: 3px 5px; |
| @@ -3953,8 +3963,8 @@ input[type="range"]::-webkit-slider-thumb { | |||
| 3953 | opacity: 0.5; | 3963 | opacity: 0.5; |
| 3954 | margin: 0 5px; | 3964 | margin: 0 5px; |
| 3955 | text-align: center; | 3965 | text-align: center; |
| 3956 | border-radius: 100%; | 3966 | /* border-radius: 100%; |
| 3957 | box-shadow: 0 0 3px black; | 3967 | box-shadow: 0 0 3px black; */ |
| 3958 | transition: all 250ms; | 3968 | transition: all 250ms; |
| 3959 | } | 3969 | } |
| 3960 | 3970 | ||
| @@ -4542,9 +4552,14 @@ h2 { | |||
| 4542 | 4552 | ||
| 4543 | a { | 4553 | a { |
| 4544 | color: orange; | 4554 | color: orange; |
| 4555 | color: var(--SmartThemeQuoteColor); | ||
| 4545 | text-decoration: none; | 4556 | text-decoration: none; |
| 4546 | } | 4557 | } |
| 4547 | 4558 | ||
| 4559 | a:hover { | ||
| 4560 | filter: brightness(1.25); | ||
| 4561 | } | ||
| 4562 | |||
| 4548 | #export_format_popup { | 4563 | #export_format_popup { |
| 4549 | display: none; | 4564 | display: none; |
| 4550 | z-index: 9999; | 4565 | z-index: 9999; |
| @@ -1,7 +1,13 @@ | |||
| 1 | const yaml = require('yaml'); | 1 | import fs from 'node:fs'; |
| 2 | const fs = require('fs'); | 2 | import process from 'node:process'; |
| 3 | const storage = require('node-persist'); | 3 | import yaml from 'yaml'; |
| 4 | const users = require('./src/users'); | 4 | import storage from 'node-persist'; |
| 5 | import { | ||
| 6 | initUserStorage, | ||
| 7 | getPasswordSalt, | ||
| 8 | getPasswordHash, | ||
| 9 | toKey, | ||
| 10 | } from './src/users.js'; | ||
| 5 | 11 | ||
| 6 | const userAccount = process.argv[2]; | 12 | const userAccount = process.argv[2]; |
| 7 | const userPassword = process.argv[3]; | 13 | const userPassword = process.argv[3]; |
| @@ -22,7 +28,7 @@ async function initStorage() { | |||
| 22 | process.exit(1); | 28 | process.exit(1); |
| 23 | } | 29 | } |
| 24 | 30 | ||
| 25 | await users.initUserStorage(dataRoot); | 31 | await initUserStorage(dataRoot); |
| 26 | } | 32 | } |
| 27 | 33 | ||
| 28 | async function main() { | 34 | async function main() { |
| @@ -31,22 +37,22 @@ async function main() { | |||
| 31 | /** | 37 | /** |
| 32 | * @type {import('./src/users').User} | 38 | * @type {import('./src/users').User} |
| 33 | */ | 39 | */ |
| 34 | const user = await storage.get(users.toKey(userAccount)); | 40 | const user = await storage.get(toKey(userAccount)); |
| 35 | 41 | ||
| 36 | if (!user) { | 42 | if (!user) { |
| 37 | console.error(`User "${userAccount}" not found.`); | 43 | console.error(`User "${userAccount}" not found.`); |
| 38 | process.exit(1); | 44 | process.exit(1); |
| 39 | } | 45 | } |
| 40 | 46 | ||
| 41 | if (!user.enabled) { | 47 | if (!user.enabled) { |
| 42 | console.log('User is disabled. Enabling...'); | 48 | console.log('User is disabled. Enabling...'); |
| 43 | user.enabled = true; | 49 | user.enabled = true; |
| 44 | } | 50 | } |
| 45 | 51 | ||
| 46 | if (userPassword) { | 52 | if (userPassword) { |
| 47 | console.log('Setting new password...'); | 53 | console.log('Setting new password...'); |
| 48 | const salt = users.getPasswordSalt(); | 54 | const salt = getPasswordSalt(); |
| 49 | const passwordHash = users.getPasswordHash(userPassword, salt); | 55 | const passwordHash = getPasswordHash(userPassword, salt); |
| 50 | user.password = passwordHash; | 56 | user.password = passwordHash; |
| 51 | user.salt = salt; | 57 | user.salt = salt; |
| 52 | } else { | 58 | } else { |
| @@ -55,7 +61,7 @@ async function main() { | |||
| 55 | user.salt = ''; | 61 | user.salt = ''; |
| 56 | } | 62 | } |
| 57 | 63 | ||
| 58 | await storage.setItem(users.toKey(userAccount), user); | 64 | await storage.setItem(toKey(userAccount), user); |
| 59 | console.log('User recovered. A program will exit now.'); | 65 | console.log('User recovered. A program will exit now.'); |
| 60 | } | 66 | } |
| 61 | 67 | ||
| @@ -1,32 +1,35 @@ | |||
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | 2 | ||
| 3 | // native node modules | 3 | // native node modules |
| 4 | const fs = require('fs'); | 4 | import fs from 'node:fs'; |
| 5 | const http = require('http'); | 5 | import http from 'node:http'; |
| 6 | const https = require('https'); | 6 | import https from 'node:https'; |
| 7 | const path = require('path'); | 7 | import path from 'node:path'; |
| 8 | const util = require('util'); | 8 | import util from 'node:util'; |
| 9 | import net from 'node:net'; | ||
| 10 | import dns from 'node:dns'; | ||
| 11 | import process from 'node:process'; | ||
| 12 | import { fileURLToPath } from 'node:url'; | ||
| 9 | 13 | ||
| 10 | // cli/fs related library imports | 14 | // cli/fs related library imports |
| 11 | const open = require('open'); | 15 | import open from 'open'; |
| 12 | const yargs = require('yargs/yargs'); | 16 | import yargs from 'yargs/yargs'; |
| 13 | const { hideBin } = require('yargs/helpers'); | 17 | import { hideBin } from 'yargs/helpers'; |
| 14 | 18 | ||
| 15 | // express/server related library imports | 19 | // express/server related library imports |
| 16 | const cors = require('cors'); | 20 | import cors from 'cors'; |
| 17 | const doubleCsrf = require('csrf-csrf').doubleCsrf; | 21 | import { doubleCsrf } from 'csrf-csrf'; |
| 18 | const express = require('express'); | 22 | import express from 'express'; |
| 19 | const compression = require('compression'); | 23 | import compression from 'compression'; |
| 20 | const cookieParser = require('cookie-parser'); | 24 | import cookieParser from 'cookie-parser'; |
| 21 | const cookieSession = require('cookie-session'); | 25 | import cookieSession from 'cookie-session'; |
| 22 | const multer = require('multer'); | 26 | import multer from 'multer'; |
| 23 | const responseTime = require('response-time'); | 27 | import responseTime from 'response-time'; |
| 24 | const helmet = require('helmet').default; | 28 | import helmet from 'helmet'; |
| 29 | import bodyParser from 'body-parser'; | ||
| 25 | 30 | ||
| 26 | // net related library imports | 31 | // net related library imports |
| 27 | const net = require('net'); | 32 | import fetch from 'node-fetch'; |
| 28 | const dns = require('dns'); | ||
| 29 | const fetch = require('node-fetch').default; | ||
| 30 | 33 | ||
| 31 | // Unrestrict console logs display limit | 34 | // Unrestrict console logs display limit |
| 32 | util.inspect.defaultOptions.maxArrayLength = null; | 35 | util.inspect.defaultOptions.maxArrayLength = null; |
| @@ -34,20 +37,81 @@ util.inspect.defaultOptions.maxStringLength = null; | |||
| 34 | util.inspect.defaultOptions.depth = 4; | 37 | util.inspect.defaultOptions.depth = 4; |
| 35 | 38 | ||
| 36 | // local library imports | 39 | // local library imports |
| 37 | const userModule = require('./src/users'); | 40 | import{ loadPlugins } from './src/plugin-loader.js'; |
| 38 | const basicAuthMiddleware = require('./src/middleware/basicAuth'); | 41 | import { |
| 39 | const whitelistMiddleware = require('./src/middleware/whitelist'); | 42 | initUserStorage, |
| 40 | const initRequestProxy = require('./src/request-proxy'); | 43 | getCsrfSecret, |
| 41 | const contentManager = require('./src/endpoints/content-manager'); | 44 | getCookieSecret, |
| 42 | const { | 45 | getCookieSessionName, |
| 46 | getAllEnabledUsers, | ||
| 47 | ensurePublicDirectoriesExist, | ||
| 48 | getUserDirectoriesList, | ||
| 49 | migrateSystemPrompts, | ||
| 50 | migrateUserData, | ||
| 51 | requireLoginMiddleware, | ||
| 52 | setUserDataMiddleware, | ||
| 53 | shouldRedirectToLogin, | ||
| 54 | tryAutoLogin, | ||
| 55 | router as userDataRouter, | ||
| 56 | } from './src/users.js'; | ||
| 57 | import basicAuthMiddleware from './src/middleware/basicAuth.js'; | ||
| 58 | import whitelistMiddleware from './src/middleware/whitelist.js'; | ||
| 59 | import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js'; | ||
| 60 | import initRequestProxy from './src/request-proxy.js'; | ||
| 61 | import { | ||
| 43 | getVersion, | 62 | getVersion, |
| 44 | getConfigValue, | 63 | getConfigValue, |
| 45 | color, | 64 | color, |
| 46 | forwardFetchResponse, | 65 | forwardFetchResponse, |
| 47 | removeColorFormatting, | 66 | removeColorFormatting, |
| 48 | getSeparator, | 67 | getSeparator, |
| 49 | } = require('./src/util'); | 68 | } from './src/util.js'; |
| 50 | const { ensureThumbnailCache } = require('./src/endpoints/thumbnails'); | 69 | import { UPLOADS_DIRECTORY } from './src/constants.js'; |
| 70 | import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; | ||
| 71 | |||
| 72 | // Routers | ||
| 73 | import { router as usersPublicRouter } from './src/endpoints/users-public.js'; | ||
| 74 | import { router as usersPrivateRouter } from './src/endpoints/users-private.js'; | ||
| 75 | import { router as usersAdminRouter } from './src/endpoints/users-admin.js'; | ||
| 76 | import { router as movingUIRouter } from './src/endpoints/moving-ui.js'; | ||
| 77 | import { router as imagesRouter } from './src/endpoints/images.js'; | ||
| 78 | import { router as quickRepliesRouter } from './src/endpoints/quick-replies.js'; | ||
| 79 | import { router as avatarsRouter } from './src/endpoints/avatars.js'; | ||
| 80 | import { router as themesRouter } from './src/endpoints/themes.js'; | ||
| 81 | import { router as openAiRouter } from './src/endpoints/openai.js'; | ||
| 82 | import { router as googleRouter } from './src/endpoints/google.js'; | ||
| 83 | import { router as anthropicRouter } from './src/endpoints/anthropic.js'; | ||
| 84 | import { router as tokenizersRouter } from './src/endpoints/tokenizers.js'; | ||
| 85 | import { router as presetsRouter } from './src/endpoints/presets.js'; | ||
| 86 | import { router as secretsRouter } from './src/endpoints/secrets.js'; | ||
| 87 | import { router as thumbnailRouter } from './src/endpoints/thumbnails.js'; | ||
| 88 | import { router as novelAiRouter } from './src/endpoints/novelai.js'; | ||
| 89 | import { router as extensionsRouter } from './src/endpoints/extensions.js'; | ||
| 90 | import { router as assetsRouter } from './src/endpoints/assets.js'; | ||
| 91 | import { router as filesRouter } from './src/endpoints/files.js'; | ||
| 92 | import { router as charactersRouter } from './src/endpoints/characters.js'; | ||
| 93 | import { router as chatsRouter } from './src/endpoints/chats.js'; | ||
| 94 | import { router as groupsRouter } from './src/endpoints/groups.js'; | ||
| 95 | import { router as worldInfoRouter } from './src/endpoints/worldinfo.js'; | ||
| 96 | import { router as statsRouter, init as statsInit, onExit as statsOnExit } from './src/endpoints/stats.js'; | ||
| 97 | import { router as backgroundsRouter } from './src/endpoints/backgrounds.js'; | ||
| 98 | import { router as spritesRouter } from './src/endpoints/sprites.js'; | ||
| 99 | import { router as contentManagerRouter, checkForNewContent } from './src/endpoints/content-manager.js'; | ||
| 100 | import { router as settingsRouter, init as settingsInit } from './src/endpoints/settings.js'; | ||
| 101 | import { router as stableDiffusionRouter } from './src/endpoints/stable-diffusion.js'; | ||
| 102 | import { router as hordeRouter } from './src/endpoints/horde.js'; | ||
| 103 | import { router as vectorsRouter } from './src/endpoints/vectors.js'; | ||
| 104 | import { router as translateRouter } from './src/endpoints/translate.js'; | ||
| 105 | import { router as classifyRouter } from './src/endpoints/classify.js'; | ||
| 106 | import { router as captionRouter } from './src/endpoints/caption.js'; | ||
| 107 | import { router as searchRouter } from './src/endpoints/search.js'; | ||
| 108 | import { router as openRouterRouter } from './src/endpoints/openrouter.js'; | ||
| 109 | import { router as chatCompletionsRouter } from './src/endpoints/backends/chat-completions.js'; | ||
| 110 | import { router as koboldRouter } from './src/endpoints/backends/kobold.js'; | ||
| 111 | import { router as textCompletionsRouter } from './src/endpoints/backends/text-completions.js'; | ||
| 112 | import { router as scaleAltRouter } from './src/endpoints/backends/scale-alt.js'; | ||
| 113 | import { router as speechRouter } from './src/endpoints/speech.js'; | ||
| 114 | import { router as azureRouter } from './src/endpoints/azure.js'; | ||
| 51 | 115 | ||
| 52 | // Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0. | 116 | // Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0. |
| 53 | // https://github.com/nodejs/node/issues/47822#issuecomment-1564708870 | 117 | // https://github.com/nodejs/node/issues/47822#issuecomment-1564708870 |
| @@ -166,8 +230,8 @@ const cliArguments = yargs(hideBin(process.argv)) | |||
| 166 | }).parseSync(); | 230 | }).parseSync(); |
| 167 | 231 | ||
| 168 | // change all relative paths | 232 | // change all relative paths |
| 169 | console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment.`); | 233 | const serverDirectory = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url)); |
| 170 | const serverDirectory = __dirname; | 234 | console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`); |
| 171 | process.chdir(serverDirectory); | 235 | process.chdir(serverDirectory); |
| 172 | 236 | ||
| 173 | const app = express(); | 237 | const app = express(); |
| @@ -188,7 +252,7 @@ const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMod | |||
| 188 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); | 252 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); |
| 189 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); | 253 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); |
| 190 | 254 | ||
| 191 | const uploadsPath = path.join(dataRoot, require('./src/constants').UPLOADS_DIRECTORY); | 255 | const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY); |
| 192 | 256 | ||
| 193 | const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); | 257 | const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); |
| 194 | const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); | 258 | const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); |
| @@ -232,7 +296,6 @@ if (listen && basicAuthMode) app.use(basicAuthMiddleware); | |||
| 232 | app.use(whitelistMiddleware(enableWhitelist, listen)); | 296 | app.use(whitelistMiddleware(enableWhitelist, listen)); |
| 233 | 297 | ||
| 234 | if (enableCorsProxy) { | 298 | if (enableCorsProxy) { |
| 235 | const bodyParser = require('body-parser'); | ||
| 236 | app.use(bodyParser.json({ | 299 | app.use(bodyParser.json({ |
| 237 | limit: '200mb', | 300 | limit: '200mb', |
| 238 | })); | 301 | })); |
| @@ -301,24 +364,23 @@ function getSessionCookieAge() { | |||
| 301 | } | 364 | } |
| 302 | 365 | ||
| 303 | app.use(cookieSession({ | 366 | app.use(cookieSession({ |
| 304 | name: userModule.getCookieSessionName(), | 367 | name: getCookieSessionName(), |
| 305 | sameSite: 'strict', | 368 | sameSite: 'strict', |
| 306 | httpOnly: true, | 369 | httpOnly: true, |
| 307 | maxAge: getSessionCookieAge(), | 370 | maxAge: getSessionCookieAge(), |
| 308 | secret: userModule.getCookieSecret(), | 371 | secret: getCookieSecret(), |
| 309 | })); | 372 | })); |
| 310 | 373 | ||
| 311 | app.use(userModule.setUserDataMiddleware); | 374 | app.use(setUserDataMiddleware); |
| 312 | 375 | ||
| 313 | // CSRF Protection // | 376 | // CSRF Protection // |
| 314 | if (!disableCsrf) { | 377 | if (!disableCsrf) { |
| 315 | const COOKIES_SECRET = userModule.getCookieSecret(); | 378 | const COOKIES_SECRET = getCookieSecret(); |
| 316 | 379 | ||
| 317 | const { generateToken, doubleCsrfProtection } = doubleCsrf({ | 380 | const { generateToken, doubleCsrfProtection } = doubleCsrf({ |
| 318 | getSecret: userModule.getCsrfSecret, | 381 | getSecret: getCsrfSecret, |
| 319 | cookieName: 'X-CSRF-Token', | 382 | cookieName: 'X-CSRF-Token', |
| 320 | cookieOptions: { | 383 | cookieOptions: { |
| 321 | httpOnly: true, | ||
| 322 | sameSite: 'strict', | 384 | sameSite: 'strict', |
| 323 | secure: false, | 385 | secure: false, |
| 324 | }, | 386 | }, |
| @@ -346,7 +408,7 @@ if (!disableCsrf) { | |||
| 346 | // Static files | 408 | // Static files |
| 347 | // Host index page | 409 | // Host index page |
| 348 | app.get('/', (request, response) => { | 410 | app.get('/', (request, response) => { |
| 349 | if (userModule.shouldRedirectToLogin(request)) { | 411 | if (shouldRedirectToLogin(request)) { |
| 350 | const query = request.url.split('?')[1]; | 412 | const query = request.url.split('?')[1]; |
| 351 | const redirectUrl = query ? `/login?${query}` : '/login'; | 413 | const redirectUrl = query ? `/login?${query}` : '/login'; |
| 352 | return response.redirect(redirectUrl); | 414 | return response.redirect(redirectUrl); |
| @@ -363,7 +425,7 @@ app.get('/login', async (request, response) => { | |||
| 363 | } | 425 | } |
| 364 | 426 | ||
| 365 | try { | 427 | try { |
| 366 | const autoLogin = await userModule.tryAutoLogin(request, basicAuthMode); | 428 | const autoLogin = await tryAutoLogin(request, basicAuthMode); |
| 367 | 429 | ||
| 368 | if (autoLogin) { | 430 | if (autoLogin) { |
| 369 | return response.redirect('/'); | 431 | return response.redirect('/'); |
| @@ -379,22 +441,22 @@ app.get('/login', async (request, response) => { | |||
| 379 | app.use(express.static(process.cwd() + '/public', {})); | 441 | app.use(express.static(process.cwd() + '/public', {})); |
| 380 | 442 | ||
| 381 | // Public API | 443 | // Public API |
| 382 | app.use('/api/users', require('./src/endpoints/users-public').router); | 444 | app.use('/api/users', usersPublicRouter); |
| 383 | 445 | ||
| 384 | // Everything below this line requires authentication | 446 | // Everything below this line requires authentication |
| 385 | app.use(userModule.requireLoginMiddleware); | 447 | app.use(requireLoginMiddleware); |
| 386 | app.get('/api/ping', (_, response) => response.sendStatus(204)); | 448 | app.get('/api/ping', (_, response) => response.sendStatus(204)); |
| 387 | 449 | ||
| 388 | // File uploads | 450 | // File uploads |
| 389 | app.use(multer({ dest: uploadsPath, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); | 451 | app.use(multer({ dest: uploadsPath, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); |
| 390 | app.use(require('./src/middleware/multerMonkeyPatch')); | 452 | app.use(multerMonkeyPatch); |
| 391 | 453 | ||
| 392 | // User data mount | 454 | // User data mount |
| 393 | app.use('/', userModule.router); | 455 | app.use('/', userDataRouter); |
| 394 | // Private endpoints | 456 | // Private endpoints |
| 395 | app.use('/api/users', require('./src/endpoints/users-private').router); | 457 | app.use('/api/users', usersPrivateRouter); |
| 396 | // Admin endpoints | 458 | // Admin endpoints |
| 397 | app.use('/api/users', require('./src/endpoints/users-admin').router); | 459 | app.use('/api/users', usersAdminRouter); |
| 398 | 460 | ||
| 399 | app.get('/version', async function (_, response) { | 461 | app.get('/version', async function (_, response) { |
| 400 | const data = await getVersion(); | 462 | const data = await getVersion(); |
| @@ -509,126 +571,45 @@ redirect('/api/serpapi/search', '/api/search/serpapi'); | |||
| 509 | redirect('/api/serpapi/visit', '/api/search/visit'); | 571 | redirect('/api/serpapi/visit', '/api/search/visit'); |
| 510 | redirect('/api/serpapi/transcript', '/api/search/transcript'); | 572 | redirect('/api/serpapi/transcript', '/api/search/transcript'); |
| 511 | 573 | ||
| 512 | // Moving UI | 574 | app.use('/api/moving-ui', movingUIRouter); |
| 513 | app.use('/api/moving-ui', require('./src/endpoints/moving-ui').router); | 575 | app.use('/api/images', imagesRouter); |
| 514 | 576 | app.use('/api/quick-replies', quickRepliesRouter); | |
| 515 | // Image management | 577 | app.use('/api/avatars', avatarsRouter); |
| 516 | app.use('/api/images', require('./src/endpoints/images').router); | 578 | app.use('/api/themes', themesRouter); |
| 517 | 579 | app.use('/api/openai', openAiRouter); | |
| 518 | // Quick reply management | 580 | app.use('/api/google', googleRouter); |
| 519 | app.use('/api/quick-replies', require('./src/endpoints/quick-replies').router); | 581 | app.use('/api/anthropic', anthropicRouter); |
| 520 | 582 | app.use('/api/tokenizers', tokenizersRouter); | |
| 521 | // Avatar management | 583 | app.use('/api/presets', presetsRouter); |
| 522 | app.use('/api/avatars', require('./src/endpoints/avatars').router); | 584 | app.use('/api/secrets', secretsRouter); |
| 523 | 585 | app.use('/thumbnail', thumbnailRouter); | |
| 524 | // Theme management | 586 | app.use('/api/novelai', novelAiRouter); |
| 525 | app.use('/api/themes', require('./src/endpoints/themes').router); | 587 | app.use('/api/extensions', extensionsRouter); |
| 526 | 588 | app.use('/api/assets', assetsRouter); | |
| 527 | // OpenAI API | 589 | app.use('/api/files', filesRouter); |
| 528 | app.use('/api/openai', require('./src/endpoints/openai').router); | 590 | app.use('/api/characters', charactersRouter); |
| 529 | 591 | app.use('/api/chats', chatsRouter); | |
| 530 | //Google API | 592 | app.use('/api/groups', groupsRouter); |
| 531 | app.use('/api/google', require('./src/endpoints/google').router); | 593 | app.use('/api/worldinfo', worldInfoRouter); |
| 532 | 594 | app.use('/api/stats', statsRouter); | |
| 533 | //Anthropic API | 595 | app.use('/api/backgrounds', backgroundsRouter); |
| 534 | app.use('/api/anthropic', require('./src/endpoints/anthropic').router); | 596 | app.use('/api/sprites', spritesRouter); |
| 535 | 597 | app.use('/api/content', contentManagerRouter); | |
| 536 | // Tokenizers | 598 | app.use('/api/settings', settingsRouter); |
| 537 | app.use('/api/tokenizers', require('./src/endpoints/tokenizers').router); | 599 | app.use('/api/sd', stableDiffusionRouter); |
| 538 | 600 | app.use('/api/horde', hordeRouter); | |
| 539 | // Preset management | 601 | app.use('/api/vector', vectorsRouter); |
| 540 | app.use('/api/presets', require('./src/endpoints/presets').router); | 602 | app.use('/api/translate', translateRouter); |
| 541 | 603 | app.use('/api/extra/classify', classifyRouter); | |
| 542 | // Secrets managemenet | 604 | app.use('/api/extra/caption', captionRouter); |
| 543 | app.use('/api/secrets', require('./src/endpoints/secrets').router); | 605 | app.use('/api/search', searchRouter); |
| 544 | 606 | app.use('/api/backends/text-completions', textCompletionsRouter); | |
| 545 | // Thumbnail generation. These URLs are saved in chat, so this route cannot be renamed! | 607 | app.use('/api/openrouter', openRouterRouter); |
| 546 | app.use('/thumbnail', require('./src/endpoints/thumbnails').router); | 608 | app.use('/api/backends/kobold', koboldRouter); |
| 547 | 609 | app.use('/api/backends/chat-completions', chatCompletionsRouter); | |
| 548 | // NovelAI generation | 610 | app.use('/api/backends/scale-alt', scaleAltRouter); |
| 549 | app.use('/api/novelai', require('./src/endpoints/novelai').router); | 611 | app.use('/api/speech', speechRouter); |
| 550 | 612 | app.use('/api/azure', azureRouter); | |
| 551 | // Third-party extensions | ||
| 552 | app.use('/api/extensions', require('./src/endpoints/extensions').router); | ||
| 553 | |||
| 554 | // Asset management | ||
| 555 | app.use('/api/assets', require('./src/endpoints/assets').router); | ||
| 556 | |||
| 557 | // File management | ||
| 558 | app.use('/api/files', require('./src/endpoints/files').router); | ||
| 559 | |||
| 560 | // Character management | ||
| 561 | app.use('/api/characters', require('./src/endpoints/characters').router); | ||
| 562 | |||
| 563 | // Chat management | ||
| 564 | app.use('/api/chats', require('./src/endpoints/chats').router); | ||
| 565 | |||
| 566 | // Group management | ||
| 567 | app.use('/api/groups', require('./src/endpoints/groups').router); | ||
| 568 | |||
| 569 | // World info management | ||
| 570 | app.use('/api/worldinfo', require('./src/endpoints/worldinfo').router); | ||
| 571 | |||
| 572 | // Stats calculation | ||
| 573 | const statsEndpoint = require('./src/endpoints/stats'); | ||
| 574 | app.use('/api/stats', statsEndpoint.router); | ||
| 575 | |||
| 576 | // Background management | ||
| 577 | app.use('/api/backgrounds', require('./src/endpoints/backgrounds').router); | ||
| 578 | |||
| 579 | // Character sprite management | ||
| 580 | app.use('/api/sprites', require('./src/endpoints/sprites').router); | ||
| 581 | |||
| 582 | // Custom content management | ||
| 583 | app.use('/api/content', require('./src/endpoints/content-manager').router); | ||
| 584 | |||
| 585 | // Settings load/store | ||
| 586 | const settingsEndpoint = require('./src/endpoints/settings'); | ||
| 587 | app.use('/api/settings', settingsEndpoint.router); | ||
| 588 | |||
| 589 | // Stable Diffusion generation | ||
| 590 | app.use('/api/sd', require('./src/endpoints/stable-diffusion').router); | ||
| 591 | |||
| 592 | // LLM and SD Horde generation | ||
| 593 | app.use('/api/horde', require('./src/endpoints/horde').router); | ||
| 594 | |||
| 595 | // Vector storage DB | ||
| 596 | app.use('/api/vector', require('./src/endpoints/vectors').router); | ||
| 597 | |||
| 598 | // Chat translation | ||
| 599 | app.use('/api/translate', require('./src/endpoints/translate').router); | ||
| 600 | |||
| 601 | // Emotion classification | ||
| 602 | app.use('/api/extra/classify', require('./src/endpoints/classify').router); | ||
| 603 | |||
| 604 | // Image captioning | ||
| 605 | app.use('/api/extra/caption', require('./src/endpoints/caption').router); | ||
| 606 | |||
| 607 | // Web search and scraping | ||
| 608 | app.use('/api/search', require('./src/endpoints/search').router); | ||
| 609 | |||
| 610 | // The different text generation APIs | ||
| 611 | |||
| 612 | // Ooba/OpenAI text completions | ||
| 613 | app.use('/api/backends/text-completions', require('./src/endpoints/backends/text-completions').router); | ||
| 614 | |||
| 615 | // OpenRouter | ||
| 616 | app.use('/api/openrouter', require('./src/endpoints/openrouter').router); | ||
| 617 | |||
| 618 | // KoboldAI | ||
| 619 | app.use('/api/backends/kobold', require('./src/endpoints/backends/kobold').router); | ||
| 620 | |||
| 621 | // OpenAI chat completions | ||
| 622 | app.use('/api/backends/chat-completions', require('./src/endpoints/backends/chat-completions').router); | ||
| 623 | |||
| 624 | // Scale (alt method) | ||
| 625 | app.use('/api/backends/scale-alt', require('./src/endpoints/backends/scale-alt').router); | ||
| 626 | |||
| 627 | // Speech (text-to-speech and speech-to-text) | ||
| 628 | app.use('/api/speech', require('./src/endpoints/speech').router); | ||
| 629 | |||
| 630 | // Azure TTS | ||
| 631 | app.use('/api/azure', require('./src/endpoints/azure').router); | ||
| 632 | 613 | ||
| 633 | const tavernUrlV6 = new URL( | 614 | const tavernUrlV6 = new URL( |
| 634 | (cliArguments.ssl ? 'https://' : 'http://') + | 615 | (cliArguments.ssl ? 'https://' : 'http://') + |
| @@ -658,22 +639,22 @@ const preSetupTasks = async function () { | |||
| 658 | } | 639 | } |
| 659 | console.log(); | 640 | console.log(); |
| 660 | 641 | ||
| 661 | const directories = await userModule.getUserDirectoriesList(); | 642 | const directories = await getUserDirectoriesList(); |
| 662 | await contentManager.checkForNewContent(directories); | 643 | await checkForNewContent(directories); |
| 663 | await ensureThumbnailCache(); | 644 | await ensureThumbnailCache(); |
| 664 | cleanUploads(); | 645 | cleanUploads(); |
| 665 | 646 | ||
| 666 | await settingsEndpoint.init(); | 647 | await settingsInit(); |
| 667 | await statsEndpoint.init(); | 648 | await statsInit(); |
| 668 | 649 | ||
| 669 | const cleanupPlugins = await loadPlugins(); | 650 | const cleanupPlugins = await initializePlugins(); |
| 670 | const consoleTitle = process.title; | 651 | const consoleTitle = process.title; |
| 671 | 652 | ||
| 672 | let isExiting = false; | 653 | let isExiting = false; |
| 673 | const exitProcess = async () => { | 654 | const exitProcess = async () => { |
| 674 | if (isExiting) return; | 655 | if (isExiting) return; |
| 675 | isExiting = true; | 656 | isExiting = true; |
| 676 | statsEndpoint.onExit(); | 657 | await statsOnExit(); |
| 677 | if (typeof cleanupPlugins === 'function') { | 658 | if (typeof cleanupPlugins === 'function') { |
| 678 | await cleanupPlugins(); | 659 | await cleanupPlugins(); |
| 679 | } | 660 | } |
| @@ -773,11 +754,10 @@ const postSetupTasks = async function (v6Failed, v4Failed) { | |||
| 773 | * Loads server plugins from a directory. | 754 | * Loads server plugins from a directory. |
| 774 | * @returns {Promise<Function>} Function to be run on server exit | 755 | * @returns {Promise<Function>} Function to be run on server exit |
| 775 | */ | 756 | */ |
| 776 | async function loadPlugins() { | 757 | async function initializePlugins() { |
| 777 | try { | 758 | try { |
| 778 | const pluginDirectory = path.join(serverDirectory, 'plugins'); | 759 | const pluginDirectory = path.join(serverDirectory, 'plugins'); |
| 779 | const loader = require('./src/plugin-loader'); | 760 | const cleanupPlugins = await loadPlugins(app, pluginDirectory); |
| 780 | const cleanupPlugins = await loader.loadPlugins(app, pluginDirectory); | ||
| 781 | return cleanupPlugins; | 761 | return cleanupPlugins; |
| 782 | } catch { | 762 | } catch { |
| 783 | console.log('Plugin loading failed.'); | 763 | console.log('Plugin loading failed.'); |
| @@ -850,7 +830,7 @@ function createHttpsServer(url) { | |||
| 850 | }, app); | 830 | }, app); |
| 851 | server.on('error', reject); | 831 | server.on('error', reject); |
| 852 | server.on('listening', resolve); | 832 | server.on('listening', resolve); |
| 853 | server.listen(url.port || 443, url.hostname); | 833 | server.listen(Number(url.port || 443), url.hostname); |
| 854 | }); | 834 | }); |
| 855 | } | 835 | } |
| 856 | 836 | ||
| @@ -865,7 +845,7 @@ function createHttpServer(url) { | |||
| 865 | const server = http.createServer(app); | 845 | const server = http.createServer(app); |
| 866 | server.on('error', reject); | 846 | server.on('error', reject); |
| 867 | server.on('listening', resolve); | 847 | server.on('listening', resolve); |
| 868 | server.listen(url.port || 80, url.hostname); | 848 | server.listen(Number(url.port || 80), url.hostname); |
| 869 | }); | 849 | }); |
| 870 | } | 850 | } |
| 871 | 851 | ||
| @@ -917,7 +897,7 @@ async function verifySecuritySettings() { | |||
| 917 | logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.'); | 897 | logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.'); |
| 918 | } | 898 | } |
| 919 | 899 | ||
| 920 | const users = await userModule.getAllEnabledUsers(); | 900 | const users = await getAllEnabledUsers(); |
| 921 | const unprotectedUsers = users.filter(x => !x.password); | 901 | const unprotectedUsers = users.filter(x => !x.password); |
| 922 | const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin); | 902 | const unprotectedAdminUsers = unprotectedUsers.filter(x => x.admin); |
| 923 | 903 | ||
| @@ -935,10 +915,10 @@ async function verifySecuritySettings() { | |||
| 935 | } | 915 | } |
| 936 | 916 | ||
| 937 | // User storage module needs to be initialized before starting the server | 917 | // User storage module needs to be initialized before starting the server |
| 938 | userModule.initUserStorage(dataRoot) | 918 | initUserStorage(dataRoot) |
| 939 | .then(userModule.ensurePublicDirectoriesExist) | 919 | .then(ensurePublicDirectoriesExist) |
| 940 | .then(userModule.migrateUserData) | 920 | .then(migrateUserData) |
| 941 | .then(userModule.migrateSystemPrompts) | 921 | .then(migrateSystemPrompts) |
| 942 | .then(verifySecuritySettings) | 922 | .then(verifySecuritySettings) |
| 943 | .then(preSetupTasks) | 923 | .then(preSetupTasks) |
| 944 | .finally(startServer); | 924 | .finally(startServer); |
| @@ -1,10 +1,10 @@ | |||
| 1 | const { TEXTGEN_TYPES, OPENROUTER_HEADERS, FEATHERLESS_HEADERS } = require('./constants'); | 1 | import { TEXTGEN_TYPES, OPENROUTER_HEADERS, FEATHERLESS_HEADERS } from './constants.js'; |
| 2 | const { SECRET_KEYS, readSecret } = require('./endpoints/secrets'); | 2 | import { SECRET_KEYS, readSecret } from './endpoints/secrets.js'; |
| 3 | const { getConfigValue } = require('./util'); | 3 | import { getConfigValue } from './util.js'; |
| 4 | 4 | ||
| 5 | /** | 5 | /** |
| 6 | * Gets the headers for the Mancer API. | 6 | * Gets the headers for the Mancer API. |
| 7 | * @param {import('./users').UserDirectoryList} directories User directories | 7 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 8 | * @returns {object} Headers for the request | 8 | * @returns {object} Headers for the request |
| 9 | */ | 9 | */ |
| 10 | function getMancerHeaders(directories) { | 10 | function getMancerHeaders(directories) { |
| @@ -18,7 +18,7 @@ function getMancerHeaders(directories) { | |||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Gets the headers for the TogetherAI API. | 20 | * Gets the headers for the TogetherAI API. |
| 21 | * @param {import('./users').UserDirectoryList} directories User directories | 21 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 22 | * @returns {object} Headers for the request | 22 | * @returns {object} Headers for the request |
| 23 | */ | 23 | */ |
| 24 | function getTogetherAIHeaders(directories) { | 24 | function getTogetherAIHeaders(directories) { |
| @@ -31,7 +31,7 @@ function getTogetherAIHeaders(directories) { | |||
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | * Gets the headers for the InfermaticAI API. | 33 | * Gets the headers for the InfermaticAI API. |
| 34 | * @param {import('./users').UserDirectoryList} directories User directories | 34 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 35 | * @returns {object} Headers for the request | 35 | * @returns {object} Headers for the request |
| 36 | */ | 36 | */ |
| 37 | function getInfermaticAIHeaders(directories) { | 37 | function getInfermaticAIHeaders(directories) { |
| @@ -44,7 +44,7 @@ function getInfermaticAIHeaders(directories) { | |||
| 44 | 44 | ||
| 45 | /** | 45 | /** |
| 46 | * Gets the headers for the DreamGen API. | 46 | * Gets the headers for the DreamGen API. |
| 47 | * @param {import('./users').UserDirectoryList} directories User directories | 47 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 48 | * @returns {object} Headers for the request | 48 | * @returns {object} Headers for the request |
| 49 | */ | 49 | */ |
| 50 | function getDreamGenHeaders(directories) { | 50 | function getDreamGenHeaders(directories) { |
| @@ -57,7 +57,7 @@ function getDreamGenHeaders(directories) { | |||
| 57 | 57 | ||
| 58 | /** | 58 | /** |
| 59 | * Gets the headers for the OpenRouter API. | 59 | * Gets the headers for the OpenRouter API. |
| 60 | * @param {import('./users').UserDirectoryList} directories User directories | 60 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 61 | * @returns {object} Headers for the request | 61 | * @returns {object} Headers for the request |
| 62 | */ | 62 | */ |
| 63 | function getOpenRouterHeaders(directories) { | 63 | function getOpenRouterHeaders(directories) { |
| @@ -69,7 +69,7 @@ function getOpenRouterHeaders(directories) { | |||
| 69 | 69 | ||
| 70 | /** | 70 | /** |
| 71 | * Gets the headers for the vLLM API. | 71 | * Gets the headers for the vLLM API. |
| 72 | * @param {import('./users').UserDirectoryList} directories User directories | 72 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 73 | * @returns {object} Headers for the request | 73 | * @returns {object} Headers for the request |
| 74 | */ | 74 | */ |
| 75 | function getVllmHeaders(directories) { | 75 | function getVllmHeaders(directories) { |
| @@ -82,7 +82,7 @@ function getVllmHeaders(directories) { | |||
| 82 | 82 | ||
| 83 | /** | 83 | /** |
| 84 | * Gets the headers for the Aphrodite API. | 84 | * Gets the headers for the Aphrodite API. |
| 85 | * @param {import('./users').UserDirectoryList} directories User directories | 85 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 86 | * @returns {object} Headers for the request | 86 | * @returns {object} Headers for the request |
| 87 | */ | 87 | */ |
| 88 | function getAphroditeHeaders(directories) { | 88 | function getAphroditeHeaders(directories) { |
| @@ -96,7 +96,7 @@ function getAphroditeHeaders(directories) { | |||
| 96 | 96 | ||
| 97 | /** | 97 | /** |
| 98 | * Gets the headers for the Tabby API. | 98 | * Gets the headers for the Tabby API. |
| 99 | * @param {import('./users').UserDirectoryList} directories User directories | 99 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 100 | * @returns {object} Headers for the request | 100 | * @returns {object} Headers for the request |
| 101 | */ | 101 | */ |
| 102 | function getTabbyHeaders(directories) { | 102 | function getTabbyHeaders(directories) { |
| @@ -110,7 +110,7 @@ function getTabbyHeaders(directories) { | |||
| 110 | 110 | ||
| 111 | /** | 111 | /** |
| 112 | * Gets the headers for the LlamaCPP API. | 112 | * Gets the headers for the LlamaCPP API. |
| 113 | * @param {import('./users').UserDirectoryList} directories User directories | 113 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 114 | * @returns {object} Headers for the request | 114 | * @returns {object} Headers for the request |
| 115 | */ | 115 | */ |
| 116 | function getLlamaCppHeaders(directories) { | 116 | function getLlamaCppHeaders(directories) { |
| @@ -123,7 +123,7 @@ function getLlamaCppHeaders(directories) { | |||
| 123 | 123 | ||
| 124 | /** | 124 | /** |
| 125 | * Gets the headers for the Ooba API. | 125 | * Gets the headers for the Ooba API. |
| 126 | * @param {import('./users').UserDirectoryList} directories | 126 | * @param {import('./users.js').UserDirectoryList} directories |
| 127 | * @returns {object} Headers for the request | 127 | * @returns {object} Headers for the request |
| 128 | */ | 128 | */ |
| 129 | function getOobaHeaders(directories) { | 129 | function getOobaHeaders(directories) { |
| @@ -136,7 +136,7 @@ function getOobaHeaders(directories) { | |||
| 136 | 136 | ||
| 137 | /** | 137 | /** |
| 138 | * Gets the headers for the KoboldCpp API. | 138 | * Gets the headers for the KoboldCpp API. |
| 139 | * @param {import('./users').UserDirectoryList} directories | 139 | * @param {import('./users.js').UserDirectoryList} directories |
| 140 | * @returns {object} Headers for the request | 140 | * @returns {object} Headers for the request |
| 141 | */ | 141 | */ |
| 142 | function getKoboldCppHeaders(directories) { | 142 | function getKoboldCppHeaders(directories) { |
| @@ -149,7 +149,7 @@ function getKoboldCppHeaders(directories) { | |||
| 149 | 149 | ||
| 150 | /** | 150 | /** |
| 151 | * Gets the headers for the Featherless API. | 151 | * Gets the headers for the Featherless API. |
| 152 | * @param {import('./users').UserDirectoryList} directories | 152 | * @param {import('./users.js').UserDirectoryList} directories |
| 153 | * @returns {object} Headers for the request | 153 | * @returns {object} Headers for the request |
| 154 | */ | 154 | */ |
| 155 | function getFeatherlessHeaders(directories) { | 155 | function getFeatherlessHeaders(directories) { |
| @@ -161,7 +161,7 @@ function getFeatherlessHeaders(directories) { | |||
| 161 | 161 | ||
| 162 | /** | 162 | /** |
| 163 | * Gets the headers for the HuggingFace API. | 163 | * Gets the headers for the HuggingFace API. |
| 164 | * @param {import('./users').UserDirectoryList} directories | 164 | * @param {import('./users.js').UserDirectoryList} directories |
| 165 | * @returns {object} Headers for the request | 165 | * @returns {object} Headers for the request |
| 166 | */ | 166 | */ |
| 167 | function getHuggingFaceHeaders(directories) { | 167 | function getHuggingFaceHeaders(directories) { |
| @@ -172,7 +172,7 @@ function getHuggingFaceHeaders(directories) { | |||
| 172 | }) : {}; | 172 | }) : {}; |
| 173 | } | 173 | } |
| 174 | 174 | ||
| 175 | function getOverrideHeaders(urlHost) { | 175 | export function getOverrideHeaders(urlHost) { |
| 176 | const requestOverrides = getConfigValue('requestOverrides', []); | 176 | const requestOverrides = getConfigValue('requestOverrides', []); |
| 177 | const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; | 177 | const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; |
| 178 | if (overrideHeaders && urlHost) { | 178 | if (overrideHeaders && urlHost) { |
| @@ -188,7 +188,7 @@ function getOverrideHeaders(urlHost) { | |||
| 188 | * @param {object} args New request arguments | 188 | * @param {object} args New request arguments |
| 189 | * @param {string|null} server API server for new request | 189 | * @param {string|null} server API server for new request |
| 190 | */ | 190 | */ |
| 191 | function setAdditionalHeaders(request, args, server) { | 191 | export function setAdditionalHeaders(request, args, server) { |
| 192 | setAdditionalHeadersByType(args.headers, request.body.api_type, server, request.user.directories); | 192 | setAdditionalHeadersByType(args.headers, request.body.api_type, server, request.user.directories); |
| 193 | } | 193 | } |
| 194 | 194 | ||
| @@ -197,9 +197,9 @@ function setAdditionalHeaders(request, args, server) { | |||
| 197 | * @param {object} requestHeaders Request headers | 197 | * @param {object} requestHeaders Request headers |
| 198 | * @param {string} type API type | 198 | * @param {string} type API type |
| 199 | * @param {string|null} server API server for new request | 199 | * @param {string|null} server API server for new request |
| 200 | * @param {import('./users').UserDirectoryList} directories User directories | 200 | * @param {import('./users.js').UserDirectoryList} directories User directories |
| 201 | */ | 201 | */ |
| 202 | function setAdditionalHeadersByType(requestHeaders, type, server, directories) { | 202 | export function setAdditionalHeadersByType(requestHeaders, type, server, directories) { |
| 203 | const headerGetters = { | 203 | const headerGetters = { |
| 204 | [TEXTGEN_TYPES.MANCER]: getMancerHeaders, | 204 | [TEXTGEN_TYPES.MANCER]: getMancerHeaders, |
| 205 | [TEXTGEN_TYPES.VLLM]: getVllmHeaders, | 205 | [TEXTGEN_TYPES.VLLM]: getVllmHeaders, |
| @@ -234,9 +234,3 @@ function setAdditionalHeadersByType(requestHeaders, type, server, directories) { | |||
| 234 | 234 | ||
| 235 | Object.assign(requestHeaders, headers); | 235 | Object.assign(requestHeaders, headers); |
| 236 | } | 236 | } |
| 237 | |||
| 238 | module.exports = { | ||
| 239 | getOverrideHeaders, | ||
| 240 | setAdditionalHeaders, | ||
| 241 | setAdditionalHeadersByType, | ||
| 242 | }; | ||
| @@ -1,8 +1,9 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | import { Buffer } from 'node:buffer'; | ||
| 2 | 3 | ||
| 3 | const encode = require('png-chunks-encode'); | 4 | import encode from 'png-chunks-encode'; |
| 4 | const extract = require('png-chunks-extract'); | 5 | import extract from 'png-chunks-extract'; |
| 5 | const PNGtext = require('png-chunk-text'); | 6 | import PNGtext from 'png-chunk-text'; |
| 6 | 7 | ||
| 7 | /** | 8 | /** |
| 8 | * Writes Character metadata to a PNG image buffer. | 9 | * Writes Character metadata to a PNG image buffer. |
| @@ -11,8 +12,8 @@ const PNGtext = require('png-chunk-text'); | |||
| 11 | * @param {string} data Character data to write | 12 | * @param {string} data Character data to write |
| 12 | * @returns {Buffer} PNG image buffer with metadata | 13 | * @returns {Buffer} PNG image buffer with metadata |
| 13 | */ | 14 | */ |
| 14 | const write = (image, data) => { | 15 | export const write = (image, data) => { |
| 15 | const chunks = extract(image); | 16 | const chunks = extract(new Uint8Array(image)); |
| 16 | const tEXtChunks = chunks.filter(chunk => chunk.name === 'tEXt'); | 17 | const tEXtChunks = chunks.filter(chunk => chunk.name === 'tEXt'); |
| 17 | 18 | ||
| 18 | // Remove existing tEXt chunks | 19 | // Remove existing tEXt chunks |
| @@ -36,7 +37,9 @@ const write = (image, data) => { | |||
| 36 | 37 | ||
| 37 | const base64EncodedData = Buffer.from(JSON.stringify(v3Data), 'utf8').toString('base64'); | 38 | const base64EncodedData = Buffer.from(JSON.stringify(v3Data), 'utf8').toString('base64'); |
| 38 | chunks.splice(-1, 0, PNGtext.encode('ccv3', base64EncodedData)); | 39 | chunks.splice(-1, 0, PNGtext.encode('ccv3', base64EncodedData)); |
| 39 | } catch (error) { } | 40 | } catch (error) { |
| 41 | // Ignore errors when adding v3 chunk | ||
| 42 | } | ||
| 40 | 43 | ||
| 41 | const newBuffer = Buffer.from(encode(chunks)); | 44 | const newBuffer = Buffer.from(encode(chunks)); |
| 42 | return newBuffer; | 45 | return newBuffer; |
| @@ -48,8 +51,8 @@ const write = (image, data) => { | |||
| 48 | * @param {Buffer} image PNG image buffer | 51 | * @param {Buffer} image PNG image buffer |
| 49 | * @returns {string} Character data | 52 | * @returns {string} Character data |
| 50 | */ | 53 | */ |
| 51 | const read = (image) => { | 54 | export const read = (image) => { |
| 52 | const chunks = extract(image); | 55 | const chunks = extract(new Uint8Array(image)); |
| 53 | 56 | ||
| 54 | const textChunks = chunks.filter((chunk) => chunk.name === 'tEXt').map((chunk) => PNGtext.decode(chunk.data)); | 57 | const textChunks = chunks.filter((chunk) => chunk.name === 'tEXt').map((chunk) => PNGtext.decode(chunk.data)); |
| 55 | 58 | ||
| @@ -80,7 +83,7 @@ const read = (image) => { | |||
| 80 | * @param {string} format File format | 83 | * @param {string} format File format |
| 81 | * @returns {string} Character data | 84 | * @returns {string} Character data |
| 82 | */ | 85 | */ |
| 83 | const parse = (cardUrl, format) => { | 86 | export const parse = (cardUrl, format) => { |
| 84 | let fileFormat = format === undefined ? 'png' : format; | 87 | let fileFormat = format === undefined ? 'png' : format; |
| 85 | 88 | ||
| 86 | switch (fileFormat) { | 89 | switch (fileFormat) { |
| @@ -93,8 +96,3 @@ const parse = (cardUrl, format) => { | |||
| 93 | throw new Error('Unsupported format'); | 96 | throw new Error('Unsupported format'); |
| 94 | }; | 97 | }; |
| 95 | 98 | ||
| 96 | module.exports = { | ||
| 97 | parse, | ||
| 98 | write, | ||
| 99 | read, | ||
| 100 | }; | ||
| @@ -1,18 +1,18 @@ | |||
| 1 | const PUBLIC_DIRECTORIES = { | 1 | export const PUBLIC_DIRECTORIES = { |
| 2 | images: 'public/img/', | 2 | images: 'public/img/', |
| 3 | backups: 'backups/', | 3 | backups: 'backups/', |
| 4 | sounds: 'public/sounds', | 4 | sounds: 'public/sounds', |
| 5 | extensions: 'public/scripts/extensions', | 5 | extensions: 'public/scripts/extensions', |
| 6 | }; | 6 | }; |
| 7 | 7 | ||
| 8 | const SETTINGS_FILE = 'settings.json'; | 8 | export const SETTINGS_FILE = 'settings.json'; |
| 9 | 9 | ||
| 10 | /** | 10 | /** |
| 11 | * @type {import('./users').UserDirectoryList} | 11 | * @type {import('./users.js').UserDirectoryList} |
| 12 | * @readonly | 12 | * @readonly |
| 13 | * @enum {string} | 13 | * @enum {string} |
| 14 | */ | 14 | */ |
| 15 | const USER_DIRECTORY_TEMPLATE = Object.freeze({ | 15 | export const USER_DIRECTORY_TEMPLATE = Object.freeze({ |
| 16 | root: '', | 16 | root: '', |
| 17 | thumbnails: 'thumbnails', | 17 | thumbnails: 'thumbnails', |
| 18 | thumbnailsBg: 'thumbnails/bg', | 18 | thumbnailsBg: 'thumbnails/bg', |
| @@ -45,10 +45,10 @@ const USER_DIRECTORY_TEMPLATE = Object.freeze({ | |||
| 45 | }); | 45 | }); |
| 46 | 46 | ||
| 47 | /** | 47 | /** |
| 48 | * @type {import('./users').User} | 48 | * @type {import('./users.js').User} |
| 49 | * @readonly | 49 | * @readonly |
| 50 | */ | 50 | */ |
| 51 | const DEFAULT_USER = Object.freeze({ | 51 | export const DEFAULT_USER = Object.freeze({ |
| 52 | handle: 'default-user', | 52 | handle: 'default-user', |
| 53 | name: 'User', | 53 | name: 'User', |
| 54 | created: Date.now(), | 54 | created: Date.now(), |
| @@ -58,7 +58,7 @@ const DEFAULT_USER = Object.freeze({ | |||
| 58 | salt: '', | 58 | salt: '', |
| 59 | }); | 59 | }); |
| 60 | 60 | ||
| 61 | const UNSAFE_EXTENSIONS = [ | 61 | export const UNSAFE_EXTENSIONS = [ |
| 62 | '.php', | 62 | '.php', |
| 63 | '.exe', | 63 | '.exe', |
| 64 | '.com', | 64 | '.com', |
| @@ -135,7 +135,7 @@ const UNSAFE_EXTENSIONS = [ | |||
| 135 | '.ws', | 135 | '.ws', |
| 136 | ]; | 136 | ]; |
| 137 | 137 | ||
| 138 | const GEMINI_SAFETY = [ | 138 | export const GEMINI_SAFETY = [ |
| 139 | { | 139 | { |
| 140 | category: 'HARM_CATEGORY_HARASSMENT', | 140 | category: 'HARM_CATEGORY_HARASSMENT', |
| 141 | threshold: 'BLOCK_NONE', | 141 | threshold: 'BLOCK_NONE', |
| @@ -158,7 +158,7 @@ const GEMINI_SAFETY = [ | |||
| 158 | }, | 158 | }, |
| 159 | ]; | 159 | ]; |
| 160 | 160 | ||
| 161 | const BISON_SAFETY = [ | 161 | export const BISON_SAFETY = [ |
| 162 | { | 162 | { |
| 163 | category: 'HARM_CATEGORY_DEROGATORY', | 163 | category: 'HARM_CATEGORY_DEROGATORY', |
| 164 | threshold: 'BLOCK_NONE', | 164 | threshold: 'BLOCK_NONE', |
| @@ -185,7 +185,7 @@ const BISON_SAFETY = [ | |||
| 185 | }, | 185 | }, |
| 186 | ]; | 186 | ]; |
| 187 | 187 | ||
| 188 | const CHAT_COMPLETION_SOURCES = { | 188 | export const CHAT_COMPLETION_SOURCES = { |
| 189 | OPENAI: 'openai', | 189 | OPENAI: 'openai', |
| 190 | WINDOWAI: 'windowai', | 190 | WINDOWAI: 'windowai', |
| 191 | CLAUDE: 'claude', | 191 | CLAUDE: 'claude', |
| @@ -205,10 +205,10 @@ const CHAT_COMPLETION_SOURCES = { | |||
| 205 | /** | 205 | /** |
| 206 | * Path to multer file uploads under the data root. | 206 | * Path to multer file uploads under the data root. |
| 207 | */ | 207 | */ |
| 208 | const UPLOADS_DIRECTORY = '_uploads'; | 208 | export const UPLOADS_DIRECTORY = '_uploads'; |
| 209 | 209 | ||
| 210 | // TODO: this is copied from the client code; there should be a way to de-duplicate it eventually | 210 | // TODO: this is copied from the client code; there should be a way to de-duplicate it eventually |
| 211 | const TEXTGEN_TYPES = { | 211 | export const TEXTGEN_TYPES = { |
| 212 | OOBA: 'ooba', | 212 | OOBA: 'ooba', |
| 213 | MANCER: 'mancer', | 213 | MANCER: 'mancer', |
| 214 | VLLM: 'vllm', | 214 | VLLM: 'vllm', |
| @@ -225,7 +225,7 @@ const TEXTGEN_TYPES = { | |||
| 225 | HUGGINGFACE: 'huggingface', | 225 | HUGGINGFACE: 'huggingface', |
| 226 | }; | 226 | }; |
| 227 | 227 | ||
| 228 | const INFERMATICAI_KEYS = [ | 228 | export const INFERMATICAI_KEYS = [ |
| 229 | 'model', | 229 | 'model', |
| 230 | 'prompt', | 230 | 'prompt', |
| 231 | 'max_tokens', | 231 | 'max_tokens', |
| @@ -248,7 +248,7 @@ const INFERMATICAI_KEYS = [ | |||
| 248 | 'logprobs', | 248 | 'logprobs', |
| 249 | ]; | 249 | ]; |
| 250 | 250 | ||
| 251 | const FEATHERLESS_KEYS = [ | 251 | export const FEATHERLESS_KEYS = [ |
| 252 | 'model', | 252 | 'model', |
| 253 | 'prompt', | 253 | 'prompt', |
| 254 | 'best_of', | 254 | 'best_of', |
| @@ -290,9 +290,8 @@ const FEATHERLESS_KEYS = [ | |||
| 290 | 'guided_whitespace_pattern', | 290 | 'guided_whitespace_pattern', |
| 291 | ]; | 291 | ]; |
| 292 | 292 | ||
| 293 | |||
| 294 | // https://dreamgen.com/docs/api#openai-text | 293 | // https://dreamgen.com/docs/api#openai-text |
| 295 | const DREAMGEN_KEYS = [ | 294 | export const DREAMGEN_KEYS = [ |
| 296 | 'model', | 295 | 'model', |
| 297 | 'prompt', | 296 | 'prompt', |
| 298 | 'max_tokens', | 297 | 'max_tokens', |
| @@ -309,7 +308,7 @@ const DREAMGEN_KEYS = [ | |||
| 309 | ]; | 308 | ]; |
| 310 | 309 | ||
| 311 | // https://docs.together.ai/reference/completions | 310 | // https://docs.together.ai/reference/completions |
| 312 | const TOGETHERAI_KEYS = [ | 311 | export const TOGETHERAI_KEYS = [ |
| 313 | 'model', | 312 | 'model', |
| 314 | 'prompt', | 313 | 'prompt', |
| 315 | 'max_tokens', | 314 | 'max_tokens', |
| @@ -325,7 +324,7 @@ const TOGETHERAI_KEYS = [ | |||
| 325 | ]; | 324 | ]; |
| 326 | 325 | ||
| 327 | // https://github.com/jmorganca/ollama/blob/main/docs/api.md#request-with-options | 326 | // https://github.com/jmorganca/ollama/blob/main/docs/api.md#request-with-options |
| 328 | const OLLAMA_KEYS = [ | 327 | export const OLLAMA_KEYS = [ |
| 329 | 'num_predict', | 328 | 'num_predict', |
| 330 | 'num_ctx', | 329 | 'num_ctx', |
| 331 | 'stop', | 330 | 'stop', |
| @@ -345,20 +344,20 @@ const OLLAMA_KEYS = [ | |||
| 345 | 'min_p', | 344 | 'min_p', |
| 346 | ]; | 345 | ]; |
| 347 | 346 | ||
| 348 | const AVATAR_WIDTH = 512; | 347 | export const AVATAR_WIDTH = 512; |
| 349 | const AVATAR_HEIGHT = 768; | 348 | export const AVATAR_HEIGHT = 768; |
| 350 | 349 | ||
| 351 | const OPENROUTER_HEADERS = { | 350 | export const OPENROUTER_HEADERS = { |
| 352 | 'HTTP-Referer': 'https://sillytavern.app', | 351 | 'HTTP-Referer': 'https://sillytavern.app', |
| 353 | 'X-Title': 'SillyTavern', | 352 | 'X-Title': 'SillyTavern', |
| 354 | }; | 353 | }; |
| 355 | 354 | ||
| 356 | const FEATHERLESS_HEADERS = { | 355 | export const FEATHERLESS_HEADERS = { |
| 357 | 'HTTP-Referer': 'https://sillytavern.app', | 356 | 'HTTP-Referer': 'https://sillytavern.app', |
| 358 | 'X-Title': 'SillyTavern', | 357 | 'X-Title': 'SillyTavern', |
| 359 | }; | 358 | }; |
| 360 | 359 | ||
| 361 | const OPENROUTER_KEYS = [ | 360 | export const OPENROUTER_KEYS = [ |
| 362 | 'max_tokens', | 361 | 'max_tokens', |
| 363 | 'temperature', | 362 | 'temperature', |
| 364 | 'top_k', | 363 | 'top_k', |
| @@ -378,7 +377,7 @@ const OPENROUTER_KEYS = [ | |||
| 378 | ]; | 377 | ]; |
| 379 | 378 | ||
| 380 | // https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220 | 379 | // https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220 |
| 381 | const VLLM_KEYS = [ | 380 | export const VLLM_KEYS = [ |
| 382 | 'model', | 381 | 'model', |
| 383 | 'prompt', | 382 | 'prompt', |
| 384 | 'best_of', | 383 | 'best_of', |
| @@ -419,27 +418,3 @@ const VLLM_KEYS = [ | |||
| 419 | 'guided_decoding_backend', | 418 | 'guided_decoding_backend', |
| 420 | 'guided_whitespace_pattern', | 419 | 'guided_whitespace_pattern', |
| 421 | ]; | 420 | ]; |
| 422 | |||
| 423 | module.exports = { | ||
| 424 | DEFAULT_USER, | ||
| 425 | SETTINGS_FILE, | ||
| 426 | PUBLIC_DIRECTORIES, | ||
| 427 | USER_DIRECTORY_TEMPLATE, | ||
| 428 | UNSAFE_EXTENSIONS, | ||
| 429 | UPLOADS_DIRECTORY, | ||
| 430 | GEMINI_SAFETY, | ||
| 431 | BISON_SAFETY, | ||
| 432 | TEXTGEN_TYPES, | ||
| 433 | CHAT_COMPLETION_SOURCES, | ||
| 434 | AVATAR_WIDTH, | ||
| 435 | AVATAR_HEIGHT, | ||
| 436 | TOGETHERAI_KEYS, | ||
| 437 | OLLAMA_KEYS, | ||
| 438 | INFERMATICAI_KEYS, | ||
| 439 | DREAMGEN_KEYS, | ||
| 440 | OPENROUTER_HEADERS, | ||
| 441 | OPENROUTER_KEYS, | ||
| 442 | VLLM_KEYS, | ||
| 443 | FEATHERLESS_KEYS, | ||
| 444 | FEATHERLESS_HEADERS, | ||
| 445 | }; | ||
| @@ -1,9 +1,10 @@ | |||
| 1 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 1 | import fetch from 'node-fetch'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import express from 'express'; |
| 3 | const express = require('express'); | ||
| 4 | const { jsonParser } = require('../express-common'); | ||
| 5 | 3 | ||
| 6 | const router = express.Router(); | 4 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| 5 | import { jsonParser } from '../express-common.js'; | ||
| 6 | |||
| 7 | export const router = express.Router(); | ||
| 7 | 8 | ||
| 8 | router.post('/caption-image', jsonParser, async (request, response) => { | 9 | router.post('/caption-image', jsonParser, async (request, response) => { |
| 9 | try { | 10 | try { |
| @@ -41,7 +42,6 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 41 | 'anthropic-version': '2023-06-01', | 42 | 'anthropic-version': '2023-06-01', |
| 42 | 'x-api-key': request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE), | 43 | 'x-api-key': request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.CLAUDE), |
| 43 | }, | 44 | }, |
| 44 | timeout: 0, | ||
| 45 | }); | 45 | }); |
| 46 | 46 | ||
| 47 | if (!result.ok) { | 47 | if (!result.ok) { |
| @@ -50,6 +50,7 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 50 | return response.status(result.status).send({ error: true }); | 50 | return response.status(result.status).send({ error: true }); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | /** @type {any} */ | ||
| 53 | const generateResponseJson = await result.json(); | 54 | const generateResponseJson = await result.json(); |
| 54 | const caption = generateResponseJson.content[0].text; | 55 | const caption = generateResponseJson.content[0].text; |
| 55 | console.log('Claude response:', generateResponseJson); | 56 | console.log('Claude response:', generateResponseJson); |
| @@ -64,5 +65,3 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 64 | response.status(500).send('Internal server error'); | 65 | response.status(500).send('Internal server error'); |
| 65 | } | 66 | } |
| 66 | }); | 67 | }); |
| 67 | |||
| 68 | module.exports = { router }; | ||
| @@ -1,13 +1,15 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fs = require('fs'); | 2 | import fs from 'node:fs'; |
| 3 | const mime = require('mime-types'); | 3 | import { finished } from 'node:stream/promises'; |
| 4 | const express = require('express'); | 4 | |
| 5 | const sanitize = require('sanitize-filename'); | 5 | import mime from 'mime-types'; |
| 6 | const fetch = require('node-fetch').default; | 6 | import express from 'express'; |
| 7 | const { finished } = require('stream/promises'); | 7 | import sanitize from 'sanitize-filename'; |
| 8 | const { UNSAFE_EXTENSIONS } = require('../constants'); | 8 | import fetch from 'node-fetch'; |
| 9 | const { jsonParser } = require('../express-common'); | 9 | |
| 10 | const { clientRelativePath } = require('../util'); | 10 | import { UNSAFE_EXTENSIONS } from '../constants.js'; |
| 11 | import { jsonParser } from '../express-common.js'; | ||
| 12 | import { clientRelativePath } from '../util.js'; | ||
| 11 | 13 | ||
| 12 | const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm', 'character', 'temp']; | 14 | const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm', 'character', 'temp']; |
| 13 | 15 | ||
| @@ -16,7 +18,7 @@ const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm', 'character' | |||
| 16 | * @param {string} inputFilename Input filename | 18 | * @param {string} inputFilename Input filename |
| 17 | * @returns {{error: boolean, message?: string}} Whether validation failed, and why if so | 19 | * @returns {{error: boolean, message?: string}} Whether validation failed, and why if so |
| 18 | */ | 20 | */ |
| 19 | function validateAssetFileName(inputFilename) { | 21 | export function validateAssetFileName(inputFilename) { |
| 20 | if (!/^[a-zA-Z0-9_\-.]+$/.test(inputFilename)) { | 22 | if (!/^[a-zA-Z0-9_\-.]+$/.test(inputFilename)) { |
| 21 | return { | 23 | return { |
| 22 | error: true, | 24 | error: true, |
| @@ -77,7 +79,7 @@ function getFiles(dir, files = []) { | |||
| 77 | 79 | ||
| 78 | /** | 80 | /** |
| 79 | * Ensure that the asset folders exist. | 81 | * Ensure that the asset folders exist. |
| 80 | * @param {import('../users').UserDirectoryList} directories - The user's directories | 82 | * @param {import('../users.js').UserDirectoryList} directories - The user's directories |
| 81 | */ | 83 | */ |
| 82 | function ensureFoldersExist(directories) { | 84 | function ensureFoldersExist(directories) { |
| 83 | const folderPath = path.join(directories.assets); | 85 | const folderPath = path.join(directories.assets); |
| @@ -93,7 +95,7 @@ function ensureFoldersExist(directories) { | |||
| 93 | } | 95 | } |
| 94 | } | 96 | } |
| 95 | 97 | ||
| 96 | const router = express.Router(); | 98 | export const router = express.Router(); |
| 97 | 99 | ||
| 98 | /** | 100 | /** |
| 99 | * HTTP POST handler function to retrieve name of all files of a given folder path. | 101 | * HTTP POST handler function to retrieve name of all files of a given folder path. |
| @@ -366,5 +368,3 @@ router.post('/character', jsonParser, async (request, response) => { | |||
| 366 | return response.sendStatus(500); | 368 | return response.sendStatus(500); |
| 367 | } | 369 | } |
| 368 | }); | 370 | }); |
| 369 | |||
| 370 | module.exports = { router, validateAssetFileName }; | ||
| @@ -1,16 +1,16 @@ | |||
| 1 | const express = require('express'); | 1 | import path from 'node:path'; |
| 2 | const path = require('path'); | 2 | import fs from 'node:fs'; |
| 3 | const fs = require('fs'); | ||
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | ||
| 6 | const { jsonParser, urlencodedParser } = require('../express-common'); | ||
| 7 | const { AVATAR_WIDTH, AVATAR_HEIGHT } = require('../constants'); | ||
| 8 | const { getImages, tryParse } = require('../util'); | ||
| 9 | 3 | ||
| 10 | // image processing related library imports | 4 | import express from 'express'; |
| 11 | const jimp = require('jimp'); | 5 | import sanitize from 'sanitize-filename'; |
| 6 | import jimp from 'jimp'; | ||
| 7 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 12 | 8 | ||
| 13 | const router = express.Router(); | 9 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 10 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; | ||
| 11 | import { getImages, tryParse } from '../util.js'; | ||
| 12 | |||
| 13 | export const router = express.Router(); | ||
| 14 | 14 | ||
| 15 | router.post('/get', jsonParser, function (request, response) { | 15 | router.post('/get', jsonParser, function (request, response) { |
| 16 | var images = getImages(request.user.directories.avatars); | 16 | var images = getImages(request.user.directories.avatars); |
| @@ -58,5 +58,3 @@ router.post('/upload', urlencodedParser, async (request, response) => { | |||
| 58 | return response.status(400).send('Is not a valid image'); | 58 | return response.status(400).send('Is not a valid image'); |
| 59 | } | 59 | } |
| 60 | }); | 60 | }); |
| 61 | |||
| 62 | module.exports = { router }; | ||
| @@ -1,9 +1,10 @@ | |||
| 1 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 1 | import fetch from 'node-fetch'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import { Router } from 'express'; |
| 3 | const express = require('express'); | ||
| 4 | const { jsonParser } = require('../express-common'); | ||
| 5 | 3 | ||
| 6 | const router = express.Router(); | 4 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| 5 | import { jsonParser } from '../express-common.js'; | ||
| 6 | |||
| 7 | export const router = Router(); | ||
| 7 | 8 | ||
| 8 | router.post('/list', jsonParser, async (req, res) => { | 9 | router.post('/list', jsonParser, async (req, res) => { |
| 9 | try { | 10 | try { |
| @@ -86,7 +87,3 @@ router.post('/generate', jsonParser, async (req, res) => { | |||
| 86 | return res.sendStatus(500); | 87 | return res.sendStatus(500); |
| 87 | } | 88 | } |
| 88 | }); | 89 | }); |
| 89 | |||
| 90 | module.exports = { | ||
| 91 | router, | ||
| 92 | }; | ||
| @@ -1,13 +1,41 @@ | |||
| 1 | const express = require('express'); | 1 | import process from 'node:process'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import express from 'express'; |
| 3 | 3 | import fetch from 'node-fetch'; | |
| 4 | const { jsonParser } = require('../../express-common'); | 4 | |
| 5 | const { CHAT_COMPLETION_SOURCES, GEMINI_SAFETY, BISON_SAFETY, OPENROUTER_HEADERS } = require('../../constants'); | 5 | import { jsonParser } from '../../express-common.js'; |
| 6 | const { forwardFetchResponse, getConfigValue, tryParse, uuidv4, mergeObjectWithYaml, excludeKeysByYaml, color } = require('../../util'); | 6 | import { |
| 7 | const { convertClaudeMessages, convertGooglePrompt, convertTextCompletionPrompt, convertCohereMessages, convertMistralMessages, convertAI21Messages, mergeMessages } = require('../../prompt-converters'); | 7 | CHAT_COMPLETION_SOURCES, |
| 8 | 8 | GEMINI_SAFETY, | |
| 9 | const { readSecret, SECRET_KEYS } = require('../secrets'); | 9 | BISON_SAFETY, |
| 10 | const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sentencepieceTokenizers, TEXT_COMPLETION_MODELS } = require('../tokenizers'); | 10 | OPENROUTER_HEADERS, |
| 11 | } from '../../constants.js'; | ||
| 12 | import { | ||
| 13 | forwardFetchResponse, | ||
| 14 | getConfigValue, | ||
| 15 | tryParse, | ||
| 16 | uuidv4, | ||
| 17 | mergeObjectWithYaml, | ||
| 18 | excludeKeysByYaml, | ||
| 19 | color, | ||
| 20 | } from '../../util.js'; | ||
| 21 | import { | ||
| 22 | convertClaudeMessages, | ||
| 23 | convertGooglePrompt, | ||
| 24 | convertTextCompletionPrompt, | ||
| 25 | convertCohereMessages, | ||
| 26 | convertMistralMessages, | ||
| 27 | convertAI21Messages, | ||
| 28 | mergeMessages, | ||
| 29 | } from '../../prompt-converters.js'; | ||
| 30 | |||
| 31 | import { readSecret, SECRET_KEYS } from '../secrets.js'; | ||
| 32 | import { | ||
| 33 | getTokenizerModel, | ||
| 34 | getSentencepiceTokenizer, | ||
| 35 | getTiktokenTokenizer, | ||
| 36 | sentencepieceTokenizers, | ||
| 37 | TEXT_COMPLETION_MODELS, | ||
| 38 | } from '../tokenizers.js'; | ||
| 11 | 39 | ||
| 12 | const API_OPENAI = 'https://api.openai.com/v1'; | 40 | const API_OPENAI = 'https://api.openai.com/v1'; |
| 13 | const API_CLAUDE = 'https://api.anthropic.com/v1'; | 41 | const API_CLAUDE = 'https://api.anthropic.com/v1'; |
| @@ -42,43 +70,6 @@ function postProcessPrompt(messages, type, charName, userName) { | |||
| 42 | } | 70 | } |
| 43 | 71 | ||
| 44 | /** | 72 | /** |
| 45 | * Ollama strikes back. Special boy #2's steaming routine. | ||
| 46 | * Wrap this abomination into proper SSE stream, again. | ||
| 47 | * @param {Response} jsonStream JSON stream | ||
| 48 | * @param {import('express').Request} request Express request | ||
| 49 | * @param {import('express').Response} response Express response | ||
| 50 | * @returns {Promise<any>} Nothing valuable | ||
| 51 | */ | ||
| 52 | async function parseCohereStream(jsonStream, request, response) { | ||
| 53 | try { | ||
| 54 | const stream = new CohereStream({ stream: jsonStream.body, eventShape: { type: 'json', messageTerminator: '\n' } }); | ||
| 55 | |||
| 56 | for await (const json of stream.iterMessages()) { | ||
| 57 | if (json.message) { | ||
| 58 | const message = json.message || 'Unknown error'; | ||
| 59 | const chunk = { error: { message: message } }; | ||
| 60 | response.write(`data: ${JSON.stringify(chunk)}\n\n`); | ||
| 61 | } else if (json.event_type === 'text-generation') { | ||
| 62 | const text = json.text || ''; | ||
| 63 | const chunk = { choices: [{ text }] }; | ||
| 64 | response.write(`data: ${JSON.stringify(chunk)}\n\n`); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | console.log('Streaming request finished'); | ||
| 69 | response.write('data: [DONE]\n\n'); | ||
| 70 | response.end(); | ||
| 71 | } catch (error) { | ||
| 72 | console.log('Error forwarding streaming response:', error); | ||
| 73 | if (!response.headersSent) { | ||
| 74 | return response.status(500).send({ error: true }); | ||
| 75 | } else { | ||
| 76 | return response.end(); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Sends a request to Claude API. | 73 | * Sends a request to Claude API. |
| 83 | * @param {express.Request} request Express request | 74 | * @param {express.Request} request Express request |
| 84 | * @param {express.Response} response Express response | 75 | * @param {express.Response} response Express response |
| @@ -161,7 +152,6 @@ async function sendClaudeRequest(request, response) { | |||
| 161 | 'x-api-key': apiKey, | 152 | 'x-api-key': apiKey, |
| 162 | ...additionalHeaders, | 153 | ...additionalHeaders, |
| 163 | }, | 154 | }, |
| 164 | timeout: 0, | ||
| 165 | }); | 155 | }); |
| 166 | 156 | ||
| 167 | if (request.body.stream) { | 157 | if (request.body.stream) { |
| @@ -174,6 +164,7 @@ async function sendClaudeRequest(request, response) { | |||
| 174 | return response.status(generateResponse.status).send({ error: true }); | 164 | return response.status(generateResponse.status).send({ error: true }); |
| 175 | } | 165 | } |
| 176 | 166 | ||
| 167 | /** @type {any} */ | ||
| 177 | const generateResponseJson = await generateResponse.json(); | 168 | const generateResponseJson = await generateResponse.json(); |
| 178 | const responseText = generateResponseJson?.content?.[0]?.text || ''; | 169 | const responseText = generateResponseJson?.content?.[0]?.text || ''; |
| 179 | console.log('Claude response:', generateResponseJson); | 170 | console.log('Claude response:', generateResponseJson); |
| @@ -221,7 +212,6 @@ async function sendScaleRequest(request, response) { | |||
| 221 | 'Content-Type': 'application/json', | 212 | 'Content-Type': 'application/json', |
| 222 | 'Authorization': `Basic ${apiKey}`, | 213 | 'Authorization': `Basic ${apiKey}`, |
| 223 | }, | 214 | }, |
| 224 | timeout: 0, | ||
| 225 | }); | 215 | }); |
| 226 | 216 | ||
| 227 | if (!generateResponse.ok) { | 217 | if (!generateResponse.ok) { |
| @@ -229,6 +219,7 @@ async function sendScaleRequest(request, response) { | |||
| 229 | return response.status(500).send({ error: true }); | 219 | return response.status(500).send({ error: true }); |
| 230 | } | 220 | } |
| 231 | 221 | ||
| 222 | /** @type {any} */ | ||
| 232 | const generateResponseJson = await generateResponse.json(); | 223 | const generateResponseJson = await generateResponse.json(); |
| 233 | console.log('Scale response:', generateResponseJson); | 224 | console.log('Scale response:', generateResponseJson); |
| 234 | 225 | ||
| @@ -337,14 +328,13 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 337 | ? (stream ? 'streamGenerateContent' : 'generateContent') | 328 | ? (stream ? 'streamGenerateContent' : 'generateContent') |
| 338 | : (isText ? 'generateText' : 'generateMessage'); | 329 | : (isText ? 'generateText' : 'generateMessage'); |
| 339 | 330 | ||
| 340 | const generateResponse = await fetch(`${apiUrl}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { | 331 | const generateResponse = await fetch(`${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { |
| 341 | body: JSON.stringify(body), | 332 | body: JSON.stringify(body), |
| 342 | method: 'POST', | 333 | method: 'POST', |
| 343 | headers: { | 334 | headers: { |
| 344 | 'Content-Type': 'application/json', | 335 | 'Content-Type': 'application/json', |
| 345 | }, | 336 | }, |
| 346 | signal: controller.signal, | 337 | signal: controller.signal, |
| 347 | timeout: 0, | ||
| 348 | }); | 338 | }); |
| 349 | // have to do this because of their busted ass streaming endpoint | 339 | // have to do this because of their busted ass streaming endpoint |
| 350 | if (stream) { | 340 | if (stream) { |
| @@ -363,6 +353,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 363 | return response.status(generateResponse.status).send({ error: true }); | 353 | return response.status(generateResponse.status).send({ error: true }); |
| 364 | } | 354 | } |
| 365 | 355 | ||
| 356 | /** @type {any} */ | ||
| 366 | const generateResponseJson = await generateResponse.json(); | 357 | const generateResponseJson = await generateResponse.json(); |
| 367 | 358 | ||
| 368 | const candidates = generateResponseJson?.candidates; | 359 | const candidates = generateResponseJson?.candidates; |
| @@ -626,7 +617,7 @@ async function sendCohereRequest(request, response) { | |||
| 626 | } | 617 | } |
| 627 | } | 618 | } |
| 628 | 619 | ||
| 629 | const router = express.Router(); | 620 | export const router = express.Router(); |
| 630 | 621 | ||
| 631 | router.post('/status', jsonParser, async function (request, response_getstatus_openai) { | 622 | router.post('/status', jsonParser, async function (request, response_getstatus_openai) { |
| 632 | if (!request.body) return response_getstatus_openai.sendStatus(400); | 623 | if (!request.body) return response_getstatus_openai.sendStatus(400); |
| @@ -685,6 +676,7 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o | |||
| 685 | }); | 676 | }); |
| 686 | 677 | ||
| 687 | if (response.ok) { | 678 | if (response.ok) { |
| 679 | /** @type {any} */ | ||
| 688 | const data = await response.json(); | 680 | const data = await response.json(); |
| 689 | response_getstatus_openai.send(data); | 681 | response_getstatus_openai.send(data); |
| 690 | 682 | ||
| @@ -988,7 +980,6 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 988 | }, | 980 | }, |
| 989 | body: JSON.stringify(requestBody), | 981 | body: JSON.stringify(requestBody), |
| 990 | signal: controller.signal, | 982 | signal: controller.signal, |
| 991 | timeout: 0, | ||
| 992 | }; | 983 | }; |
| 993 | 984 | ||
| 994 | console.log(requestBody); | 985 | console.log(requestBody); |
| @@ -1014,6 +1005,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1014 | } | 1005 | } |
| 1015 | 1006 | ||
| 1016 | if (fetchResponse.ok) { | 1007 | if (fetchResponse.ok) { |
| 1008 | /** @type {any} */ | ||
| 1017 | let json = await fetchResponse.json(); | 1009 | let json = await fetchResponse.json(); |
| 1018 | response.send(json); | 1010 | response.send(json); |
| 1019 | console.log(json); | 1011 | console.log(json); |
| @@ -1069,6 +1061,3 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1069 | } | 1061 | } |
| 1070 | }); | 1062 | }); |
| 1071 | 1063 | ||
| 1072 | module.exports = { | ||
| 1073 | router, | ||
| 1074 | }; | ||
| @@ -1,13 +1,13 @@ | |||
| 1 | const express = require('express'); | 1 | import fs from 'node:fs'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import express from 'express'; |
| 3 | const fs = require('fs'); | 3 | import fetch from 'node-fetch'; |
| 4 | 4 | ||
| 5 | const { jsonParser, urlencodedParser } = require('../../express-common'); | 5 | import { jsonParser, urlencodedParser } from '../../express-common.js'; |
| 6 | const { forwardFetchResponse, delay } = require('../../util'); | 6 | import { forwardFetchResponse, delay } from '../../util.js'; |
| 7 | const { getOverrideHeaders, setAdditionalHeaders, setAdditionalHeadersByType } = require('../../additional-headers'); | 7 | import { getOverrideHeaders, setAdditionalHeaders, setAdditionalHeadersByType } from '../../additional-headers.js'; |
| 8 | const { TEXTGEN_TYPES } = require('../../constants'); | 8 | import { TEXTGEN_TYPES } from '../../constants.js'; |
| 9 | 9 | ||
| 10 | const router = express.Router(); | 10 | export const router = express.Router(); |
| 11 | 11 | ||
| 12 | router.post('/generate', jsonParser, async function (request, response_generate) { | 12 | router.post('/generate', jsonParser, async function (request, response_generate) { |
| 13 | if (!request.body) return response_generate.sendStatus(400); | 13 | if (!request.body) return response_generate.sendStatus(400); |
| @@ -96,7 +96,7 @@ router.post('/generate', jsonParser, async function (request, response_generate) | |||
| 96 | for (let i = 0; i < MAX_RETRIES; i++) { | 96 | for (let i = 0; i < MAX_RETRIES; i++) { |
| 97 | try { | 97 | try { |
| 98 | const url = request.body.streaming ? `${request.body.api_server}/extra/generate/stream` : `${request.body.api_server}/v1/generate`; | 98 | const url = request.body.streaming ? `${request.body.api_server}/extra/generate/stream` : `${request.body.api_server}/v1/generate`; |
| 99 | const response = await fetch(url, { method: 'POST', timeout: 0, ...args }); | 99 | const response = await fetch(url, { method: 'POST', ...args }); |
| 100 | 100 | ||
| 101 | if (request.body.streaming) { | 101 | if (request.body.streaming) { |
| 102 | // Pipe remote SSE stream to Express response | 102 | // Pipe remote SSE stream to Express response |
| @@ -156,6 +156,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 156 | 156 | ||
| 157 | const result = {}; | 157 | const result = {}; |
| 158 | 158 | ||
| 159 | /** @type {any} */ | ||
| 159 | const [koboldUnitedResponse, koboldExtraResponse, koboldModelResponse] = await Promise.all([ | 160 | const [koboldUnitedResponse, koboldExtraResponse, koboldModelResponse] = await Promise.all([ |
| 160 | // We catch errors both from the response not having a successful HTTP status and from JSON parsing failing | 161 | // We catch errors both from the response not having a successful HTTP status and from JSON parsing failing |
| 161 | 162 | ||
| @@ -237,5 +238,3 @@ router.post('/transcribe-audio', urlencodedParser, async function (request, resp | |||
| 237 | response.status(500).send('Internal server error'); | 238 | response.status(500).send('Internal server error'); |
| 238 | } | 239 | } |
| 239 | }); | 240 | }); |
| 240 | |||
| 241 | module.exports = { router }; | ||
| @@ -1,11 +1,10 @@ | |||
| 1 | const express = require('express'); | 1 | import express from 'express'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import fetch from 'node-fetch'; |
| 3 | 3 | ||
| 4 | const { jsonParser } = require('../../express-common'); | 4 | import { jsonParser } from '../../express-common.js'; |
| 5 | import { readSecret, SECRET_KEYS } from '../secrets.js'; | ||
| 5 | 6 | ||
| 6 | const { readSecret, SECRET_KEYS } = require('../secrets'); | 7 | export const router = express.Router(); |
| 7 | |||
| 8 | const router = express.Router(); | ||
| 9 | 8 | ||
| 10 | router.post('/generate', jsonParser, async function (request, response) { | 9 | router.post('/generate', jsonParser, async function (request, response) { |
| 11 | if (!request.body) return response.sendStatus(400); | 10 | if (!request.body) return response.sendStatus(400); |
| @@ -71,7 +70,6 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 71 | 'Content-Type': 'application/json', | 70 | 'Content-Type': 'application/json', |
| 72 | 'cookie': `_jwt=${cookie}`, | 71 | 'cookie': `_jwt=${cookie}`, |
| 73 | }, | 72 | }, |
| 74 | timeout: 0, | ||
| 75 | body: JSON.stringify(body), | 73 | body: JSON.stringify(body), |
| 76 | }); | 74 | }); |
| 77 | 75 | ||
| @@ -81,6 +79,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 81 | return response.status(500).send({ error: { message: result.statusText } }); | 79 | return response.status(500).send({ error: { message: result.statusText } }); |
| 82 | } | 80 | } |
| 83 | 81 | ||
| 82 | /** @type {any} */ | ||
| 84 | const data = await result.json(); | 83 | const data = await result.json(); |
| 85 | const output = data?.result?.data?.json?.outputs?.[0] || ''; | 84 | const output = data?.result?.data?.json?.outputs?.[0] || ''; |
| 86 | 85 | ||
| @@ -97,5 +96,3 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 97 | return response.sendStatus(500); | 96 | return response.sendStatus(500); |
| 98 | } | 97 | } |
| 99 | }); | 98 | }); |
| 100 | |||
| 101 | module.exports = { router }; | ||
| @@ -1,14 +1,23 @@ | |||
| 1 | const express = require('express'); | 1 | import { Readable } from 'node:stream'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import fetch from 'node-fetch'; |
| 3 | const _ = require('lodash'); | 3 | import express from 'express'; |
| 4 | const Readable = require('stream').Readable; | 4 | import _ from 'lodash'; |
| 5 | 5 | ||
| 6 | const { jsonParser } = require('../../express-common'); | 6 | import { jsonParser } from '../../express-common.js'; |
| 7 | const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS, INFERMATICAI_KEYS, OPENROUTER_KEYS, VLLM_KEYS, DREAMGEN_KEYS, FEATHERLESS_KEYS } = require('../../constants'); | 7 | import { |
| 8 | const { forwardFetchResponse, trimV1, getConfigValue } = require('../../util'); | 8 | TEXTGEN_TYPES, |
| 9 | const { setAdditionalHeaders } = require('../../additional-headers'); | 9 | TOGETHERAI_KEYS, |
| 10 | 10 | OLLAMA_KEYS, | |
| 11 | const router = express.Router(); | 11 | INFERMATICAI_KEYS, |
| 12 | OPENROUTER_KEYS, | ||
| 13 | VLLM_KEYS, | ||
| 14 | DREAMGEN_KEYS, | ||
| 15 | FEATHERLESS_KEYS, | ||
| 16 | } from '../../constants.js'; | ||
| 17 | import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js'; | ||
| 18 | import { setAdditionalHeaders } from '../../additional-headers.js'; | ||
| 19 | |||
| 20 | export const router = express.Router(); | ||
| 12 | 21 | ||
| 13 | /** | 22 | /** |
| 14 | * Special boy's steaming routine. Wrap this abomination into proper SSE stream. | 23 | * Special boy's steaming routine. Wrap this abomination into proper SSE stream. |
| @@ -19,6 +28,10 @@ const router = express.Router(); | |||
| 19 | */ | 28 | */ |
| 20 | async function parseOllamaStream(jsonStream, request, response) { | 29 | async function parseOllamaStream(jsonStream, request, response) { |
| 21 | try { | 30 | try { |
| 31 | if (!jsonStream.body) { | ||
| 32 | throw new Error('No body in the response'); | ||
| 33 | } | ||
| 34 | |||
| 22 | let partialData = ''; | 35 | let partialData = ''; |
| 23 | jsonStream.body.on('data', (data) => { | 36 | jsonStream.body.on('data', (data) => { |
| 24 | const chunk = data.toString(); | 37 | const chunk = data.toString(); |
| @@ -144,6 +157,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 144 | return response.status(400); | 157 | return response.status(400); |
| 145 | } | 158 | } |
| 146 | 159 | ||
| 160 | /** @type {any} */ | ||
| 147 | let data = await modelsReply.json(); | 161 | let data = await modelsReply.json(); |
| 148 | 162 | ||
| 149 | if (request.body.legacy_api) { | 163 | if (request.body.legacy_api) { |
| @@ -181,6 +195,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 181 | const modelInfoReply = await fetch(modelInfoUrl, args); | 195 | const modelInfoReply = await fetch(modelInfoUrl, args); |
| 182 | 196 | ||
| 183 | if (modelInfoReply.ok) { | 197 | if (modelInfoReply.ok) { |
| 198 | /** @type {any} */ | ||
| 184 | const modelInfo = await modelInfoReply.json(); | 199 | const modelInfo = await modelInfoReply.json(); |
| 185 | console.log('Ooba model info:', modelInfo); | 200 | console.log('Ooba model info:', modelInfo); |
| 186 | 201 | ||
| @@ -197,6 +212,7 @@ router.post('/status', jsonParser, async function (request, response) { | |||
| 197 | const modelInfoReply = await fetch(modelInfoUrl, args); | 212 | const modelInfoReply = await fetch(modelInfoUrl, args); |
| 198 | 213 | ||
| 199 | if (modelInfoReply.ok) { | 214 | if (modelInfoReply.ok) { |
| 215 | /** @type {any} */ | ||
| 200 | const modelInfo = await modelInfoReply.json(); | 216 | const modelInfo = await modelInfoReply.json(); |
| 201 | console.log('Tabby model info:', modelInfo); | 217 | console.log('Tabby model info:', modelInfo); |
| 202 | 218 | ||
| @@ -350,6 +366,7 @@ router.post('/generate', jsonParser, async function (request, response) { | |||
| 350 | const completionsReply = await fetch(url, args); | 366 | const completionsReply = await fetch(url, args); |
| 351 | 367 | ||
| 352 | if (completionsReply.ok) { | 368 | if (completionsReply.ok) { |
| 369 | /** @type {any} */ | ||
| 353 | const data = await completionsReply.json(); | 370 | const data = await completionsReply.json(); |
| 354 | console.log('Endpoint response:', data); | 371 | console.log('Endpoint response:', data); |
| 355 | 372 | ||
| @@ -406,7 +423,6 @@ ollama.post('/download', jsonParser, async function (request, response) { | |||
| 406 | name: name, | 423 | name: name, |
| 407 | stream: false, | 424 | stream: false, |
| 408 | }), | 425 | }), |
| 409 | timeout: 0, | ||
| 410 | }); | 426 | }); |
| 411 | 427 | ||
| 412 | if (!fetchResponse.ok) { | 428 | if (!fetchResponse.ok) { |
| @@ -439,7 +455,6 @@ ollama.post('/caption-image', jsonParser, async function (request, response) { | |||
| 439 | images: [request.body.image], | 455 | images: [request.body.image], |
| 440 | stream: false, | 456 | stream: false, |
| 441 | }), | 457 | }), |
| 442 | timeout: 0, | ||
| 443 | }); | 458 | }); |
| 444 | 459 | ||
| 445 | if (!fetchResponse.ok) { | 460 | if (!fetchResponse.ok) { |
| @@ -447,6 +462,7 @@ ollama.post('/caption-image', jsonParser, async function (request, response) { | |||
| 447 | return response.status(500).send({ error: true }); | 462 | return response.status(500).send({ error: true }); |
| 448 | } | 463 | } |
| 449 | 464 | ||
| 465 | /** @type {any} */ | ||
| 450 | const data = await fetchResponse.json(); | 466 | const data = await fetchResponse.json(); |
| 451 | console.log('Ollama caption response:', data); | 467 | console.log('Ollama caption response:', data); |
| 452 | 468 | ||
| @@ -478,7 +494,6 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) { | |||
| 478 | const fetchResponse = await fetch(`${baseUrl}/completion`, { | 494 | const fetchResponse = await fetch(`${baseUrl}/completion`, { |
| 479 | method: 'POST', | 495 | method: 'POST', |
| 480 | headers: { 'Content-Type': 'application/json' }, | 496 | headers: { 'Content-Type': 'application/json' }, |
| 481 | timeout: 0, | ||
| 482 | body: JSON.stringify({ | 497 | body: JSON.stringify({ |
| 483 | prompt: `USER:[img-1]${String(request.body.prompt).trim()}\nASSISTANT:`, | 498 | prompt: `USER:[img-1]${String(request.body.prompt).trim()}\nASSISTANT:`, |
| 484 | image_data: [{ data: request.body.image, id: 1 }], | 499 | image_data: [{ data: request.body.image, id: 1 }], |
| @@ -493,6 +508,7 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) { | |||
| 493 | return response.status(500).send({ error: true }); | 508 | return response.status(500).send({ error: true }); |
| 494 | } | 509 | } |
| 495 | 510 | ||
| 511 | /** @type {any} */ | ||
| 496 | const data = await fetchResponse.json(); | 512 | const data = await fetchResponse.json(); |
| 497 | console.log('LlamaCpp caption response:', data); | 513 | console.log('LlamaCpp caption response:', data); |
| 498 | 514 | ||
| @@ -522,7 +538,6 @@ llamacpp.post('/props', jsonParser, async function (request, response) { | |||
| 522 | 538 | ||
| 523 | const fetchResponse = await fetch(`${baseUrl}/props`, { | 539 | const fetchResponse = await fetch(`${baseUrl}/props`, { |
| 524 | method: 'GET', | 540 | method: 'GET', |
| 525 | timeout: 0, | ||
| 526 | }); | 541 | }); |
| 527 | 542 | ||
| 528 | if (!fetchResponse.ok) { | 543 | if (!fetchResponse.ok) { |
| @@ -557,7 +572,6 @@ llamacpp.post('/slots', jsonParser, async function (request, response) { | |||
| 557 | if (request.body.action === 'info') { | 572 | if (request.body.action === 'info') { |
| 558 | fetchResponse = await fetch(`${baseUrl}/slots`, { | 573 | fetchResponse = await fetch(`${baseUrl}/slots`, { |
| 559 | method: 'GET', | 574 | method: 'GET', |
| 560 | timeout: 0, | ||
| 561 | }); | 575 | }); |
| 562 | } else { | 576 | } else { |
| 563 | if (!/^\d+$/.test(request.body.id_slot)) { | 577 | if (!/^\d+$/.test(request.body.id_slot)) { |
| @@ -570,7 +584,6 @@ llamacpp.post('/slots', jsonParser, async function (request, response) { | |||
| 570 | fetchResponse = await fetch(`${baseUrl}/slots/${request.body.id_slot}?action=${request.body.action}`, { | 584 | fetchResponse = await fetch(`${baseUrl}/slots/${request.body.id_slot}?action=${request.body.action}`, { |
| 571 | method: 'POST', | 585 | method: 'POST', |
| 572 | headers: { 'Content-Type': 'application/json' }, | 586 | headers: { 'Content-Type': 'application/json' }, |
| 573 | timeout: 0, | ||
| 574 | body: JSON.stringify({ | 587 | body: JSON.stringify({ |
| 575 | filename: request.body.action !== 'erase' ? `${request.body.filename}` : undefined, | 588 | filename: request.body.action !== 'erase' ? `${request.body.filename}` : undefined, |
| 576 | }), | 589 | }), |
| @@ -614,6 +627,7 @@ tabby.post('/download', jsonParser, async function (request, response) { | |||
| 614 | }); | 627 | }); |
| 615 | 628 | ||
| 616 | if (permissionResponse.ok) { | 629 | if (permissionResponse.ok) { |
| 630 | /** @type {any} */ | ||
| 617 | const permissionJson = await permissionResponse.json(); | 631 | const permissionJson = await permissionResponse.json(); |
| 618 | 632 | ||
| 619 | if (permissionJson['permission'] !== 'admin') { | 633 | if (permissionJson['permission'] !== 'admin') { |
| @@ -641,5 +655,3 @@ tabby.post('/download', jsonParser, async function (request, response) { | |||
| 641 | router.use('/ollama', ollama); | 655 | router.use('/ollama', ollama); |
| 642 | router.use('/llamacpp', llamacpp); | 656 | router.use('/llamacpp', llamacpp); |
| 643 | router.use('/tabby', tabby); | 657 | router.use('/tabby', tabby); |
| 644 | |||
| 645 | module.exports = { router }; | ||
| @@ -1,13 +1,14 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | ||
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | 3 | ||
| 6 | const { jsonParser, urlencodedParser } = require('../express-common'); | 4 | import express from 'express'; |
| 7 | const { invalidateThumbnail } = require('./thumbnails'); | 5 | import sanitize from 'sanitize-filename'; |
| 8 | const { getImages } = require('../util'); | ||
| 9 | 6 | ||
| 10 | const router = express.Router(); | 7 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 8 | import { invalidateThumbnail } from './thumbnails.js'; | ||
| 9 | import { getImages } from '../util.js'; | ||
| 10 | |||
| 11 | export const router = express.Router(); | ||
| 11 | 12 | ||
| 12 | router.post('/all', jsonParser, function (request, response) { | 13 | router.post('/all', jsonParser, function (request, response) { |
| 13 | var images = getImages(request.user.directories.backgrounds); | 14 | var images = getImages(request.user.directories.backgrounds); |
| @@ -72,5 +73,3 @@ router.post('/upload', urlencodedParser, function (request, response) { | |||
| 72 | response.sendStatus(500); | 73 | response.sendStatus(500); |
| 73 | } | 74 | } |
| 74 | }); | 75 | }); |
| 75 | |||
| 76 | module.exports = { router }; | ||
| @@ -1,23 +1,23 @@ | |||
| 1 | const express = require('express'); | 1 | import express from 'express'; |
| 2 | const { jsonParser } = require('../express-common'); | 2 | import { jsonParser } from '../express-common.js'; |
| 3 | import { getPipeline, getRawImage } from '../transformers.mjs'; | ||
| 3 | 4 | ||
| 4 | const TASK = 'image-to-text'; | 5 | const TASK = 'image-to-text'; |
| 5 | 6 | ||
| 6 | const router = express.Router(); | 7 | export const router = express.Router(); |
| 7 | 8 | ||
| 8 | router.post('/', jsonParser, async (req, res) => { | 9 | router.post('/', jsonParser, async (req, res) => { |
| 9 | try { | 10 | try { |
| 10 | const { image } = req.body; | 11 | const { image } = req.body; |
| 11 | 12 | ||
| 12 | const module = await import('../transformers.mjs'); | 13 | const rawImage = await getRawImage(image); |
| 13 | const rawImage = await module.default.getRawImage(image); | ||
| 14 | 14 | ||
| 15 | if (!rawImage) { | 15 | if (!rawImage) { |
| 16 | console.log('Failed to parse captioned image'); | 16 | console.log('Failed to parse captioned image'); |
| 17 | return res.sendStatus(400); | 17 | return res.sendStatus(400); |
| 18 | } | 18 | } |
| 19 | 19 | ||
| 20 | const pipe = await module.default.getPipeline(TASK); | 20 | const pipe = await getPipeline(TASK); |
| 21 | const result = await pipe(rawImage); | 21 | const result = await pipe(rawImage); |
| 22 | const text = result[0].generated_text; | 22 | const text = result[0].generated_text; |
| 23 | console.log('Image caption:', text); | 23 | console.log('Image caption:', text); |
| @@ -28,5 +28,3 @@ router.post('/', jsonParser, async (req, res) => { | |||
| 28 | return res.sendStatus(500); | 28 | return res.sendStatus(500); |
| 29 | } | 29 | } |
| 30 | }); | 30 | }); |
| 31 | |||
| 32 | module.exports = { router }; | ||
| @@ -1,24 +1,25 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fs = require('fs'); | 2 | import fs from 'node:fs'; |
| 3 | const fsPromises = require('fs').promises; | 3 | import { promises as fsPromises } from 'node:fs'; |
| 4 | const readline = require('readline'); | 4 | import readline from 'node:readline'; |
| 5 | const express = require('express'); | 5 | import { Buffer } from 'node:buffer'; |
| 6 | const sanitize = require('sanitize-filename'); | 6 | |
| 7 | const writeFileAtomicSync = require('write-file-atomic').sync; | 7 | import express from 'express'; |
| 8 | const yaml = require('yaml'); | 8 | import sanitize from 'sanitize-filename'; |
| 9 | const _ = require('lodash'); | 9 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 10 | const mime = require('mime-types'); | 10 | import yaml from 'yaml'; |
| 11 | 11 | import _ from 'lodash'; | |
| 12 | const jimp = require('jimp'); | 12 | import mime from 'mime-types'; |
| 13 | 13 | import jimp from 'jimp'; | |
| 14 | const { AVATAR_WIDTH, AVATAR_HEIGHT } = require('../constants'); | 14 | |
| 15 | const { jsonParser, urlencodedParser } = require('../express-common'); | 15 | import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js'; |
| 16 | const { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer } = require('../util'); | 16 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 17 | const { TavernCardValidator } = require('../validator/TavernCardValidator'); | 17 | import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer } from '../util.js'; |
| 18 | const characterCardParser = require('../character-card-parser.js'); | 18 | import { TavernCardValidator } from '../validator/TavernCardValidator.js'; |
| 19 | const { readWorldInfoFile } = require('./worldinfo'); | 19 | import { parse, write } from '../character-card-parser.js'; |
| 20 | const { invalidateThumbnail } = require('./thumbnails'); | 20 | import { readWorldInfoFile } from './worldinfo.js'; |
| 21 | const { importRisuSprites } = require('./sprites'); | 21 | import { invalidateThumbnail } from './thumbnails.js'; |
| 22 | import { importRisuSprites } from './sprites.js'; | ||
| 22 | const defaultAvatarPath = './public/img/ai4.png'; | 23 | const defaultAvatarPath = './public/img/ai4.png'; |
| 23 | 24 | ||
| 24 | // KV-store for parsed character data | 25 | // KV-store for parsed character data |
| @@ -37,7 +38,7 @@ async function readCharacterData(inputFile, inputFormat = 'png') { | |||
| 37 | return characterDataCache.get(cacheKey); | 38 | return characterDataCache.get(cacheKey); |
| 38 | } | 39 | } |
| 39 | 40 | ||
| 40 | const result = characterCardParser.parse(inputFile, inputFormat); | 41 | const result = parse(inputFile, inputFormat); |
| 41 | characterDataCache.set(cacheKey, result); | 42 | characterDataCache.set(cacheKey, result); |
| 42 | return result; | 43 | return result; |
| 43 | } | 44 | } |
| @@ -76,7 +77,7 @@ async function writeCharacterData(inputFile, data, outputFile, request, crop = u | |||
| 76 | const inputImage = await getInputImage(); | 77 | const inputImage = await getInputImage(); |
| 77 | 78 | ||
| 78 | // Get the chunks | 79 | // Get the chunks |
| 79 | const outputImage = characterCardParser.write(inputImage, data); | 80 | const outputImage = write(inputImage, data); |
| 80 | const outputImagePath = path.join(request.user.directories.characters, `${outputFile}.png`); | 81 | const outputImagePath = path.join(request.user.directories.characters, `${outputFile}.png`); |
| 81 | 82 | ||
| 82 | writeFileAtomicSync(outputImagePath, outputImage); | 83 | writeFileAtomicSync(outputImagePath, outputImage); |
| @@ -188,7 +189,7 @@ const calculateDataSize = (data) => { | |||
| 188 | * processCharacter - Process a given character, read its data and calculate its statistics. | 189 | * processCharacter - Process a given character, read its data and calculate its statistics. |
| 189 | * | 190 | * |
| 190 | * @param {string} item The name of the character. | 191 | * @param {string} item The name of the character. |
| 191 | * @param {import('../users').UserDirectoryList} directories User directories | 192 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 192 | * @return {Promise<object>} A Promise that resolves when the character processing is done. | 193 | * @return {Promise<object>} A Promise that resolves when the character processing is done. |
| 193 | */ | 194 | */ |
| 194 | const processCharacter = async (item, directories) => { | 195 | const processCharacter = async (item, directories) => { |
| @@ -232,7 +233,7 @@ const processCharacter = async (item, directories) => { | |||
| 232 | /** | 233 | /** |
| 233 | * Convert a character object to Spec V2 format. | 234 | * Convert a character object to Spec V2 format. |
| 234 | * @param {object} jsonObject Character object | 235 | * @param {object} jsonObject Character object |
| 235 | * @param {import('../users').UserDirectoryList} directories User directories | 236 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 236 | * @param {boolean} hoistDate Will set the chat and create_date fields to the current date if they are missing | 237 | * @param {boolean} hoistDate Will set the chat and create_date fields to the current date if they are missing |
| 237 | * @returns {object} Character object in Spec V2 format | 238 | * @returns {object} Character object in Spec V2 format |
| 238 | */ | 239 | */ |
| @@ -252,7 +253,7 @@ function getCharaCardV2(jsonObject, directories, hoistDate = true) { | |||
| 252 | /** | 253 | /** |
| 253 | * Convert a character object to Spec V2 format. | 254 | * Convert a character object to Spec V2 format. |
| 254 | * @param {object} char Character object | 255 | * @param {object} char Character object |
| 255 | * @param {import('../users').UserDirectoryList} directories User directories | 256 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 256 | * @returns {object} Character object in Spec V2 format | 257 | * @returns {object} Character object in Spec V2 format |
| 257 | */ | 258 | */ |
| 258 | function convertToV2(char, directories) { | 259 | function convertToV2(char, directories) { |
| @@ -342,7 +343,7 @@ function readFromV2(char) { | |||
| 342 | /** | 343 | /** |
| 343 | * Format character data to Spec V2 format. | 344 | * Format character data to Spec V2 format. |
| 344 | * @param {object} data Character data | 345 | * @param {object} data Character data |
| 345 | * @param {import('../users').UserDirectoryList} directories User directories | 346 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 346 | * @returns | 347 | * @returns |
| 347 | */ | 348 | */ |
| 348 | function charaFormatData(data, directories) { | 349 | function charaFormatData(data, directories) { |
| @@ -467,6 +468,7 @@ function convertWorldInfoToCharacterBook(name, entries) { | |||
| 467 | position: entry.position == 0 ? 'before_char' : 'after_char', | 468 | position: entry.position == 0 ? 'before_char' : 'after_char', |
| 468 | use_regex: true, // ST keys are always regex | 469 | use_regex: true, // ST keys are always regex |
| 469 | extensions: { | 470 | extensions: { |
| 471 | ...entry.extensions, | ||
| 470 | position: entry.position, | 472 | position: entry.position, |
| 471 | exclude_recursion: entry.excludeRecursion, | 473 | exclude_recursion: entry.excludeRecursion, |
| 472 | display_index: entry.displayIndex, | 474 | display_index: entry.displayIndex, |
| @@ -715,7 +717,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) { | |||
| 715 | return ''; | 717 | return ''; |
| 716 | } | 718 | } |
| 717 | 719 | ||
| 718 | const router = express.Router(); | 720 | export const router = express.Router(); |
| 719 | 721 | ||
| 720 | router.post('/create', urlencodedParser, async function (request, response) { | 722 | router.post('/create', urlencodedParser, async function (request, response) { |
| 721 | try { | 723 | try { |
| @@ -1081,7 +1083,7 @@ router.post('/chats', jsonParser, async function (request, response) { | |||
| 1081 | /** | 1083 | /** |
| 1082 | * Gets the name for the uploaded PNG file. | 1084 | * Gets the name for the uploaded PNG file. |
| 1083 | * @param {string} file File name | 1085 | * @param {string} file File name |
| 1084 | * @param {import('../users').UserDirectoryList} directories User directories | 1086 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 1085 | * @returns {string} - The name for the uploaded PNG file | 1087 | * @returns {string} - The name for the uploaded PNG file |
| 1086 | */ | 1088 | */ |
| 1087 | function getPngName(file, directories) { | 1089 | function getPngName(file, directories) { |
| @@ -1231,5 +1233,3 @@ router.post('/export', jsonParser, async function (request, response) { | |||
| 1231 | response.sendStatus(500); | 1233 | response.sendStatus(500); |
| 1232 | } | 1234 | } |
| 1233 | }); | 1235 | }); |
| 1234 | |||
| 1235 | module.exports = { router }; | ||
| @@ -1,13 +1,15 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const readline = require('readline'); | 3 | import readline from 'node:readline'; |
| 4 | const express = require('express'); | 4 | import process from 'node:process'; |
| 5 | const sanitize = require('sanitize-filename'); | ||
| 6 | const writeFileAtomicSync = require('write-file-atomic').sync; | ||
| 7 | const _ = require('lodash'); | ||
| 8 | 5 | ||
| 9 | const { jsonParser, urlencodedParser } = require('../express-common'); | 6 | import express from 'express'; |
| 10 | const { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } = require('../util'); | 7 | import sanitize from 'sanitize-filename'; |
| 8 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 9 | import _ from 'lodash'; | ||
| 10 | |||
| 11 | import { jsonParser, urlencodedParser } from '../express-common.js'; | ||
| 12 | import { getConfigValue, humanizedISO8601DateTime, tryParse, generateTimestamp, removeOldBackups } from '../util.js'; | ||
| 11 | 13 | ||
| 12 | /** | 14 | /** |
| 13 | * Saves a chat to the backups directory. | 15 | * Saves a chat to the backups directory. |
| @@ -188,7 +190,7 @@ function flattenChubChat(userName, characterName, lines) { | |||
| 188 | return (lines ?? []).map(convert).join('\n'); | 190 | return (lines ?? []).map(convert).join('\n'); |
| 189 | } | 191 | } |
| 190 | 192 | ||
| 191 | const router = express.Router(); | 193 | export const router = express.Router(); |
| 192 | 194 | ||
| 193 | router.post('/save', jsonParser, function (request, response) { | 195 | router.post('/save', jsonParser, function (request, response) { |
| 194 | try { | 196 | try { |
| @@ -514,5 +516,3 @@ router.post('/group/save', jsonParser, (request, response) => { | |||
| 514 | getBackupFunction(request.user.profile.handle)(request.user.directories.backups, String(id), jsonlData); | 516 | getBackupFunction(request.user.profile.handle)(request.user.directories.backups, String(id), jsonlData); |
| 515 | return response.send({ ok: true }); | 517 | return response.send({ ok: true }); |
| 516 | }); | 518 | }); |
| 517 | |||
| 518 | module.exports = { router }; | ||
| @@ -1,9 +1,11 @@ | |||
| 1 | const express = require('express'); | 1 | import express from 'express'; |
| 2 | const { jsonParser } = require('../express-common'); | 2 | |
| 3 | import { getPipeline } from '../transformers.mjs'; | ||
| 4 | import { jsonParser } from '../express-common.js'; | ||
| 3 | 5 | ||
| 4 | const TASK = 'text-classification'; | 6 | const TASK = 'text-classification'; |
| 5 | 7 | ||
| 6 | const router = express.Router(); | 8 | export const router = express.Router(); |
| 7 | 9 | ||
| 8 | /** | 10 | /** |
| 9 | * @type {Map<string, object>} Cache for classification results | 11 | * @type {Map<string, object>} Cache for classification results |
| @@ -12,8 +14,7 @@ const cacheObject = new Map(); | |||
| 12 | 14 | ||
| 13 | router.post('/labels', jsonParser, async (req, res) => { | 15 | router.post('/labels', jsonParser, async (req, res) => { |
| 14 | try { | 16 | try { |
| 15 | const module = await import('../transformers.mjs'); | 17 | const pipe = await getPipeline(TASK); |
| 16 | const pipe = await module.default.getPipeline(TASK); | ||
| 17 | const result = Object.keys(pipe.model.config.label2id); | 18 | const result = Object.keys(pipe.model.config.label2id); |
| 18 | return res.json({ labels: result }); | 19 | return res.json({ labels: result }); |
| 19 | } catch (error) { | 20 | } catch (error) { |
| @@ -35,8 +36,7 @@ router.post('/', jsonParser, async (req, res) => { | |||
| 35 | if (cacheObject.has(text)) { | 36 | if (cacheObject.has(text)) { |
| 36 | return cacheObject.get(text); | 37 | return cacheObject.get(text); |
| 37 | } else { | 38 | } else { |
| 38 | const module = await import('../transformers.mjs'); | 39 | const pipe = await getPipeline(TASK); |
| 39 | const pipe = await module.default.getPipeline(TASK); | ||
| 40 | const result = await pipe(text, { topk: 5 }); | 40 | const result = await pipe(text, { topk: 5 }); |
| 41 | result.sort((a, b) => b.score - a.score); | 41 | result.sort((a, b) => b.score - a.score); |
| 42 | cacheObject.set(text, result); | 42 | cacheObject.set(text, result); |
| @@ -54,5 +54,3 @@ router.post('/', jsonParser, async (req, res) => { | |||
| 54 | return res.sendStatus(500); | 54 | return res.sendStatus(500); |
| 55 | } | 55 | } |
| 56 | }); | 56 | }); |
| 57 | |||
| 58 | module.exports = { router }; | ||
| @@ -1,16 +1,21 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | import process from 'node:process'; |
| 4 | const fetch = require('node-fetch').default; | 4 | import { Buffer } from 'node:buffer'; |
| 5 | const sanitize = require('sanitize-filename'); | 5 | |
| 6 | const { getConfigValue, color } = require('../util'); | 6 | import express from 'express'; |
| 7 | const { jsonParser } = require('../express-common'); | 7 | import fetch from 'node-fetch'; |
| 8 | const writeFileAtomicSync = require('write-file-atomic').sync; | 8 | import sanitize from 'sanitize-filename'; |
| 9 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 10 | |||
| 11 | import { getConfigValue, color } from '../util.js'; | ||
| 12 | import { jsonParser } from '../express-common.js'; | ||
| 13 | import { write } from '../character-card-parser.js'; | ||
| 14 | |||
| 9 | const contentDirectory = path.join(process.cwd(), 'default/content'); | 15 | const contentDirectory = path.join(process.cwd(), 'default/content'); |
| 10 | const scaffoldDirectory = path.join(process.cwd(), 'default/scaffold'); | 16 | const scaffoldDirectory = path.join(process.cwd(), 'default/scaffold'); |
| 11 | const contentIndexPath = path.join(contentDirectory, 'index.json'); | 17 | const contentIndexPath = path.join(contentDirectory, 'index.json'); |
| 12 | const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json'); | 18 | const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json'); |
| 13 | const characterCardParser = require('../character-card-parser.js'); | ||
| 14 | 19 | ||
| 15 | const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []); | 20 | const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []); |
| 16 | 21 | ||
| @@ -26,7 +31,7 @@ const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDo | |||
| 26 | * @typedef {string} ContentType | 31 | * @typedef {string} ContentType |
| 27 | * @enum {string} | 32 | * @enum {string} |
| 28 | */ | 33 | */ |
| 29 | const CONTENT_TYPES = { | 34 | export const CONTENT_TYPES = { |
| 30 | SETTINGS: 'settings', | 35 | SETTINGS: 'settings', |
| 31 | CHARACTER: 'character', | 36 | CHARACTER: 'character', |
| 32 | SPRITES: 'sprites', | 37 | SPRITES: 'sprites', |
| @@ -48,10 +53,10 @@ const CONTENT_TYPES = { | |||
| 48 | 53 | ||
| 49 | /** | 54 | /** |
| 50 | * Gets the default presets from the content directory. | 55 | * Gets the default presets from the content directory. |
| 51 | * @param {import('../users').UserDirectoryList} directories User directories | 56 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 52 | * @returns {object[]} Array of default presets | 57 | * @returns {object[]} Array of default presets |
| 53 | */ | 58 | */ |
| 54 | function getDefaultPresets(directories) { | 59 | export function getDefaultPresets(directories) { |
| 55 | try { | 60 | try { |
| 56 | const contentIndex = getContentIndex(); | 61 | const contentIndex = getContentIndex(); |
| 57 | const presets = []; | 62 | const presets = []; |
| @@ -76,7 +81,7 @@ function getDefaultPresets(directories) { | |||
| 76 | * @param {string} filename Name of the file to get | 81 | * @param {string} filename Name of the file to get |
| 77 | * @returns {object | null} JSON object or null if the file doesn't exist | 82 | * @returns {object | null} JSON object or null if the file doesn't exist |
| 78 | */ | 83 | */ |
| 79 | function getDefaultPresetFile(filename) { | 84 | export function getDefaultPresetFile(filename) { |
| 80 | try { | 85 | try { |
| 81 | const contentPath = path.join(contentDirectory, filename); | 86 | const contentPath = path.join(contentDirectory, filename); |
| 82 | 87 | ||
| @@ -95,7 +100,7 @@ function getDefaultPresetFile(filename) { | |||
| 95 | /** | 100 | /** |
| 96 | * Seeds content for a user. | 101 | * Seeds content for a user. |
| 97 | * @param {ContentItem[]} contentIndex Content index | 102 | * @param {ContentItem[]} contentIndex Content index |
| 98 | * @param {import('../users').UserDirectoryList} directories User directories | 103 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 99 | * @param {string[]} forceCategories List of categories to force check (even if content check is skipped) | 104 | * @param {string[]} forceCategories List of categories to force check (even if content check is skipped) |
| 100 | * @returns {Promise<boolean>} Whether any content was added | 105 | * @returns {Promise<boolean>} Whether any content was added |
| 101 | */ | 106 | */ |
| @@ -154,11 +159,11 @@ async function seedContentForUser(contentIndex, directories, forceCategories) { | |||
| 154 | 159 | ||
| 155 | /** | 160 | /** |
| 156 | * Checks for new content and seeds it for all users. | 161 | * Checks for new content and seeds it for all users. |
| 157 | * @param {import('../users').UserDirectoryList[]} directoriesList List of user directories | 162 | * @param {import('../users.js').UserDirectoryList[]} directoriesList List of user directories |
| 158 | * @param {string[]} forceCategories List of categories to force check (even if content check is skipped) | 163 | * @param {string[]} forceCategories List of categories to force check (even if content check is skipped) |
| 159 | * @returns {Promise<void>} | 164 | * @returns {Promise<void>} |
| 160 | */ | 165 | */ |
| 161 | async function checkForNewContent(directoriesList, forceCategories = []) { | 166 | export async function checkForNewContent(directoriesList, forceCategories = []) { |
| 162 | try { | 167 | try { |
| 163 | const contentCheckSkip = getConfigValue('skipContentCheck', false); | 168 | const contentCheckSkip = getConfigValue('skipContentCheck', false); |
| 164 | if (contentCheckSkip && forceCategories?.length === 0) { | 169 | if (contentCheckSkip && forceCategories?.length === 0) { |
| @@ -224,7 +229,7 @@ function getContentIndex() { | |||
| 224 | * @param {'json'|'string'|'raw'} format Format of content | 229 | * @param {'json'|'string'|'raw'} format Format of content |
| 225 | * @returns {string[]|Buffer[]} Array of content | 230 | * @returns {string[]|Buffer[]} Array of content |
| 226 | */ | 231 | */ |
| 227 | function getContentOfType(type, format) { | 232 | export function getContentOfType(type, format) { |
| 228 | const contentIndex = getContentIndex(); | 233 | const contentIndex = getContentIndex(); |
| 229 | const indexItems = contentIndex.filter((item) => item.type === type && item.folder); | 234 | const indexItems = contentIndex.filter((item) => item.type === type && item.folder); |
| 230 | const files = []; | 235 | const files = []; |
| @@ -256,7 +261,7 @@ function getContentOfType(type, format) { | |||
| 256 | /** | 261 | /** |
| 257 | * Gets the target directory for the specified asset type. | 262 | * Gets the target directory for the specified asset type. |
| 258 | * @param {ContentType} type Asset type | 263 | * @param {ContentType} type Asset type |
| 259 | * @param {import('../users').UserDirectoryList} directories User directories | 264 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 260 | * @returns {string | null} Target directory | 265 | * @returns {string | null} Target directory |
| 261 | */ | 266 | */ |
| 262 | function getTargetByType(type, directories) { | 267 | function getTargetByType(type, directories) { |
| @@ -375,6 +380,7 @@ async function downloadPygmalionCharacter(id) { | |||
| 375 | throw new Error('Failed to download character'); | 380 | throw new Error('Failed to download character'); |
| 376 | } | 381 | } |
| 377 | 382 | ||
| 383 | /** @type {any} */ | ||
| 378 | const jsonData = await result.json(); | 384 | const jsonData = await result.json(); |
| 379 | const characterData = jsonData?.character; | 385 | const characterData = jsonData?.character; |
| 380 | 386 | ||
| @@ -394,7 +400,7 @@ async function downloadPygmalionCharacter(id) { | |||
| 394 | const avatarResult = await fetch(avatarUrl); | 400 | const avatarResult = await fetch(avatarUrl); |
| 395 | const avatarBuffer = await avatarResult.buffer(); | 401 | const avatarBuffer = await avatarResult.buffer(); |
| 396 | 402 | ||
| 397 | const cardBuffer = characterCardParser.write(avatarBuffer, JSON.stringify(characterData)); | 403 | const cardBuffer = write(avatarBuffer, JSON.stringify(characterData)); |
| 398 | 404 | ||
| 399 | return { | 405 | return { |
| 400 | buffer: cardBuffer, | 406 | buffer: cardBuffer, |
| @@ -467,6 +473,7 @@ async function downloadJannyCharacter(uuid) { | |||
| 467 | }); | 473 | }); |
| 468 | 474 | ||
| 469 | if (result.ok) { | 475 | if (result.ok) { |
| 476 | /** @type {any} */ | ||
| 470 | const downloadResult = await result.json(); | 477 | const downloadResult = await result.json(); |
| 471 | if (downloadResult.status === 'ok') { | 478 | if (downloadResult.status === 'ok') { |
| 472 | const imageResult = await fetch(downloadResult.downloadUrl); | 479 | const imageResult = await fetch(downloadResult.downloadUrl); |
| @@ -618,7 +625,7 @@ function isHostWhitelisted(host) { | |||
| 618 | return WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES.includes(host); | 625 | return WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES.includes(host); |
| 619 | } | 626 | } |
| 620 | 627 | ||
| 621 | const router = express.Router(); | 628 | export const router = express.Router(); |
| 622 | 629 | ||
| 623 | router.post('/importURL', jsonParser, async (request, response) => { | 630 | router.post('/importURL', jsonParser, async (request, response) => { |
| 624 | if (!request.body.url) { | 631 | if (!request.body.url) { |
| @@ -753,12 +760,3 @@ router.post('/importUUID', jsonParser, async (request, response) => { | |||
| 753 | return response.sendStatus(500); | 760 | return response.sendStatus(500); |
| 754 | } | 761 | } |
| 755 | }); | 762 | }); |
| 756 | |||
| 757 | module.exports = { | ||
| 758 | CONTENT_TYPES, | ||
| 759 | checkForNewContent, | ||
| 760 | getDefaultPresets, | ||
| 761 | getDefaultPresetFile, | ||
| 762 | getContentOfType, | ||
| 763 | router, | ||
| 764 | }; | ||
| @@ -1,10 +1,12 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fs = require('fs'); | 2 | import fs from 'node:fs'; |
| 3 | const express = require('express'); | 3 | |
| 4 | const { default: simpleGit } = require('simple-git'); | 4 | import express from 'express'; |
| 5 | const sanitize = require('sanitize-filename'); | 5 | import sanitize from 'sanitize-filename'; |
| 6 | const { PUBLIC_DIRECTORIES } = require('../constants'); | 6 | import { default as simpleGit } from 'simple-git'; |
| 7 | const { jsonParser } = require('../express-common'); | 7 | |
| 8 | import { PUBLIC_DIRECTORIES } from '../constants.js'; | ||
| 9 | import { jsonParser } from '../express-common.js'; | ||
| 8 | 10 | ||
| 9 | /** | 11 | /** |
| 10 | * This function extracts the extension information from the manifest file. | 12 | * This function extracts the extension information from the manifest file. |
| @@ -47,7 +49,7 @@ async function checkIfRepoIsUpToDate(extensionPath) { | |||
| 47 | }; | 49 | }; |
| 48 | } | 50 | } |
| 49 | 51 | ||
| 50 | const router = express.Router(); | 52 | export const router = express.Router(); |
| 51 | 53 | ||
| 52 | /** | 54 | /** |
| 53 | * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest, | 55 | * HTTP POST handler function to clone a git repository from a provided URL, read the extension manifest, |
| @@ -240,5 +242,3 @@ router.get('/discover', jsonParser, function (request, response) { | |||
| 240 | 242 | ||
| 241 | return response.send(extensions); | 243 | return response.send(extensions); |
| 242 | }); | 244 | }); |
| 243 | |||
| 244 | module.exports = { router }; | ||
| @@ -1,12 +1,15 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fs = require('fs'); | 2 | import fs from 'node:fs'; |
| 3 | const writeFileSyncAtomic = require('write-file-atomic').sync; | 3 | |
| 4 | const express = require('express'); | 4 | import express from 'express'; |
| 5 | const sanitize = require('sanitize-filename'); | 5 | import sanitize from 'sanitize-filename'; |
| 6 | const router = express.Router(); | 6 | import { sync as writeFileSyncAtomic } from 'write-file-atomic'; |
| 7 | const { validateAssetFileName } = require('./assets'); | 7 | |
| 8 | const { jsonParser } = require('../express-common'); | 8 | import { validateAssetFileName } from './assets.js'; |
| 9 | const { clientRelativePath } = require('../util'); | 9 | import { jsonParser } from '../express-common.js'; |
| 10 | import { clientRelativePath } from '../util.js'; | ||
| 11 | |||
| 12 | export const router = express.Router(); | ||
| 10 | 13 | ||
| 11 | router.post('/sanitize-filename', jsonParser, async (request, response) => { | 14 | router.post('/sanitize-filename', jsonParser, async (request, response) => { |
| 12 | try { | 15 | try { |
| @@ -97,5 +100,3 @@ router.post('/verify', jsonParser, async (request, response) => { | |||
| 97 | return response.sendStatus(500); | 100 | return response.sendStatus(500); |
| 98 | } | 101 | } |
| 99 | }); | 102 | }); |
| 100 | |||
| 101 | module.exports = { router }; | ||
| @@ -1,12 +1,15 @@ | |||
| 1 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 1 | import { Buffer } from 'node:buffer'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import fetch from 'node-fetch'; |
| 3 | const express = require('express'); | 3 | import express from 'express'; |
| 4 | const { jsonParser } = require('../express-common'); | 4 | import { speak, languages } from 'google-translate-api-x'; |
| 5 | const { GEMINI_SAFETY } = require('../constants'); | 5 | |
| 6 | import { readSecret, SECRET_KEYS } from './secrets.js'; | ||
| 7 | import { jsonParser } from '../express-common.js'; | ||
| 8 | import { GEMINI_SAFETY } from '../constants.js'; | ||
| 6 | 9 | ||
| 7 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; | 10 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; |
| 8 | 11 | ||
| 9 | const router = express.Router(); | 12 | export const router = express.Router(); |
| 10 | 13 | ||
| 11 | router.post('/caption-image', jsonParser, async (request, response) => { | 14 | router.post('/caption-image', jsonParser, async (request, response) => { |
| 12 | try { | 15 | try { |
| @@ -39,7 +42,6 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 39 | headers: { | 42 | headers: { |
| 40 | 'Content-Type': 'application/json', | 43 | 'Content-Type': 'application/json', |
| 41 | }, | 44 | }, |
| 42 | timeout: 0, | ||
| 43 | }); | 45 | }); |
| 44 | 46 | ||
| 45 | if (!result.ok) { | 47 | if (!result.ok) { |
| @@ -48,6 +50,7 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 48 | return response.status(result.status).send({ error: true }); | 50 | return response.status(result.status).send({ error: true }); |
| 49 | } | 51 | } |
| 50 | 52 | ||
| 53 | /** @type {any} */ | ||
| 51 | const data = await result.json(); | 54 | const data = await result.json(); |
| 52 | console.log('Multimodal captioning response', data); | 55 | console.log('Multimodal captioning response', data); |
| 53 | 56 | ||
| @@ -68,4 +71,24 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 68 | } | 71 | } |
| 69 | }); | 72 | }); |
| 70 | 73 | ||
| 71 | module.exports = { router }; | 74 | router.post('/list-voices', (_, response) => { |
| 75 | return response.json(languages); | ||
| 76 | }); | ||
| 77 | |||
| 78 | router.post('/generate-voice', jsonParser, async (request, response) => { | ||
| 79 | try { | ||
| 80 | const text = request.body.text; | ||
| 81 | const voice = request.body.voice ?? 'en'; | ||
| 82 | |||
| 83 | const result = await speak(text, { to: voice, forceBatch: false }); | ||
| 84 | const buffer = Array.isArray(result) | ||
| 85 | ? Buffer.concat(result.map(x => new Uint8Array(Buffer.from(x.toString(), 'base64')))) | ||
| 86 | : Buffer.from(result.toString(), 'base64'); | ||
| 87 | |||
| 88 | response.setHeader('Content-Type', 'audio/mpeg'); | ||
| 89 | return response.send(buffer); | ||
| 90 | } catch (error) { | ||
| 91 | console.error('Google Translate TTS generation failed', error); | ||
| 92 | response.status(500).send('Internal server error'); | ||
| 93 | } | ||
| 94 | }); | ||
| @@ -1,13 +1,14 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | ||
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | ||
| 6 | 3 | ||
| 7 | const { jsonParser } = require('../express-common'); | 4 | import express from 'express'; |
| 8 | const { humanizedISO8601DateTime } = require('../util'); | 5 | import sanitize from 'sanitize-filename'; |
| 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 9 | 7 | ||
| 10 | const router = express.Router(); | 8 | import { jsonParser } from '../express-common.js'; |
| 9 | import { humanizedISO8601DateTime } from '../util.js'; | ||
| 10 | |||
| 11 | export const router = express.Router(); | ||
| 11 | 12 | ||
| 12 | router.post('/all', jsonParser, (request, response) => { | 13 | router.post('/all', jsonParser, (request, response) => { |
| 13 | const groups = []; | 14 | const groups = []; |
| @@ -131,5 +132,3 @@ router.post('/delete', jsonParser, async (request, response) => { | |||
| 131 | 132 | ||
| 132 | return response.send({ ok: true }); | 133 | return response.send({ ok: true }); |
| 133 | }); | 134 | }); |
| 134 | |||
| 135 | module.exports = { router }; | ||
| @@ -1,14 +1,14 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const express = require('express'); | 2 | import express from 'express'; |
| 3 | const { AIHorde, ModelGenerationInputStableSamplers, ModelInterrogationFormTypes, HordeAsyncRequestStates } = require('@zeldafan0225/ai_horde'); | 3 | import { AIHorde, ModelGenerationInputStableSamplers, ModelInterrogationFormTypes, HordeAsyncRequestStates } from '@zeldafan0225/ai_horde'; |
| 4 | const { getVersion, delay, Cache } = require('../util'); | 4 | import { getVersion, delay, Cache } from '../util.js'; |
| 5 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 5 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| 6 | const { jsonParser } = require('../express-common'); | 6 | import { jsonParser } from '../express-common.js'; |
| 7 | 7 | ||
| 8 | const ANONYMOUS_KEY = '0000000000'; | 8 | const ANONYMOUS_KEY = '0000000000'; |
| 9 | const HORDE_TEXT_MODEL_METADATA_URL = 'https://raw.githubusercontent.com/db0/AI-Horde-text-model-reference/main/db.json'; | 9 | const HORDE_TEXT_MODEL_METADATA_URL = 'https://raw.githubusercontent.com/db0/AI-Horde-text-model-reference/main/db.json'; |
| 10 | const cache = new Cache(60 * 1000); | 10 | const cache = new Cache(60 * 1000); |
| 11 | const router = express.Router(); | 11 | export const router = express.Router(); |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | * Returns the AIHorde client agent. | 14 | * Returns the AIHorde client agent. |
| @@ -403,5 +403,3 @@ router.post('/generate-image', jsonParser, async (request, response) => { | |||
| 403 | return response.sendStatus(500); | 403 | return response.sendStatus(500); |
| 404 | } | 404 | } |
| 405 | }); | 405 | }); |
| 406 | |||
| 407 | module.exports = { router }; | ||
| @@ -1,10 +1,12 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | import { Buffer } from 'node:buffer'; |
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | 4 | ||
| 6 | const { jsonParser } = require('../express-common'); | 5 | import express from 'express'; |
| 7 | const { clientRelativePath, removeFileExtension, getImages } = require('../util'); | 6 | import sanitize from 'sanitize-filename'; |
| 7 | |||
| 8 | import { jsonParser } from '../express-common.js'; | ||
| 9 | import { clientRelativePath, removeFileExtension, getImages } from '../util.js'; | ||
| 8 | 10 | ||
| 9 | /** | 11 | /** |
| 10 | * Ensure the directory for the provided file path exists. | 12 | * Ensure the directory for the provided file path exists. |
| @@ -21,7 +23,7 @@ function ensureDirectoryExistence(filePath) { | |||
| 21 | fs.mkdirSync(dirname); | 23 | fs.mkdirSync(dirname); |
| 22 | } | 24 | } |
| 23 | 25 | ||
| 24 | const router = express.Router(); | 26 | export const router = express.Router(); |
| 25 | 27 | ||
| 26 | /** | 28 | /** |
| 27 | * Endpoint to handle image uploads. | 29 | * Endpoint to handle image uploads. |
| @@ -66,7 +68,7 @@ router.post('/upload', jsonParser, async (request, response) => { | |||
| 66 | 68 | ||
| 67 | ensureDirectoryExistence(pathToNewFile); | 69 | ensureDirectoryExistence(pathToNewFile); |
| 68 | const imageBuffer = Buffer.from(base64Data, 'base64'); | 70 | const imageBuffer = Buffer.from(base64Data, 'base64'); |
| 69 | await fs.promises.writeFile(pathToNewFile, imageBuffer); | 71 | await fs.promises.writeFile(pathToNewFile, new Uint8Array(imageBuffer)); |
| 70 | response.send({ path: clientRelativePath(request.user.directories.root, pathToNewFile) }); | 72 | response.send({ path: clientRelativePath(request.user.directories.root, pathToNewFile) }); |
| 71 | } catch (error) { | 73 | } catch (error) { |
| 72 | console.log(error); | 74 | console.log(error); |
| @@ -89,5 +91,3 @@ router.post('/list/:folder', (request, response) => { | |||
| 89 | return response.status(500).send({ error: 'Unable to retrieve files' }); | 91 | return response.status(500).send({ error: 'Unable to retrieve files' }); |
| 90 | } | 92 | } |
| 91 | }); | 93 | }); |
| 92 | |||
| 93 | module.exports = { router }; | ||
| @@ -1,11 +1,11 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const express = require('express'); | 2 | import express from 'express'; |
| 3 | const sanitize = require('sanitize-filename'); | 3 | import sanitize from 'sanitize-filename'; |
| 4 | const writeFileAtomicSync = require('write-file-atomic').sync; | 4 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 5 | 5 | ||
| 6 | const { jsonParser } = require('../express-common'); | 6 | import { jsonParser } from '../express-common.js'; |
| 7 | 7 | ||
| 8 | const router = express.Router(); | 8 | export const router = express.Router(); |
| 9 | 9 | ||
| 10 | router.post('/save', jsonParser, (request, response) => { | 10 | router.post('/save', jsonParser, (request, response) => { |
| 11 | if (!request.body || !request.body.name) { | 11 | if (!request.body || !request.body.name) { |
| @@ -17,5 +17,3 @@ router.post('/save', jsonParser, (request, response) => { | |||
| 17 | 17 | ||
| 18 | return response.sendStatus(200); | 18 | return response.sendStatus(200); |
| 19 | }); | 19 | }); |
| 20 | |||
| 21 | module.exports = { router }; | ||
| @@ -1,9 +1,12 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import util from 'node:util'; |
| 2 | const express = require('express'); | 2 | import { Buffer } from 'node:buffer'; |
| 3 | const util = require('util'); | 3 | |
| 4 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 4 | import fetch from 'node-fetch'; |
| 5 | const { readAllChunks, extractFileFromZipBuffer, forwardFetchResponse } = require('../util'); | 5 | import express from 'express'; |
| 6 | const { jsonParser } = require('../express-common'); | 6 | |
| 7 | import { readSecret, SECRET_KEYS } from './secrets.js'; | ||
| 8 | import { readAllChunks, extractFileFromZipBuffer, forwardFetchResponse } from '../util.js'; | ||
| 9 | import { jsonParser } from '../express-common.js'; | ||
| 7 | 10 | ||
| 8 | const API_NOVELAI = 'https://api.novelai.net'; | 11 | const API_NOVELAI = 'https://api.novelai.net'; |
| 9 | const TEXT_NOVELAI = 'https://text.novelai.net'; | 12 | const TEXT_NOVELAI = 'https://text.novelai.net'; |
| @@ -110,7 +113,7 @@ function getRepPenaltyWhitelist(model) { | |||
| 110 | return null; | 113 | return null; |
| 111 | } | 114 | } |
| 112 | 115 | ||
| 113 | const router = express.Router(); | 116 | export const router = express.Router(); |
| 114 | 117 | ||
| 115 | router.post('/status', jsonParser, async function (req, res) { | 118 | router.post('/status', jsonParser, async function (req, res) { |
| 116 | if (!req.body) return res.sendStatus(400); | 119 | if (!req.body) return res.sendStatus(400); |
| @@ -249,7 +252,7 @@ router.post('/generate', jsonParser, async function (req, res) { | |||
| 249 | try { | 252 | try { |
| 250 | const baseURL = (req.body.model.includes('kayra') || req.body.model.includes('erato')) ? TEXT_NOVELAI : API_NOVELAI; | 253 | const baseURL = (req.body.model.includes('kayra') || req.body.model.includes('erato')) ? TEXT_NOVELAI : API_NOVELAI; |
| 251 | const url = req.body.streaming ? `${baseURL}/ai/generate-stream` : `${baseURL}/ai/generate`; | 254 | const url = req.body.streaming ? `${baseURL}/ai/generate-stream` : `${baseURL}/ai/generate`; |
| 252 | const response = await fetch(url, { method: 'POST', timeout: 0, ...args }); | 255 | const response = await fetch(url, { method: 'POST', ...args }); |
| 253 | 256 | ||
| 254 | if (req.body.streaming) { | 257 | if (req.body.streaming) { |
| 255 | // Pipe remote SSE stream to Express response | 258 | // Pipe remote SSE stream to Express response |
| @@ -271,6 +274,7 @@ router.post('/generate', jsonParser, async function (req, res) { | |||
| 271 | return res.status(response.status).send({ error: { message } }); | 274 | return res.status(response.status).send({ error: { message } }); |
| 272 | } | 275 | } |
| 273 | 276 | ||
| 277 | /** @type {any} */ | ||
| 274 | const data = await response.json(); | 278 | const data = await response.json(); |
| 275 | console.log('NovelAI Output', data?.output); | 279 | console.log('NovelAI Output', data?.output); |
| 276 | return res.send(data); | 280 | return res.send(data); |
| @@ -413,7 +417,6 @@ router.post('/generate-voice', jsonParser, async (request, response) => { | |||
| 413 | 'Authorization': `Bearer ${token}`, | 417 | 'Authorization': `Bearer ${token}`, |
| 414 | 'Accept': 'audio/mpeg', | 418 | 'Accept': 'audio/mpeg', |
| 415 | }, | 419 | }, |
| 416 | timeout: 0, | ||
| 417 | }); | 420 | }); |
| 418 | 421 | ||
| 419 | if (!result.ok) { | 422 | if (!result.ok) { |
| @@ -423,7 +426,7 @@ router.post('/generate-voice', jsonParser, async (request, response) => { | |||
| 423 | } | 426 | } |
| 424 | 427 | ||
| 425 | const chunks = await readAllChunks(result.body); | 428 | const chunks = await readAllChunks(result.body); |
| 426 | const buffer = Buffer.concat(chunks); | 429 | const buffer = Buffer.concat(chunks.map(chunk => new Uint8Array(chunk))); |
| 427 | response.setHeader('Content-Type', 'audio/mpeg'); | 430 | response.setHeader('Content-Type', 'audio/mpeg'); |
| 428 | return response.send(buffer); | 431 | return response.send(buffer); |
| 429 | } | 432 | } |
| @@ -432,5 +435,3 @@ router.post('/generate-voice', jsonParser, async (request, response) => { | |||
| 432 | return response.sendStatus(500); | 435 | return response.sendStatus(500); |
| 433 | } | 436 | } |
| 434 | }); | 437 | }); |
| 435 | |||
| 436 | module.exports = { router }; | ||
| @@ -1,14 +1,17 @@ | |||
| 1 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 1 | import fs from 'node:fs'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import { Buffer } from 'node:buffer'; |
| 3 | const express = require('express'); | 3 | |
| 4 | const FormData = require('form-data'); | 4 | import fetch from 'node-fetch'; |
| 5 | const fs = require('fs'); | 5 | import FormData from 'form-data'; |
| 6 | const { jsonParser, urlencodedParser } = require('../express-common'); | 6 | import express from 'express'; |
| 7 | const { getConfigValue, mergeObjectWithYaml, excludeKeysByYaml, trimV1 } = require('../util'); | 7 | |
| 8 | const { setAdditionalHeaders } = require('../additional-headers'); | 8 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 9 | const { OPENROUTER_HEADERS } = require('../constants'); | 9 | import { getConfigValue, mergeObjectWithYaml, excludeKeysByYaml, trimV1 } from '../util.js'; |
| 10 | 10 | import { setAdditionalHeaders } from '../additional-headers.js'; | |
| 11 | const router = express.Router(); | 11 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| 12 | import { OPENROUTER_HEADERS } from '../constants.js'; | ||
| 13 | |||
| 14 | export const router = express.Router(); | ||
| 12 | 15 | ||
| 13 | router.post('/caption-image', jsonParser, async (request, response) => { | 16 | router.post('/caption-image', jsonParser, async (request, response) => { |
| 14 | try { | 17 | try { |
| @@ -151,7 +154,6 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 151 | ...headers, | 154 | ...headers, |
| 152 | }, | 155 | }, |
| 153 | body: JSON.stringify(body), | 156 | body: JSON.stringify(body), |
| 154 | timeout: 0, | ||
| 155 | }); | 157 | }); |
| 156 | 158 | ||
| 157 | if (!result.ok) { | 159 | if (!result.ok) { |
| @@ -160,6 +162,7 @@ router.post('/caption-image', jsonParser, async (request, response) => { | |||
| 160 | return response.status(500).send(text); | 162 | return response.status(500).send(text); |
| 161 | } | 163 | } |
| 162 | 164 | ||
| 165 | /** @type {any} */ | ||
| 163 | const data = await result.json(); | 166 | const data = await result.json(); |
| 164 | console.log('Multimodal captioning response', data); | 167 | console.log('Multimodal captioning response', data); |
| 165 | const caption = data?.choices[0]?.message?.content; | 168 | const caption = data?.choices[0]?.message?.content; |
| @@ -281,7 +284,6 @@ router.post('/generate-image', jsonParser, async (request, response) => { | |||
| 281 | Authorization: `Bearer ${key}`, | 284 | Authorization: `Bearer ${key}`, |
| 282 | }, | 285 | }, |
| 283 | body: JSON.stringify(request.body), | 286 | body: JSON.stringify(request.body), |
| 284 | timeout: 0, | ||
| 285 | }); | 287 | }); |
| 286 | 288 | ||
| 287 | if (!result.ok) { | 289 | if (!result.ok) { |
| @@ -341,5 +343,3 @@ custom.post('/generate-voice', jsonParser, async (request, response) => { | |||
| 341 | }); | 343 | }); |
| 342 | 344 | ||
| 343 | router.use('/custom', custom); | 345 | router.use('/custom', custom); |
| 344 | |||
| 345 | module.exports = { router }; | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | const express = require('express'); | 1 | import express from 'express'; |
| 2 | const { jsonParser } = require('../express-common'); | 2 | import { jsonParser } from '../express-common.js'; |
| 3 | 3 | ||
| 4 | const router = express.Router(); | 4 | export const router = express.Router(); |
| 5 | const API_OPENROUTER = 'https://openrouter.ai/api/v1'; | 5 | const API_OPENROUTER = 'https://openrouter.ai/api/v1'; |
| 6 | 6 | ||
| 7 | router.post('/models/multimodal', jsonParser, async (_req, res) => { | 7 | router.post('/models/multimodal', jsonParser, async (_req, res) => { |
| @@ -28,5 +28,3 @@ router.post('/models/multimodal', jsonParser, async (_req, res) => { | |||
| 28 | return res.sendStatus(500); | 28 | return res.sendStatus(500); |
| 29 | } | 29 | } |
| 30 | }); | 30 | }); |
| 31 | |||
| 32 | module.exports = { router }; | ||
| @@ -1,15 +1,17 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | |
| 4 | const sanitize = require('sanitize-filename'); | 4 | import express from 'express'; |
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | 5 | import sanitize from 'sanitize-filename'; |
| 6 | const { getDefaultPresetFile, getDefaultPresets } = require('./content-manager'); | 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 7 | const { jsonParser } = require('../express-common'); | 7 | |
| 8 | import { getDefaultPresetFile, getDefaultPresets } from './content-manager.js'; | ||
| 9 | import { jsonParser } from '../express-common.js'; | ||
| 8 | 10 | ||
| 9 | /** | 11 | /** |
| 10 | * Gets the folder and extension for the preset settings based on the API source ID. | 12 | * Gets the folder and extension for the preset settings based on the API source ID. |
| 11 | * @param {string} apiId API source ID | 13 | * @param {string} apiId API source ID |
| 12 | * @param {import('../users').UserDirectoryList} directories User directories | 14 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 13 | * @returns {object} Object containing the folder and extension for the preset settings | 15 | * @returns {object} Object containing the folder and extension for the preset settings |
| 14 | */ | 16 | */ |
| 15 | function getPresetSettingsByAPI(apiId, directories) { | 17 | function getPresetSettingsByAPI(apiId, directories) { |
| @@ -34,7 +36,7 @@ function getPresetSettingsByAPI(apiId, directories) { | |||
| 34 | } | 36 | } |
| 35 | } | 37 | } |
| 36 | 38 | ||
| 37 | const router = express.Router(); | 39 | export const router = express.Router(); |
| 38 | 40 | ||
| 39 | router.post('/save', jsonParser, function (request, response) { | 41 | router.post('/save', jsonParser, function (request, response) { |
| 40 | const name = sanitize(request.body.name); | 42 | const name = sanitize(request.body.name); |
| @@ -127,5 +129,3 @@ router.post('/delete-openai', jsonParser, function (request, response) { | |||
| 127 | 129 | ||
| 128 | return response.send({ error: true }); | 130 | return response.send({ error: true }); |
| 129 | }); | 131 | }); |
| 130 | |||
| 131 | module.exports = { router }; | ||
| @@ -1,12 +1,13 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | ||
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | ||
| 6 | 3 | ||
| 7 | const { jsonParser } = require('../express-common'); | 4 | import express from 'express'; |
| 5 | import sanitize from 'sanitize-filename'; | ||
| 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 8 | 7 | ||
| 9 | const router = express.Router(); | 8 | import { jsonParser } from '../express-common.js'; |
| 9 | |||
| 10 | export const router = express.Router(); | ||
| 10 | 11 | ||
| 11 | router.post('/save', jsonParser, (request, response) => { | 12 | router.post('/save', jsonParser, (request, response) => { |
| 12 | if (!request.body || !request.body.name) { | 13 | if (!request.body || !request.body.name) { |
| @@ -31,5 +32,3 @@ router.post('/delete', jsonParser, (request, response) => { | |||
| 31 | 32 | ||
| 32 | return response.sendStatus(200); | 33 | return response.sendStatus(200); |
| 33 | }); | 34 | }); |
| 34 | |||
| 35 | module.exports = { router }; | ||
| @@ -1,9 +1,11 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const express = require('express'); | 2 | import express from 'express'; |
| 3 | const { readSecret, SECRET_KEYS } = require('./secrets'); | ||
| 4 | const { jsonParser } = require('../express-common'); | ||
| 5 | 3 | ||
| 6 | const router = express.Router(); | 4 | import { decode } from 'html-entities'; |
| 5 | import { readSecret, SECRET_KEYS } from './secrets.js'; | ||
| 6 | import { jsonParser } from '../express-common.js'; | ||
| 7 | |||
| 8 | export const router = express.Router(); | ||
| 7 | 9 | ||
| 8 | // Cosplay as Chrome | 10 | // Cosplay as Chrome |
| 9 | const visitHeaders = { | 11 | const visitHeaders = { |
| @@ -29,7 +31,6 @@ const visitHeaders = { | |||
| 29 | * @returns {Promise<string>} Transcript text | 31 | * @returns {Promise<string>} Transcript text |
| 30 | */ | 32 | */ |
| 31 | async function extractTranscript(videoPageBody, lang) { | 33 | async function extractTranscript(videoPageBody, lang) { |
| 32 | const he = require('he'); | ||
| 33 | const RE_XML_TRANSCRIPT = /<text start="([^"]*)" dur="([^"]*)">([^<]*)<\/text>/g; | 34 | const RE_XML_TRANSCRIPT = /<text start="([^"]*)" dur="([^"]*)">([^<]*)<\/text>/g; |
| 34 | const splittedHTML = videoPageBody.split('"captions":'); | 35 | const splittedHTML = videoPageBody.split('"captions":'); |
| 35 | 36 | ||
| @@ -84,7 +85,7 @@ async function extractTranscript(videoPageBody, lang) { | |||
| 84 | lang: lang ?? captions.captionTracks[0].languageCode, | 85 | lang: lang ?? captions.captionTracks[0].languageCode, |
| 85 | })); | 86 | })); |
| 86 | // The text is double-encoded | 87 | // The text is double-encoded |
| 87 | const transcriptText = transcript.map((line) => he.decode(he.decode(line.text))).join(' '); | 88 | const transcriptText = transcript.map((line) => decode(decode(line.text))).join(' '); |
| 88 | return transcriptText; | 89 | return transcriptText; |
| 89 | } | 90 | } |
| 90 | 91 | ||
| @@ -263,5 +264,3 @@ router.post('/visit', jsonParser, async (request, response) => { | |||
| 263 | return response.sendStatus(500); | 264 | return response.sendStatus(500); |
| 264 | } | 265 | } |
| 265 | }); | 266 | }); |
| 266 | |||
| 267 | module.exports = { router }; | ||
| @@ -1,12 +1,13 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | |
| 4 | const { getConfigValue } = require('../util'); | 4 | import express from 'express'; |
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | 5 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 6 | const { jsonParser } = require('../express-common'); | 6 | import { getConfigValue } from '../util.js'; |
| 7 | 7 | import { jsonParser } from '../express-common.js'; | |
| 8 | const SECRETS_FILE = 'secrets.json'; | 8 | |
| 9 | const SECRET_KEYS = { | 9 | export const SECRETS_FILE = 'secrets.json'; |
| 10 | export const SECRET_KEYS = { | ||
| 10 | HORDE: 'api_key_horde', | 11 | HORDE: 'api_key_horde', |
| 11 | MANCER: 'api_key_mancer', | 12 | MANCER: 'api_key_mancer', |
| 12 | VLLM: 'api_key_vllm', | 13 | VLLM: 'api_key_vllm', |
| @@ -58,11 +59,11 @@ const EXPORTABLE_KEYS = [ | |||
| 58 | 59 | ||
| 59 | /** | 60 | /** |
| 60 | * Writes a secret to the secrets file | 61 | * Writes a secret to the secrets file |
| 61 | * @param {import('../users').UserDirectoryList} directories User directories | 62 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 62 | * @param {string} key Secret key | 63 | * @param {string} key Secret key |
| 63 | * @param {string} value Secret value | 64 | * @param {string} value Secret value |
| 64 | */ | 65 | */ |
| 65 | function writeSecret(directories, key, value) { | 66 | export function writeSecret(directories, key, value) { |
| 66 | const filePath = path.join(directories.root, SECRETS_FILE); | 67 | const filePath = path.join(directories.root, SECRETS_FILE); |
| 67 | 68 | ||
| 68 | if (!fs.existsSync(filePath)) { | 69 | if (!fs.existsSync(filePath)) { |
| @@ -78,11 +79,11 @@ function writeSecret(directories, key, value) { | |||
| 78 | 79 | ||
| 79 | /** | 80 | /** |
| 80 | * Deletes a secret from the secrets file | 81 | * Deletes a secret from the secrets file |
| 81 | * @param {import('../users').UserDirectoryList} directories User directories | 82 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 82 | * @param {string} key Secret key | 83 | * @param {string} key Secret key |
| 83 | * @returns | 84 | * @returns |
| 84 | */ | 85 | */ |
| 85 | function deleteSecret(directories, key) { | 86 | export function deleteSecret(directories, key) { |
| 86 | const filePath = path.join(directories.root, SECRETS_FILE); | 87 | const filePath = path.join(directories.root, SECRETS_FILE); |
| 87 | 88 | ||
| 88 | if (!fs.existsSync(filePath)) { | 89 | if (!fs.existsSync(filePath)) { |
| @@ -97,11 +98,11 @@ function deleteSecret(directories, key) { | |||
| 97 | 98 | ||
| 98 | /** | 99 | /** |
| 99 | * Reads a secret from the secrets file | 100 | * Reads a secret from the secrets file |
| 100 | * @param {import('../users').UserDirectoryList} directories User directories | 101 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 101 | * @param {string} key Secret key | 102 | * @param {string} key Secret key |
| 102 | * @returns {string} Secret value | 103 | * @returns {string} Secret value |
| 103 | */ | 104 | */ |
| 104 | function readSecret(directories, key) { | 105 | export function readSecret(directories, key) { |
| 105 | const filePath = path.join(directories.root, SECRETS_FILE); | 106 | const filePath = path.join(directories.root, SECRETS_FILE); |
| 106 | 107 | ||
| 107 | if (!fs.existsSync(filePath)) { | 108 | if (!fs.existsSync(filePath)) { |
| @@ -115,10 +116,10 @@ function readSecret(directories, key) { | |||
| 115 | 116 | ||
| 116 | /** | 117 | /** |
| 117 | * Reads the secret state from the secrets file | 118 | * Reads the secret state from the secrets file |
| 118 | * @param {import('../users').UserDirectoryList} directories User directories | 119 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 119 | * @returns {object} Secret state | 120 | * @returns {object} Secret state |
| 120 | */ | 121 | */ |
| 121 | function readSecretState(directories) { | 122 | export function readSecretState(directories) { |
| 122 | const filePath = path.join(directories.root, SECRETS_FILE); | 123 | const filePath = path.join(directories.root, SECRETS_FILE); |
| 123 | 124 | ||
| 124 | if (!fs.existsSync(filePath)) { | 125 | if (!fs.existsSync(filePath)) { |
| @@ -138,10 +139,10 @@ function readSecretState(directories) { | |||
| 138 | 139 | ||
| 139 | /** | 140 | /** |
| 140 | * Reads all secrets from the secrets file | 141 | * Reads all secrets from the secrets file |
| 141 | * @param {import('../users').UserDirectoryList} directories User directories | 142 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 142 | * @returns {Record<string, string> | undefined} Secrets | 143 | * @returns {Record<string, string> | undefined} Secrets |
| 143 | */ | 144 | */ |
| 144 | function getAllSecrets(directories) { | 145 | export function getAllSecrets(directories) { |
| 145 | const filePath = path.join(directories.root, SECRETS_FILE); | 146 | const filePath = path.join(directories.root, SECRETS_FILE); |
| 146 | 147 | ||
| 147 | if (!fs.existsSync(filePath)) { | 148 | if (!fs.existsSync(filePath)) { |
| @@ -154,7 +155,7 @@ function getAllSecrets(directories) { | |||
| 154 | return secrets; | 155 | return secrets; |
| 155 | } | 156 | } |
| 156 | 157 | ||
| 157 | const router = express.Router(); | 158 | export const router = express.Router(); |
| 158 | 159 | ||
| 159 | router.post('/write', jsonParser, (request, response) => { | 160 | router.post('/write', jsonParser, (request, response) => { |
| 160 | const key = request.body.key; | 161 | const key = request.body.key; |
| @@ -218,13 +219,3 @@ router.post('/find', jsonParser, (request, response) => { | |||
| 218 | return response.sendStatus(500); | 219 | return response.sendStatus(500); |
| 219 | } | 220 | } |
| 220 | }); | 221 | }); |
| 221 | |||
| 222 | module.exports = { | ||
| 223 | writeSecret, | ||
| 224 | readSecret, | ||
| 225 | deleteSecret, | ||
| 226 | readSecretState, | ||
| 227 | getAllSecrets, | ||
| 228 | SECRET_KEYS, | ||
| 229 | router, | ||
| 230 | }; | ||
| @@ -1,12 +1,14 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | |
| 4 | const _ = require('lodash'); | 4 | import express from 'express'; |
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | 5 | import _ from 'lodash'; |
| 6 | const { SETTINGS_FILE } = require('../constants'); | 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 7 | const { getConfigValue, generateTimestamp, removeOldBackups } = require('../util'); | 7 | |
| 8 | const { jsonParser } = require('../express-common'); | 8 | import { SETTINGS_FILE } from '../constants.js'; |
| 9 | const { getAllUserHandles, getUserDirectories } = require('../users'); | 9 | import { getConfigValue, generateTimestamp, removeOldBackups } from '../util.js'; |
| 10 | import { jsonParser } from '../express-common.js'; | ||
| 11 | import { getAllUserHandles, getUserDirectories } from '../users.js'; | ||
| 10 | 12 | ||
| 11 | const ENABLE_EXTENSIONS = getConfigValue('enableExtensions', true); | 13 | const ENABLE_EXTENSIONS = getConfigValue('enableExtensions', true); |
| 12 | const ENABLE_EXTENSIONS_AUTO_UPDATE = getConfigValue('enableExtensionsAutoUpdate', true); | 14 | const ENABLE_EXTENSIONS_AUTO_UPDATE = getConfigValue('enableExtensionsAutoUpdate', true); |
| @@ -190,7 +192,7 @@ function getLatestBackup(handle) { | |||
| 190 | return path.join(userDirectories.backups, latestBackup); | 192 | return path.join(userDirectories.backups, latestBackup); |
| 191 | } | 193 | } |
| 192 | 194 | ||
| 193 | const router = express.Router(); | 195 | export const router = express.Router(); |
| 194 | 196 | ||
| 195 | router.post('/save', jsonParser, function (request, response) { | 197 | router.post('/save', jsonParser, function (request, response) { |
| 196 | try { | 198 | try { |
| @@ -357,8 +359,6 @@ router.post('/restore-snapshot', jsonParser, async (request, response) => { | |||
| 357 | /** | 359 | /** |
| 358 | * Initializes the settings endpoint | 360 | * Initializes the settings endpoint |
| 359 | */ | 361 | */ |
| 360 | async function init() { | 362 | export async function init() { |
| 361 | await backupSettings(); | 363 | await backupSettings(); |
| 362 | } | 364 | } |
| 363 | |||
| 364 | module.exports = { router, init }; | ||
| @@ -1,7 +1,10 @@ | |||
| 1 | const express = require('express'); | 1 | import { Buffer } from 'node:buffer'; |
| 2 | const { jsonParser } = require('../express-common'); | 2 | import express from 'express'; |
| 3 | import wavefile from 'wavefile'; | ||
| 4 | import { jsonParser } from '../express-common.js'; | ||
| 5 | import { getPipeline } from '../transformers.mjs'; | ||
| 3 | 6 | ||
| 4 | const router = express.Router(); | 7 | export const router = express.Router(); |
| 5 | 8 | ||
| 6 | /** | 9 | /** |
| 7 | * Gets the audio data from a base64-encoded audio file. | 10 | * Gets the audio data from a base64-encoded audio file. |
| @@ -9,7 +12,6 @@ const router = express.Router(); | |||
| 9 | * @returns {Float64Array} Audio data | 12 | * @returns {Float64Array} Audio data |
| 10 | */ | 13 | */ |
| 11 | function getWaveFile(audio) { | 14 | function getWaveFile(audio) { |
| 12 | const wavefile = require('wavefile'); | ||
| 13 | const wav = new wavefile.WaveFile(); | 15 | const wav = new wavefile.WaveFile(); |
| 14 | wav.fromDataURI(audio); | 16 | wav.fromDataURI(audio); |
| 15 | wav.toBitDepth('32f'); | 17 | wav.toBitDepth('32f'); |
| @@ -36,8 +38,7 @@ router.post('/recognize', jsonParser, async (req, res) => { | |||
| 36 | try { | 38 | try { |
| 37 | const TASK = 'automatic-speech-recognition'; | 39 | const TASK = 'automatic-speech-recognition'; |
| 38 | const { model, audio, lang } = req.body; | 40 | const { model, audio, lang } = req.body; |
| 39 | const module = await import('../transformers.mjs'); | 41 | const pipe = await getPipeline(TASK, model); |
| 40 | const pipe = await module.default.getPipeline(TASK, model); | ||
| 41 | const wav = getWaveFile(audio); | 42 | const wav = getWaveFile(audio); |
| 42 | const start = performance.now(); | 43 | const start = performance.now(); |
| 43 | const result = await pipe(wav, { language: lang || null, task: 'transcribe' }); | 44 | const result = await pipe(wav, { language: lang || null, task: 'transcribe' }); |
| @@ -54,11 +55,9 @@ router.post('/recognize', jsonParser, async (req, res) => { | |||
| 54 | 55 | ||
| 55 | router.post('/synthesize', jsonParser, async (req, res) => { | 56 | router.post('/synthesize', jsonParser, async (req, res) => { |
| 56 | try { | 57 | try { |
| 57 | const wavefile = require('wavefile'); | ||
| 58 | const TASK = 'text-to-speech'; | 58 | const TASK = 'text-to-speech'; |
| 59 | const { text, model, speaker } = req.body; | 59 | const { text, model, speaker } = req.body; |
| 60 | const module = await import('../transformers.mjs'); | 60 | const pipe = await getPipeline(TASK, model); |
| 61 | const pipe = await module.default.getPipeline(TASK, model); | ||
| 62 | const speaker_embeddings = speaker | 61 | const speaker_embeddings = speaker |
| 63 | ? new Float32Array(new Uint8Array(Buffer.from(speaker.startsWith('data:') ? speaker.split(',')[1] : speaker, 'base64')).buffer) | 62 | ? new Float32Array(new Uint8Array(Buffer.from(speaker.startsWith('data:') ? speaker.split(',')[1] : speaker, 'base64')).buffer) |
| 64 | : null; | 63 | : null; |
| @@ -78,5 +77,3 @@ router.post('/synthesize', jsonParser, async (req, res) => { | |||
| 78 | return res.sendStatus(500); | 77 | return res.sendStatus(500); |
| 79 | } | 78 | } |
| 80 | }); | 79 | }); |
| 81 | |||
| 82 | module.exports = { router }; | ||
| @@ -1,16 +1,17 @@ | |||
| 1 | import fs from 'node:fs'; | ||
| 2 | import path from 'node:path'; | ||
| 1 | 3 | ||
| 2 | const fs = require('fs'); | 4 | import express from 'express'; |
| 3 | const path = require('path'); | 5 | import mime from 'mime-types'; |
| 4 | const express = require('express'); | 6 | import sanitize from 'sanitize-filename'; |
| 5 | const mime = require('mime-types'); | 7 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 6 | const sanitize = require('sanitize-filename'); | 8 | |
| 7 | const writeFileAtomicSync = require('write-file-atomic').sync; | 9 | import { getImageBuffers } from '../util.js'; |
| 8 | const { getImageBuffers } = require('../util'); | 10 | import { jsonParser, urlencodedParser } from '../express-common.js'; |
| 9 | const { jsonParser, urlencodedParser } = require('../express-common'); | ||
| 10 | 11 | ||
| 11 | /** | 12 | /** |
| 12 | * Gets the path to the sprites folder for the provided character name | 13 | * Gets the path to the sprites folder for the provided character name |
| 13 | * @param {import('../users').UserDirectoryList} directories - User directories | 14 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 14 | * @param {string} name - The name of the character | 15 | * @param {string} name - The name of the character |
| 15 | * @param {boolean} isSubfolder - Whether the name contains a subfolder | 16 | * @param {boolean} isSubfolder - Whether the name contains a subfolder |
| 16 | * @returns {string | null} The path to the sprites folder. Null if the name is invalid. | 17 | * @returns {string | null} The path to the sprites folder. Null if the name is invalid. |
| @@ -41,11 +42,11 @@ function getSpritesPath(directories, name, isSubfolder) { | |||
| 41 | * Imports base64 encoded sprites from RisuAI character data. | 42 | * Imports base64 encoded sprites from RisuAI character data. |
| 42 | * The sprites are saved in the character's sprites folder. | 43 | * The sprites are saved in the character's sprites folder. |
| 43 | * The additionalAssets and emotions are removed from the data. | 44 | * The additionalAssets and emotions are removed from the data. |
| 44 | * @param {import('../users').UserDirectoryList} directories User directories | 45 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 45 | * @param {object} data RisuAI character data | 46 | * @param {object} data RisuAI character data |
| 46 | * @returns {void} | 47 | * @returns {void} |
| 47 | */ | 48 | */ |
| 48 | function importRisuSprites(directories, data) { | 49 | export function importRisuSprites(directories, data) { |
| 49 | try { | 50 | try { |
| 50 | const name = data?.data?.name; | 51 | const name = data?.data?.name; |
| 51 | const risuData = data?.data?.extensions?.risuai; | 52 | const risuData = data?.data?.extensions?.risuai; |
| @@ -106,7 +107,7 @@ function importRisuSprites(directories, data) { | |||
| 106 | } | 107 | } |
| 107 | } | 108 | } |
| 108 | 109 | ||
| 109 | const router = express.Router(); | 110 | export const router = express.Router(); |
| 110 | 111 | ||
| 111 | router.get('/get', jsonParser, function (request, response) { | 112 | router.get('/get', jsonParser, function (request, response) { |
| 112 | const name = String(request.query.name); | 113 | const name = String(request.query.name); |
| @@ -259,8 +260,3 @@ router.post('/upload', urlencodedParser, async (request, response) => { | |||
| 259 | return response.sendStatus(500); | 260 | return response.sendStatus(500); |
| 260 | } | 261 | } |
| 261 | }); | 262 | }); |
| 262 | |||
| 263 | module.exports = { | ||
| 264 | router, | ||
| 265 | importRisuSprites, | ||
| 266 | }; | ||
| @@ -1,13 +1,15 @@ | |||
| 1 | const express = require('express'); | 1 | import fs from 'node:fs'; |
| 2 | const fetch = require('node-fetch').default; | 2 | import path from 'node:path'; |
| 3 | const sanitize = require('sanitize-filename'); | 3 | |
| 4 | const { getBasicAuthHeader, delay, getHexString } = require('../util.js'); | 4 | import express from 'express'; |
| 5 | const fs = require('fs'); | 5 | import fetch from 'node-fetch'; |
| 6 | const path = require('path'); | 6 | import sanitize from 'sanitize-filename'; |
| 7 | const writeFileAtomicSync = require('write-file-atomic').sync; | 7 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 8 | const { jsonParser } = require('../express-common'); | 8 | import FormData from 'form-data'; |
| 9 | const { readSecret, SECRET_KEYS } = require('./secrets.js'); | 9 | |
| 10 | const FormData = require('form-data'); | 10 | import { getBasicAuthHeader, delay } from '../util.js'; |
| 11 | import { jsonParser } from '../express-common.js'; | ||
| 12 | import { readSecret, SECRET_KEYS } from './secrets.js'; | ||
| 11 | 13 | ||
| 12 | /** | 14 | /** |
| 13 | * Gets the comfy workflows. | 15 | * Gets the comfy workflows. |
| @@ -21,7 +23,7 @@ function getComfyWorkflows(directories) { | |||
| 21 | .sort(Intl.Collator().compare); | 23 | .sort(Intl.Collator().compare); |
| 22 | } | 24 | } |
| 23 | 25 | ||
| 24 | const router = express.Router(); | 26 | export const router = express.Router(); |
| 25 | 27 | ||
| 26 | router.post('/ping', jsonParser, async (request, response) => { | 28 | router.post('/ping', jsonParser, async (request, response) => { |
| 27 | try { | 29 | try { |
| @@ -63,6 +65,7 @@ router.post('/upscalers', jsonParser, async (request, response) => { | |||
| 63 | throw new Error('SD WebUI returned an error.'); | 65 | throw new Error('SD WebUI returned an error.'); |
| 64 | } | 66 | } |
| 65 | 67 | ||
| 68 | /** @type {any} */ | ||
| 66 | const data = await result.json(); | 69 | const data = await result.json(); |
| 67 | const names = data.map(x => x.name); | 70 | const names = data.map(x => x.name); |
| 68 | return names; | 71 | return names; |
| @@ -83,6 +86,7 @@ router.post('/upscalers', jsonParser, async (request, response) => { | |||
| 83 | throw new Error('SD WebUI returned an error.'); | 86 | throw new Error('SD WebUI returned an error.'); |
| 84 | } | 87 | } |
| 85 | 88 | ||
| 89 | /** @type {any} */ | ||
| 86 | const data = await result.json(); | 90 | const data = await result.json(); |
| 87 | const names = data.map(x => x.name); | 91 | const names = data.map(x => x.name); |
| 88 | return names; | 92 | return names; |
| @@ -116,6 +120,7 @@ router.post('/vaes', jsonParser, async (request, response) => { | |||
| 116 | throw new Error('SD WebUI returned an error.'); | 120 | throw new Error('SD WebUI returned an error.'); |
| 117 | } | 121 | } |
| 118 | 122 | ||
| 123 | /** @type {any} */ | ||
| 119 | const data = await result.json(); | 124 | const data = await result.json(); |
| 120 | const names = data.map(x => x.model_name); | 125 | const names = data.map(x => x.model_name); |
| 121 | return response.send(names); | 126 | return response.send(names); |
| @@ -141,6 +146,7 @@ router.post('/samplers', jsonParser, async (request, response) => { | |||
| 141 | throw new Error('SD WebUI returned an error.'); | 146 | throw new Error('SD WebUI returned an error.'); |
| 142 | } | 147 | } |
| 143 | 148 | ||
| 149 | /** @type {any} */ | ||
| 144 | const data = await result.json(); | 150 | const data = await result.json(); |
| 145 | const names = data.map(x => x.name); | 151 | const names = data.map(x => x.name); |
| 146 | return response.send(names); | 152 | return response.send(names); |
| @@ -167,6 +173,7 @@ router.post('/schedulers', jsonParser, async (request, response) => { | |||
| 167 | throw new Error('SD WebUI returned an error.'); | 173 | throw new Error('SD WebUI returned an error.'); |
| 168 | } | 174 | } |
| 169 | 175 | ||
| 176 | /** @type {any} */ | ||
| 170 | const data = await result.json(); | 177 | const data = await result.json(); |
| 171 | const names = data.map(x => x.name); | 178 | const names = data.map(x => x.name); |
| 172 | return response.send(names); | 179 | return response.send(names); |
| @@ -192,6 +199,7 @@ router.post('/models', jsonParser, async (request, response) => { | |||
| 192 | throw new Error('SD WebUI returned an error.'); | 199 | throw new Error('SD WebUI returned an error.'); |
| 193 | } | 200 | } |
| 194 | 201 | ||
| 202 | /** @type {any} */ | ||
| 195 | const data = await result.json(); | 203 | const data = await result.json(); |
| 196 | const models = data.map(x => ({ value: x.title, text: x.title })); | 204 | const models = data.map(x => ({ value: x.title, text: x.title })); |
| 197 | return response.send(models); | 205 | return response.send(models); |
| @@ -212,6 +220,7 @@ router.post('/get-model', jsonParser, async (request, response) => { | |||
| 212 | 'Authorization': getBasicAuthHeader(request.body.auth), | 220 | 'Authorization': getBasicAuthHeader(request.body.auth), |
| 213 | }, | 221 | }, |
| 214 | }); | 222 | }); |
| 223 | /** @type {any} */ | ||
| 215 | const data = await result.json(); | 224 | const data = await result.json(); |
| 216 | return response.send(data['sd_model_checkpoint']); | 225 | return response.send(data['sd_model_checkpoint']); |
| 217 | } catch (error) { | 226 | } catch (error) { |
| @@ -231,7 +240,6 @@ router.post('/set-model', jsonParser, async (request, response) => { | |||
| 231 | headers: { | 240 | headers: { |
| 232 | 'Authorization': getBasicAuthHeader(request.body.auth), | 241 | 'Authorization': getBasicAuthHeader(request.body.auth), |
| 233 | }, | 242 | }, |
| 234 | timeout: 0, | ||
| 235 | }); | 243 | }); |
| 236 | const data = await result.json(); | 244 | const data = await result.json(); |
| 237 | return data; | 245 | return data; |
| @@ -251,7 +259,6 @@ router.post('/set-model', jsonParser, async (request, response) => { | |||
| 251 | 'Content-Type': 'application/json', | 259 | 'Content-Type': 'application/json', |
| 252 | 'Authorization': getBasicAuthHeader(request.body.auth), | 260 | 'Authorization': getBasicAuthHeader(request.body.auth), |
| 253 | }, | 261 | }, |
| 254 | timeout: 0, | ||
| 255 | }); | 262 | }); |
| 256 | 263 | ||
| 257 | if (!result.ok) { | 264 | if (!result.ok) { |
| @@ -262,6 +269,7 @@ router.post('/set-model', jsonParser, async (request, response) => { | |||
| 262 | const CHECK_INTERVAL = 2000; | 269 | const CHECK_INTERVAL = 2000; |
| 263 | 270 | ||
| 264 | for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { | 271 | for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { |
| 272 | /** @type {any} */ | ||
| 265 | const progressState = await getProgress(); | 273 | const progressState = await getProgress(); |
| 266 | 274 | ||
| 267 | const progress = progressState['progress']; | 275 | const progress = progressState['progress']; |
| @@ -306,8 +314,6 @@ router.post('/generate', jsonParser, async (request, response) => { | |||
| 306 | 'Content-Type': 'application/json', | 314 | 'Content-Type': 'application/json', |
| 307 | 'Authorization': getBasicAuthHeader(request.body.auth), | 315 | 'Authorization': getBasicAuthHeader(request.body.auth), |
| 308 | }, | 316 | }, |
| 309 | timeout: 0, | ||
| 310 | // @ts-ignore | ||
| 311 | signal: controller.signal, | 317 | signal: controller.signal, |
| 312 | }); | 318 | }); |
| 313 | 319 | ||
| @@ -343,6 +349,7 @@ router.post('/sd-next/upscalers', jsonParser, async (request, response) => { | |||
| 343 | // Vlad doesn't provide Latent Upscalers in the API, so we have to hardcode them here | 349 | // Vlad doesn't provide Latent Upscalers in the API, so we have to hardcode them here |
| 344 | const latentUpscalers = ['Latent', 'Latent (antialiased)', 'Latent (bicubic)', 'Latent (bicubic antialiased)', 'Latent (nearest)', 'Latent (nearest-exact)']; | 350 | const latentUpscalers = ['Latent', 'Latent (antialiased)', 'Latent (bicubic)', 'Latent (bicubic antialiased)', 'Latent (nearest)', 'Latent (nearest-exact)']; |
| 345 | 351 | ||
| 352 | /** @type {any} */ | ||
| 346 | const data = await result.json(); | 353 | const data = await result.json(); |
| 347 | const names = data.map(x => x.name); | 354 | const names = data.map(x => x.name); |
| 348 | 355 | ||
| @@ -385,6 +392,7 @@ comfy.post('/samplers', jsonParser, async (request, response) => { | |||
| 385 | throw new Error('ComfyUI returned an error.'); | 392 | throw new Error('ComfyUI returned an error.'); |
| 386 | } | 393 | } |
| 387 | 394 | ||
| 395 | /** @type {any} */ | ||
| 388 | const data = await result.json(); | 396 | const data = await result.json(); |
| 389 | return response.send(data.KSampler.input.required.sampler_name[0]); | 397 | return response.send(data.KSampler.input.required.sampler_name[0]); |
| 390 | } catch (error) { | 398 | } catch (error) { |
| @@ -402,6 +410,7 @@ comfy.post('/models', jsonParser, async (request, response) => { | |||
| 402 | if (!result.ok) { | 410 | if (!result.ok) { |
| 403 | throw new Error('ComfyUI returned an error.'); | 411 | throw new Error('ComfyUI returned an error.'); |
| 404 | } | 412 | } |
| 413 | /** @type {any} */ | ||
| 405 | const data = await result.json(); | 414 | const data = await result.json(); |
| 406 | return response.send(data.CheckpointLoaderSimple.input.required.ckpt_name[0].map(it => ({ value: it, text: it }))); | 415 | return response.send(data.CheckpointLoaderSimple.input.required.ckpt_name[0].map(it => ({ value: it, text: it }))); |
| 407 | } catch (error) { | 416 | } catch (error) { |
| @@ -420,6 +429,7 @@ comfy.post('/schedulers', jsonParser, async (request, response) => { | |||
| 420 | throw new Error('ComfyUI returned an error.'); | 429 | throw new Error('ComfyUI returned an error.'); |
| 421 | } | 430 | } |
| 422 | 431 | ||
| 432 | /** @type {any} */ | ||
| 423 | const data = await result.json(); | 433 | const data = await result.json(); |
| 424 | return response.send(data.KSampler.input.required.scheduler[0]); | 434 | return response.send(data.KSampler.input.required.scheduler[0]); |
| 425 | } catch (error) { | 435 | } catch (error) { |
| @@ -438,6 +448,7 @@ comfy.post('/vaes', jsonParser, async (request, response) => { | |||
| 438 | throw new Error('ComfyUI returned an error.'); | 448 | throw new Error('ComfyUI returned an error.'); |
| 439 | } | 449 | } |
| 440 | 450 | ||
| 451 | /** @type {any} */ | ||
| 441 | const data = await result.json(); | 452 | const data = await result.json(); |
| 442 | return response.send(data.VAELoader.input.required.vae_name[0]); | 453 | return response.send(data.VAELoader.input.required.vae_name[0]); |
| 443 | } catch (error) { | 454 | } catch (error) { |
| @@ -519,6 +530,7 @@ comfy.post('/generate', jsonParser, async (request, response) => { | |||
| 519 | throw new Error('ComfyUI returned an error.'); | 530 | throw new Error('ComfyUI returned an error.'); |
| 520 | } | 531 | } |
| 521 | 532 | ||
| 533 | /** @type {any} */ | ||
| 522 | const data = await promptResult.json(); | 534 | const data = await promptResult.json(); |
| 523 | const id = data.prompt_id; | 535 | const id = data.prompt_id; |
| 524 | let item; | 536 | let item; |
| @@ -529,6 +541,7 @@ comfy.post('/generate', jsonParser, async (request, response) => { | |||
| 529 | if (!result.ok) { | 541 | if (!result.ok) { |
| 530 | throw new Error('ComfyUI returned an error.'); | 542 | throw new Error('ComfyUI returned an error.'); |
| 531 | } | 543 | } |
| 544 | /** @type {any} */ | ||
| 532 | const history = await result.json(); | 545 | const history = await result.json(); |
| 533 | item = history[id]; | 546 | item = history[id]; |
| 534 | if (item) { | 547 | if (item) { |
| @@ -607,10 +620,9 @@ together.post('/generate', jsonParser, async (request, response) => { | |||
| 607 | 620 | ||
| 608 | console.log('TogetherAI request:', request.body); | 621 | console.log('TogetherAI request:', request.body); |
| 609 | 622 | ||
| 610 | const result = await fetch('https://api.together.xyz/api/inference', { | 623 | const result = await fetch('https://api.together.xyz/v1/images/generations', { |
| 611 | method: 'POST', | 624 | method: 'POST', |
| 612 | body: JSON.stringify({ | 625 | body: JSON.stringify({ |
| 613 | request_type: 'image-model-inference', | ||
| 614 | prompt: request.body.prompt, | 626 | prompt: request.body.prompt, |
| 615 | negative_prompt: request.body.negative_prompt, | 627 | negative_prompt: request.body.negative_prompt, |
| 616 | height: request.body.height, | 628 | height: request.body.height, |
| @@ -620,8 +632,6 @@ together.post('/generate', jsonParser, async (request, response) => { | |||
| 620 | n: 1, | 632 | n: 1, |
| 621 | // Limited to 10000 on playground, works fine with more. | 633 | // Limited to 10000 on playground, works fine with more. |
| 622 | seed: request.body.seed >= 0 ? request.body.seed : Math.floor(Math.random() * 10_000_000), | 634 | seed: request.body.seed >= 0 ? request.body.seed : Math.floor(Math.random() * 10_000_000), |
| 623 | // Don't know if that's supposed to be random or not. It works either way. | ||
| 624 | sessionKey: getHexString(40), | ||
| 625 | }), | 635 | }), |
| 626 | headers: { | 636 | headers: { |
| 627 | 'Content-Type': 'application/json', | 637 | 'Content-Type': 'application/json', |
| @@ -630,19 +640,23 @@ together.post('/generate', jsonParser, async (request, response) => { | |||
| 630 | }); | 640 | }); |
| 631 | 641 | ||
| 632 | if (!result.ok) { | 642 | if (!result.ok) { |
| 633 | console.log('TogetherAI returned an error.'); | 643 | console.log('TogetherAI returned an error.', { body: await result.text() }); |
| 634 | return response.sendStatus(500); | 644 | return response.sendStatus(500); |
| 635 | } | 645 | } |
| 636 | 646 | ||
| 647 | /** @type {any} */ | ||
| 637 | const data = await result.json(); | 648 | const data = await result.json(); |
| 638 | console.log('TogetherAI response:', data); | 649 | console.log('TogetherAI response:', data); |
| 639 | 650 | ||
| 640 | if (data.status !== 'finished') { | 651 | const choice = data?.data?.[0]; |
| 641 | console.log('TogetherAI job failed.'); | 652 | let b64_json = choice.b64_json; |
| 642 | return response.sendStatus(500); | 653 | |
| 654 | if (!b64_json) { | ||
| 655 | const buffer = await (await fetch(choice.url)).buffer(); | ||
| 656 | b64_json = buffer.toString('base64'); | ||
| 643 | } | 657 | } |
| 644 | 658 | ||
| 645 | return response.send(data); | 659 | return response.send({ format: 'jpg', data: b64_json }); |
| 646 | } catch (error) { | 660 | } catch (error) { |
| 647 | console.log(error); | 661 | console.log(error); |
| 648 | return response.sendStatus(500); | 662 | return response.sendStatus(500); |
| @@ -679,6 +693,8 @@ drawthings.post('/get-model', jsonParser, async (request, response) => { | |||
| 679 | const result = await fetch(url, { | 693 | const result = await fetch(url, { |
| 680 | method: 'GET', | 694 | method: 'GET', |
| 681 | }); | 695 | }); |
| 696 | |||
| 697 | /** @type {any} */ | ||
| 682 | const data = await result.json(); | 698 | const data = await result.json(); |
| 683 | 699 | ||
| 684 | return response.send(data['model']); | 700 | return response.send(data['model']); |
| @@ -696,6 +712,8 @@ drawthings.post('/get-upscaler', jsonParser, async (request, response) => { | |||
| 696 | const result = await fetch(url, { | 712 | const result = await fetch(url, { |
| 697 | method: 'GET', | 713 | method: 'GET', |
| 698 | }); | 714 | }); |
| 715 | |||
| 716 | /** @type {any} */ | ||
| 699 | const data = await result.json(); | 717 | const data = await result.json(); |
| 700 | 718 | ||
| 701 | return response.send(data['upscaler']); | 719 | return response.send(data['upscaler']); |
| @@ -724,7 +742,6 @@ drawthings.post('/generate', jsonParser, async (request, response) => { | |||
| 724 | 'Content-Type': 'application/json', | 742 | 'Content-Type': 'application/json', |
| 725 | 'Authorization': auth, | 743 | 'Authorization': auth, |
| 726 | }, | 744 | }, |
| 727 | timeout: 0, | ||
| 728 | }); | 745 | }); |
| 729 | 746 | ||
| 730 | if (!result.ok) { | 747 | if (!result.ok) { |
| @@ -846,7 +863,6 @@ stability.post('/generate', jsonParser, async (request, response) => { | |||
| 846 | 'Accept': 'image/*', | 863 | 'Accept': 'image/*', |
| 847 | }, | 864 | }, |
| 848 | body: formData, | 865 | body: formData, |
| 849 | timeout: 0, | ||
| 850 | }); | 866 | }); |
| 851 | 867 | ||
| 852 | if (!result.ok) { | 868 | if (!result.ok) { |
| @@ -993,5 +1009,3 @@ router.use('/pollinations', pollinations); | |||
| 993 | router.use('/stability', stability); | 1009 | router.use('/stability', stability); |
| 994 | router.use('/blockentropy', blockentropy); | 1010 | router.use('/blockentropy', blockentropy); |
| 995 | router.use('/huggingface', huggingface); | 1011 | router.use('/huggingface', huggingface); |
| 996 | |||
| 997 | module.exports = { router }; | ||
| @@ -1,14 +1,15 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | import crypto from 'node:crypto'; |
| 4 | const writeFileAtomic = require('write-file-atomic'); | 4 | |
| 5 | const crypto = require('crypto'); | 5 | import express from 'express'; |
| 6 | import writeFileAtomic from 'write-file-atomic'; | ||
| 6 | 7 | ||
| 7 | const readFile = fs.promises.readFile; | 8 | const readFile = fs.promises.readFile; |
| 8 | const readdir = fs.promises.readdir; | 9 | const readdir = fs.promises.readdir; |
| 9 | 10 | ||
| 10 | const { jsonParser } = require('../express-common'); | 11 | import { jsonParser } from '../express-common.js'; |
| 11 | const { getAllUserHandles, getUserDirectories } = require('../users'); | 12 | import { getAllUserHandles, getUserDirectories } from '../users.js'; |
| 12 | 13 | ||
| 13 | const STATS_FILE = 'stats.json'; | 14 | const STATS_FILE = 'stats.json'; |
| 14 | 15 | ||
| @@ -146,7 +147,7 @@ async function collectAndCreateStats(chatsPath, charactersPath) { | |||
| 146 | * @param {string} chatsPath Path to the directory containing the chat files. | 147 | * @param {string} chatsPath Path to the directory containing the chat files. |
| 147 | * @param {string} charactersPath Path to the directory containing the character files. | 148 | * @param {string} charactersPath Path to the directory containing the character files. |
| 148 | */ | 149 | */ |
| 149 | async function recreateStats(handle, chatsPath, charactersPath) { | 150 | export async function recreateStats(handle, chatsPath, charactersPath) { |
| 150 | console.log('Collecting and creating stats for user:', handle); | 151 | console.log('Collecting and creating stats for user:', handle); |
| 151 | const stats = await collectAndCreateStats(chatsPath, charactersPath); | 152 | const stats = await collectAndCreateStats(chatsPath, charactersPath); |
| 152 | STATS.set(handle, stats); | 153 | STATS.set(handle, stats); |
| @@ -157,7 +158,7 @@ async function recreateStats(handle, chatsPath, charactersPath) { | |||
| 157 | * Loads the stats file into memory. If the file doesn't exist or is invalid, | 158 | * Loads the stats file into memory. If the file doesn't exist or is invalid, |
| 158 | * initializes stats by collecting and creating them for each character. | 159 | * initializes stats by collecting and creating them for each character. |
| 159 | */ | 160 | */ |
| 160 | async function init() { | 161 | export async function init() { |
| 161 | try { | 162 | try { |
| 162 | const userHandles = await getAllUserHandles(); | 163 | const userHandles = await getAllUserHandles(); |
| 163 | for (const handle of userHandles) { | 164 | for (const handle of userHandles) { |
| @@ -209,7 +210,7 @@ async function saveStatsToFile() { | |||
| 209 | * Attempts to save charStats to a file and then terminates the process. | 210 | * Attempts to save charStats to a file and then terminates the process. |
| 210 | * If an error occurs during the file write, it logs the error before exiting. | 211 | * If an error occurs during the file write, it logs the error before exiting. |
| 211 | */ | 212 | */ |
| 212 | async function onExit() { | 213 | export async function onExit() { |
| 213 | try { | 214 | try { |
| 214 | await saveStatsToFile(); | 215 | await saveStatsToFile(); |
| 215 | } catch (err) { | 216 | } catch (err) { |
| @@ -434,7 +435,7 @@ function calculateTotalGenTimeAndWordCount( | |||
| 434 | }; | 435 | }; |
| 435 | } | 436 | } |
| 436 | 437 | ||
| 437 | const router = express.Router(); | 438 | export const router = express.Router(); |
| 438 | 439 | ||
| 439 | /** | 440 | /** |
| 440 | * Handle a POST request to get the stats object | 441 | * Handle a POST request to get the stats object |
| @@ -465,10 +466,3 @@ router.post('/update', jsonParser, function (request, response) { | |||
| 465 | setCharStats(request.user.profile.handle, request.body); | 466 | setCharStats(request.user.profile.handle, request.body); |
| 466 | return response.sendStatus(200); | 467 | return response.sendStatus(200); |
| 467 | }); | 468 | }); |
| 468 | |||
| 469 | module.exports = { | ||
| 470 | router, | ||
| 471 | recreateStats, | ||
| 472 | init, | ||
| 473 | onExit, | ||
| 474 | }; | ||
| @@ -1,11 +1,13 @@ | |||
| 1 | const express = require('express'); | 1 | import path from 'node:path'; |
| 2 | const path = require('path'); | 2 | import fs from 'node:fs'; |
| 3 | const fs = require('fs'); | ||
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | ||
| 6 | const { jsonParser } = require('../express-common'); | ||
| 7 | 3 | ||
| 8 | const router = express.Router(); | 4 | import express from 'express'; |
| 5 | import sanitize from 'sanitize-filename'; | ||
| 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 7 | |||
| 8 | import { jsonParser } from '../express-common.js'; | ||
| 9 | |||
| 10 | export const router = express.Router(); | ||
| 9 | 11 | ||
| 10 | router.post('/save', jsonParser, (request, response) => { | 12 | router.post('/save', jsonParser, (request, response) => { |
| 11 | if (!request.body || !request.body.name) { | 13 | if (!request.body || !request.body.name) { |
| @@ -36,5 +38,3 @@ router.post('/delete', jsonParser, function (request, response) { | |||
| 36 | return response.sendStatus(500); | 38 | return response.sendStatus(500); |
| 37 | } | 39 | } |
| 38 | }); | 40 | }); |
| 39 | |||
| 40 | module.exports = { router }; | ||
| @@ -1,14 +1,16 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const fsPromises = require('fs').promises; | 2 | import { promises as fsPromises } from 'node:fs'; |
| 3 | const path = require('path'); | 3 | import path from 'node:path'; |
| 4 | const mime = require('mime-types'); | 4 | |
| 5 | const express = require('express'); | 5 | import mime from 'mime-types'; |
| 6 | const sanitize = require('sanitize-filename'); | 6 | import express from 'express'; |
| 7 | const jimp = require('jimp'); | 7 | import sanitize from 'sanitize-filename'; |
| 8 | const writeFileAtomicSync = require('write-file-atomic').sync; | 8 | import jimp from 'jimp'; |
| 9 | const { getAllUserHandles, getUserDirectories } = require('../users'); | 9 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 10 | const { getConfigValue } = require('../util'); | 10 | |
| 11 | const { jsonParser } = require('../express-common'); | 11 | import { getAllUserHandles, getUserDirectories } from '../users.js'; |
| 12 | import { getConfigValue } from '../util.js'; | ||
| 13 | import { jsonParser } from '../express-common.js'; | ||
| 12 | 14 | ||
| 13 | const thumbnailsDisabled = getConfigValue('disableThumbnails', false); | 15 | const thumbnailsDisabled = getConfigValue('disableThumbnails', false); |
| 14 | const quality = getConfigValue('thumbnailsQuality', 95); | 16 | const quality = getConfigValue('thumbnailsQuality', 95); |
| @@ -16,7 +18,7 @@ const pngFormat = getConfigValue('avatarThumbnailsPng', false); | |||
| 16 | 18 | ||
| 17 | /** | 19 | /** |
| 18 | * Gets a path to thumbnail folder based on the type. | 20 | * Gets a path to thumbnail folder based on the type. |
| 19 | * @param {import('../users').UserDirectoryList} directories User directories | 21 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 20 | * @param {'bg' | 'avatar'} type Thumbnail type | 22 | * @param {'bg' | 'avatar'} type Thumbnail type |
| 21 | * @returns {string} Path to the thumbnails folder | 23 | * @returns {string} Path to the thumbnails folder |
| 22 | */ | 24 | */ |
| @@ -37,7 +39,7 @@ function getThumbnailFolder(directories, type) { | |||
| 37 | 39 | ||
| 38 | /** | 40 | /** |
| 39 | * Gets a path to the original images folder based on the type. | 41 | * Gets a path to the original images folder based on the type. |
| 40 | * @param {import('../users').UserDirectoryList} directories User directories | 42 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 41 | * @param {'bg' | 'avatar'} type Thumbnail type | 43 | * @param {'bg' | 'avatar'} type Thumbnail type |
| 42 | * @returns {string} Path to the original images folder | 44 | * @returns {string} Path to the original images folder |
| 43 | */ | 45 | */ |
| @@ -58,11 +60,11 @@ function getOriginalFolder(directories, type) { | |||
| 58 | 60 | ||
| 59 | /** | 61 | /** |
| 60 | * Removes the generated thumbnail from the disk. | 62 | * Removes the generated thumbnail from the disk. |
| 61 | * @param {import('../users').UserDirectoryList} directories User directories | 63 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 62 | * @param {'bg' | 'avatar'} type Type of the thumbnail | 64 | * @param {'bg' | 'avatar'} type Type of the thumbnail |
| 63 | * @param {string} file Name of the file | 65 | * @param {string} file Name of the file |
| 64 | */ | 66 | */ |
| 65 | function invalidateThumbnail(directories, type, file) { | 67 | export function invalidateThumbnail(directories, type, file) { |
| 66 | const folder = getThumbnailFolder(directories, type); | 68 | const folder = getThumbnailFolder(directories, type); |
| 67 | if (folder === undefined) throw new Error('Invalid thumbnail type'); | 69 | if (folder === undefined) throw new Error('Invalid thumbnail type'); |
| 68 | 70 | ||
| @@ -75,7 +77,7 @@ function invalidateThumbnail(directories, type, file) { | |||
| 75 | 77 | ||
| 76 | /** | 78 | /** |
| 77 | * Generates a thumbnail for the given file. | 79 | * Generates a thumbnail for the given file. |
| 78 | * @param {import('../users').UserDirectoryList} directories User directories | 80 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 79 | * @param {'bg' | 'avatar'} type Type of the thumbnail | 81 | * @param {'bg' | 'avatar'} type Type of the thumbnail |
| 80 | * @param {string} file Name of the file | 82 | * @param {string} file Name of the file |
| 81 | * @returns | 83 | * @returns |
| @@ -141,7 +143,7 @@ async function generateThumbnail(directories, type, file) { | |||
| 141 | * Ensures that the thumbnail cache for backgrounds is valid. | 143 | * Ensures that the thumbnail cache for backgrounds is valid. |
| 142 | * @returns {Promise<void>} Promise that resolves when the cache is validated | 144 | * @returns {Promise<void>} Promise that resolves when the cache is validated |
| 143 | */ | 145 | */ |
| 144 | async function ensureThumbnailCache() { | 146 | export async function ensureThumbnailCache() { |
| 145 | const userHandles = await getAllUserHandles(); | 147 | const userHandles = await getAllUserHandles(); |
| 146 | for (const handle of userHandles) { | 148 | for (const handle of userHandles) { |
| 147 | const directories = getUserDirectories(handle); | 149 | const directories = getUserDirectories(handle); |
| @@ -166,7 +168,7 @@ async function ensureThumbnailCache() { | |||
| 166 | } | 168 | } |
| 167 | } | 169 | } |
| 168 | 170 | ||
| 169 | const router = express.Router(); | 171 | export const router = express.Router(); |
| 170 | 172 | ||
| 171 | // Important: This route must be mounted as '/thumbnail'. It is used in the client code and saved to chat files. | 173 | // Important: This route must be mounted as '/thumbnail'. It is used in the client code and saved to chat files. |
| 172 | router.get('/', jsonParser, async function (request, response) { | 174 | router.get('/', jsonParser, async function (request, response) { |
| @@ -227,9 +229,3 @@ router.get('/', jsonParser, async function (request, response) { | |||
| 227 | return response.sendStatus(500); | 229 | return response.sendStatus(500); |
| 228 | } | 230 | } |
| 229 | }); | 231 | }); |
| 230 | |||
| 231 | module.exports = { | ||
| 232 | invalidateThumbnail, | ||
| 233 | ensureThumbnailCache, | ||
| 234 | router, | ||
| 235 | }; | ||
| @@ -1,15 +1,19 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | 3 | import { Buffer } from 'node:buffer'; |
| 4 | const { SentencePieceProcessor } = require('@agnai/sentencepiece-js'); | 4 | |
| 5 | const tiktoken = require('tiktoken'); | 5 | import express from 'express'; |
| 6 | const { Tokenizer } = require('@agnai/web-tokenizers'); | 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 7 | const { convertClaudePrompt } = require('../prompt-converters'); | 7 | |
| 8 | const { TEXTGEN_TYPES } = require('../constants'); | 8 | import { Tokenizer } from '@agnai/web-tokenizers'; |
| 9 | const { jsonParser } = require('../express-common'); | 9 | import { SentencePieceProcessor } from '@agnai/sentencepiece-js'; |
| 10 | const { setAdditionalHeaders } = require('../additional-headers'); | 10 | import tiktoken from 'tiktoken'; |
| 11 | const { getConfigValue, isValidUrl } = require('../util'); | 11 | |
| 12 | const writeFileAtomicSync = require('write-file-atomic').sync; | 12 | import { convertClaudePrompt } from '../prompt-converters.js'; |
| 13 | import { TEXTGEN_TYPES } from '../constants.js'; | ||
| 14 | import { jsonParser } from '../express-common.js'; | ||
| 15 | import { setAdditionalHeaders } from '../additional-headers.js'; | ||
| 16 | import { getConfigValue, isValidUrl } from '../util.js'; | ||
| 13 | 17 | ||
| 14 | /** | 18 | /** |
| 15 | * @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler | 19 | * @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler |
| @@ -23,7 +27,7 @@ const tokenizersCache = {}; | |||
| 23 | /** | 27 | /** |
| 24 | * @type {string[]} | 28 | * @type {string[]} |
| 25 | */ | 29 | */ |
| 26 | const TEXT_COMPLETION_MODELS = [ | 30 | export const TEXT_COMPLETION_MODELS = [ |
| 27 | 'gpt-3.5-turbo-instruct', | 31 | 'gpt-3.5-turbo-instruct', |
| 28 | 'gpt-3.5-turbo-instruct-0914', | 32 | 'gpt-3.5-turbo-instruct-0914', |
| 29 | 'text-davinci-003', | 33 | 'text-davinci-003', |
| @@ -223,7 +227,7 @@ const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyT | |||
| 223 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); | 227 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); |
| 224 | const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json'); | 228 | const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json'); |
| 225 | 229 | ||
| 226 | const sentencepieceTokenizers = [ | 230 | export const sentencepieceTokenizers = [ |
| 227 | 'llama', | 231 | 'llama', |
| 228 | 'nerdstash', | 232 | 'nerdstash', |
| 229 | 'nerdstash_v2', | 233 | 'nerdstash_v2', |
| @@ -238,7 +242,7 @@ const sentencepieceTokenizers = [ | |||
| 238 | * @param {string} model Sentencepiece model name | 242 | * @param {string} model Sentencepiece model name |
| 239 | * @returns {SentencePieceTokenizer|null} Sentencepiece tokenizer | 243 | * @returns {SentencePieceTokenizer|null} Sentencepiece tokenizer |
| 240 | */ | 244 | */ |
| 241 | function getSentencepiceTokenizer(model) { | 245 | export function getSentencepiceTokenizer(model) { |
| 242 | if (model.includes('llama')) { | 246 | if (model.includes('llama')) { |
| 243 | return spp_llama; | 247 | return spp_llama; |
| 244 | } | 248 | } |
| @@ -350,7 +354,7 @@ function getWebTokenizersChunks(tokenizer, ids) { | |||
| 350 | * @param {string} requestModel Models to use for tokenization | 354 | * @param {string} requestModel Models to use for tokenization |
| 351 | * @returns {string} Tokenizer model to use | 355 | * @returns {string} Tokenizer model to use |
| 352 | */ | 356 | */ |
| 353 | function getTokenizerModel(requestModel) { | 357 | export function getTokenizerModel(requestModel) { |
| 354 | if (requestModel.includes('o1-preview') || requestModel.includes('o1-mini')) { | 358 | if (requestModel.includes('o1-preview') || requestModel.includes('o1-mini')) { |
| 355 | return 'gpt-4o'; | 359 | return 'gpt-4o'; |
| 356 | } | 360 | } |
| @@ -427,7 +431,7 @@ function getTokenizerModel(requestModel) { | |||
| 427 | return 'gpt-3.5-turbo'; | 431 | return 'gpt-3.5-turbo'; |
| 428 | } | 432 | } |
| 429 | 433 | ||
| 430 | function getTiktokenTokenizer(model) { | 434 | export function getTiktokenTokenizer(model) { |
| 431 | if (tokenizersCache[model]) { | 435 | if (tokenizersCache[model]) { |
| 432 | return tokenizersCache[model]; | 436 | return tokenizersCache[model]; |
| 433 | } | 437 | } |
| @@ -444,7 +448,7 @@ function getTiktokenTokenizer(model) { | |||
| 444 | * @param {object[]} messages Array of messages | 448 | * @param {object[]} messages Array of messages |
| 445 | * @returns {number} Number of tokens | 449 | * @returns {number} Number of tokens |
| 446 | */ | 450 | */ |
| 447 | function countWebTokenizerTokens(tokenizer, messages) { | 451 | export function countWebTokenizerTokens(tokenizer, messages) { |
| 448 | // Should be fine if we use the old conversion method instead of the messages API one i think? | 452 | // Should be fine if we use the old conversion method instead of the messages API one i think? |
| 449 | const convertedPrompt = convertClaudePrompt(messages, false, '', false, false, '', false); | 453 | const convertedPrompt = convertClaudePrompt(messages, false, '', false, false, '', false); |
| 450 | 454 | ||
| @@ -636,7 +640,7 @@ function createWebTokenizerDecodingHandler(tokenizer) { | |||
| 636 | }; | 640 | }; |
| 637 | } | 641 | } |
| 638 | 642 | ||
| 639 | const router = express.Router(); | 643 | export const router = express.Router(); |
| 640 | 644 | ||
| 641 | router.post('/llama/encode', jsonParser, createSentencepieceEncodingHandler(spp_llama)); | 645 | router.post('/llama/encode', jsonParser, createSentencepieceEncodingHandler(spp_llama)); |
| 642 | router.post('/nerdstash/encode', jsonParser, createSentencepieceEncodingHandler(spp_nerd)); | 646 | router.post('/nerdstash/encode', jsonParser, createSentencepieceEncodingHandler(spp_nerd)); |
| @@ -1002,13 +1006,3 @@ router.post('/remote/textgenerationwebui/encode', jsonParser, async function (re | |||
| 1002 | return response.send({ error: true }); | 1006 | return response.send({ error: true }); |
| 1003 | } | 1007 | } |
| 1004 | }); | 1008 | }); |
| 1005 | |||
| 1006 | module.exports = { | ||
| 1007 | TEXT_COMPLETION_MODELS, | ||
| 1008 | getTokenizerModel, | ||
| 1009 | getTiktokenTokenizer, | ||
| 1010 | countWebTokenizerTokens, | ||
| 1011 | getSentencepiceTokenizer, | ||
| 1012 | sentencepieceTokenizers, | ||
| 1013 | router, | ||
| 1014 | }; | ||
| @@ -1,29 +1,18 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import https from 'node:https'; |
| 2 | const https = require('https'); | 2 | |
| 3 | const express = require('express'); | 3 | import fetch from 'node-fetch'; |
| 4 | const iconv = require('iconv-lite'); | 4 | import express from 'express'; |
| 5 | const { readSecret, SECRET_KEYS } = require('./secrets'); | 5 | import bingTranslateApi from 'bing-translate-api'; |
| 6 | const { getConfigValue, uuidv4 } = require('../util'); | 6 | import googleTranslateApi from 'google-translate-api-x'; |
| 7 | const { jsonParser } = require('../express-common'); | 7 | |
| 8 | import { readSecret, SECRET_KEYS } from './secrets.js'; | ||
| 9 | import { getConfigValue, uuidv4 } from '../util.js'; | ||
| 10 | import { jsonParser } from '../express-common.js'; | ||
| 8 | 11 | ||
| 9 | const DEEPLX_URL_DEFAULT = 'http://127.0.0.1:1188/translate'; | 12 | const DEEPLX_URL_DEFAULT = 'http://127.0.0.1:1188/translate'; |
| 10 | const ONERING_URL_DEFAULT = 'http://127.0.0.1:4990/translate'; | 13 | const ONERING_URL_DEFAULT = 'http://127.0.0.1:4990/translate'; |
| 11 | 14 | ||
| 12 | /** | 15 | export const router = express.Router(); |
| 13 | * Tries to decode a Node.js Buffer to a string using iconv-lite for UTF-8. | ||
| 14 | * @param {Buffer} buffer Node.js Buffer | ||
| 15 | * @returns {string} Decoded string | ||
| 16 | */ | ||
| 17 | function decodeBuffer(buffer) { | ||
| 18 | try { | ||
| 19 | return iconv.decode(buffer, 'utf-8'); | ||
| 20 | } catch (error) { | ||
| 21 | console.log('Failed to decode buffer:', error); | ||
| 22 | return buffer.toString('utf-8'); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | const router = express.Router(); | ||
| 27 | 16 | ||
| 28 | router.post('/libre', jsonParser, async (request, response) => { | 17 | router.post('/libre', jsonParser, async (request, response) => { |
| 29 | const key = readSecret(request.user.directories, SECRET_KEYS.LIBRE); | 18 | const key = readSecret(request.user.directories, SECRET_KEYS.LIBRE); |
| @@ -70,6 +59,7 @@ router.post('/libre', jsonParser, async (request, response) => { | |||
| 70 | return response.sendStatus(result.status); | 59 | return response.sendStatus(result.status); |
| 71 | } | 60 | } |
| 72 | 61 | ||
| 62 | /** @type {any} */ | ||
| 73 | const json = await result.json(); | 63 | const json = await result.json(); |
| 74 | console.log('Translated text: ' + json.translatedText); | 64 | console.log('Translated text: ' + json.translatedText); |
| 75 | 65 | ||
| @@ -82,7 +72,6 @@ router.post('/libre', jsonParser, async (request, response) => { | |||
| 82 | 72 | ||
| 83 | router.post('/google', jsonParser, async (request, response) => { | 73 | router.post('/google', jsonParser, async (request, response) => { |
| 84 | try { | 74 | try { |
| 85 | const { generateRequestUrl, normaliseResponse } = require('google-translate-api-browser'); | ||
| 86 | const text = request.body.text; | 75 | const text = request.body.text; |
| 87 | const lang = request.body.lang; | 76 | const lang = request.body.lang; |
| 88 | 77 | ||
| @@ -92,31 +81,12 @@ router.post('/google', jsonParser, async (request, response) => { | |||
| 92 | 81 | ||
| 93 | console.log('Input text: ' + text); | 82 | console.log('Input text: ' + text); |
| 94 | 83 | ||
| 95 | const url = generateRequestUrl(text, { to: lang }); | 84 | const result = await googleTranslateApi(text, { to: lang, forceBatch: false }); |
| 85 | const translatedText = Array.isArray(result) ? result.map(x => x.text).join('') : result.text; | ||
| 96 | 86 | ||
| 97 | https.get(url, (resp) => { | 87 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); |
| 98 | const data = []; | 88 | console.log('Translated text: ' + translatedText); |
| 99 | 89 | return response.send(translatedText); | |
| 100 | resp.on('data', (chunk) => { | ||
| 101 | data.push(chunk); | ||
| 102 | }); | ||
| 103 | |||
| 104 | resp.on('end', () => { | ||
| 105 | try { | ||
| 106 | const decodedData = decodeBuffer(Buffer.concat(data)); | ||
| 107 | const result = normaliseResponse(JSON.parse(decodedData)); | ||
| 108 | console.log('Translated text: ' + result.text); | ||
| 109 | response.setHeader('Content-Type', 'text/plain; charset=utf-8'); | ||
| 110 | return response.send(result.text); | ||
| 111 | } catch (error) { | ||
| 112 | console.log('Translation error', error); | ||
| 113 | return response.sendStatus(500); | ||
| 114 | } | ||
| 115 | }); | ||
| 116 | }).on('error', (err) => { | ||
| 117 | console.log('Translation error: ' + err.message); | ||
| 118 | return response.sendStatus(500); | ||
| 119 | }); | ||
| 120 | } catch (error) { | 90 | } catch (error) { |
| 121 | console.log('Translation error', error); | 91 | console.log('Translation error', error); |
| 122 | return response.sendStatus(500); | 92 | return response.sendStatus(500); |
| @@ -151,7 +121,6 @@ router.post('/yandex', jsonParser, async (request, response) => { | |||
| 151 | headers: { | 121 | headers: { |
| 152 | 'Content-Type': 'application/x-www-form-urlencoded', | 122 | 'Content-Type': 'application/x-www-form-urlencoded', |
| 153 | }, | 123 | }, |
| 154 | timeout: 0, | ||
| 155 | }); | 124 | }); |
| 156 | 125 | ||
| 157 | if (!result.ok) { | 126 | if (!result.ok) { |
| @@ -160,6 +129,7 @@ router.post('/yandex', jsonParser, async (request, response) => { | |||
| 160 | return response.sendStatus(500); | 129 | return response.sendStatus(500); |
| 161 | } | 130 | } |
| 162 | 131 | ||
| 132 | /** @type {any} */ | ||
| 163 | const json = await result.json(); | 133 | const json = await result.json(); |
| 164 | const translated = json.text.join(); | 134 | const translated = json.text.join(); |
| 165 | console.log('Translated text: ' + translated); | 135 | console.log('Translated text: ' + translated); |
| @@ -257,7 +227,6 @@ router.post('/deepl', jsonParser, async (request, response) => { | |||
| 257 | 'Authorization': `DeepL-Auth-Key ${key}`, | 227 | 'Authorization': `DeepL-Auth-Key ${key}`, |
| 258 | 'Content-Type': 'application/x-www-form-urlencoded', | 228 | 'Content-Type': 'application/x-www-form-urlencoded', |
| 259 | }, | 229 | }, |
| 260 | timeout: 0, | ||
| 261 | }); | 230 | }); |
| 262 | 231 | ||
| 263 | if (!result.ok) { | 232 | if (!result.ok) { |
| @@ -266,6 +235,7 @@ router.post('/deepl', jsonParser, async (request, response) => { | |||
| 266 | return response.sendStatus(result.status); | 235 | return response.sendStatus(result.status); |
| 267 | } | 236 | } |
| 268 | 237 | ||
| 238 | /** @type {any} */ | ||
| 269 | const json = await result.json(); | 239 | const json = await result.json(); |
| 270 | console.log('Translated text: ' + json.translations[0].text); | 240 | console.log('Translated text: ' + json.translations[0].text); |
| 271 | 241 | ||
| @@ -310,7 +280,6 @@ router.post('/onering', jsonParser, async (request, response) => { | |||
| 310 | 280 | ||
| 311 | const result = await fetch(fetchUrl, { | 281 | const result = await fetch(fetchUrl, { |
| 312 | method: 'GET', | 282 | method: 'GET', |
| 313 | timeout: 0, | ||
| 314 | }); | 283 | }); |
| 315 | 284 | ||
| 316 | if (!result.ok) { | 285 | if (!result.ok) { |
| @@ -319,6 +288,7 @@ router.post('/onering', jsonParser, async (request, response) => { | |||
| 319 | return response.sendStatus(result.status); | 288 | return response.sendStatus(result.status); |
| 320 | } | 289 | } |
| 321 | 290 | ||
| 291 | /** @type {any} */ | ||
| 322 | const data = await result.json(); | 292 | const data = await result.json(); |
| 323 | console.log('Translated text: ' + data.result); | 293 | console.log('Translated text: ' + data.result); |
| 324 | 294 | ||
| @@ -366,7 +336,6 @@ router.post('/deeplx', jsonParser, async (request, response) => { | |||
| 366 | 'Accept': 'application/json', | 336 | 'Accept': 'application/json', |
| 367 | 'Content-Type': 'application/json', | 337 | 'Content-Type': 'application/json', |
| 368 | }, | 338 | }, |
| 369 | timeout: 0, | ||
| 370 | }); | 339 | }); |
| 371 | 340 | ||
| 372 | if (!result.ok) { | 341 | if (!result.ok) { |
| @@ -375,6 +344,7 @@ router.post('/deeplx', jsonParser, async (request, response) => { | |||
| 375 | return response.sendStatus(result.status); | 344 | return response.sendStatus(result.status); |
| 376 | } | 345 | } |
| 377 | 346 | ||
| 347 | /** @type {any} */ | ||
| 378 | const json = await result.json(); | 348 | const json = await result.json(); |
| 379 | console.log('Translated text: ' + json.data); | 349 | console.log('Translated text: ' + json.data); |
| 380 | 350 | ||
| @@ -386,7 +356,6 @@ router.post('/deeplx', jsonParser, async (request, response) => { | |||
| 386 | }); | 356 | }); |
| 387 | 357 | ||
| 388 | router.post('/bing', jsonParser, async (request, response) => { | 358 | router.post('/bing', jsonParser, async (request, response) => { |
| 389 | const bingTranslateApi = require('bing-translate-api'); | ||
| 390 | const text = request.body.text; | 359 | const text = request.body.text; |
| 391 | let lang = request.body.lang; | 360 | let lang = request.body.lang; |
| 392 | 361 | ||
| @@ -408,5 +377,3 @@ router.post('/bing', jsonParser, async (request, response) => { | |||
| 408 | return response.sendStatus(500); | 377 | return response.sendStatus(500); |
| 409 | }); | 378 | }); |
| 410 | }); | 379 | }); |
| 411 | |||
| 412 | module.exports = { router }; | ||
| @@ -1,10 +1,11 @@ | |||
| 1 | const fsPromises = require('fs').promises; | 1 | import { promises as fsPromises } from 'node:fs'; |
| 2 | const storage = require('node-persist'); | 2 | |
| 3 | const express = require('express'); | 3 | import storage from 'node-persist'; |
| 4 | const lodash = require('lodash'); | 4 | import express from 'express'; |
| 5 | const { jsonParser } = require('../express-common'); | 5 | import lodash from 'lodash'; |
| 6 | const { checkForNewContent } = require('./content-manager'); | 6 | import { jsonParser } from '../express-common.js'; |
| 7 | const { | 7 | import { checkForNewContent } from './content-manager.js'; |
| 8 | import { | ||
| 8 | KEY_PREFIX, | 9 | KEY_PREFIX, |
| 9 | toKey, | 10 | toKey, |
| 10 | requireAdminMiddleware, | 11 | requireAdminMiddleware, |
| @@ -14,17 +15,17 @@ const { | |||
| 14 | getPasswordHash, | 15 | getPasswordHash, |
| 15 | getUserDirectories, | 16 | getUserDirectories, |
| 16 | ensurePublicDirectoriesExist, | 17 | ensurePublicDirectoriesExist, |
| 17 | } = require('../users'); | 18 | } from '../users.js'; |
| 18 | const { DEFAULT_USER } = require('../constants'); | 19 | import { DEFAULT_USER } from '../constants.js'; |
| 19 | 20 | ||
| 20 | const router = express.Router(); | 21 | export const router = express.Router(); |
| 21 | 22 | ||
| 22 | router.post('/get', requireAdminMiddleware, jsonParser, async (_request, response) => { | 23 | router.post('/get', requireAdminMiddleware, jsonParser, async (_request, response) => { |
| 23 | try { | 24 | try { |
| 24 | /** @type {import('../users').User[]} */ | 25 | /** @type {import('../users.js').User[]} */ |
| 25 | const users = await storage.values(x => x.key.startsWith(KEY_PREFIX)); | 26 | const users = await storage.values(x => x.key.startsWith(KEY_PREFIX)); |
| 26 | 27 | ||
| 27 | /** @type {Promise<import('../users').UserViewModel>[]} */ | 28 | /** @type {Promise<import('../users.js').UserViewModel>[]} */ |
| 28 | const viewModelPromises = users | 29 | const viewModelPromises = users |
| 29 | .map(user => new Promise(resolve => { | 30 | .map(user => new Promise(resolve => { |
| 30 | getUserAvatar(user.handle).then(avatar => | 31 | getUserAvatar(user.handle).then(avatar => |
| @@ -61,7 +62,7 @@ router.post('/disable', requireAdminMiddleware, jsonParser, async (request, resp | |||
| 61 | return response.status(400).json({ error: 'Cannot disable yourself' }); | 62 | return response.status(400).json({ error: 'Cannot disable yourself' }); |
| 62 | } | 63 | } |
| 63 | 64 | ||
| 64 | /** @type {import('../users').User} */ | 65 | /** @type {import('../users.js').User} */ |
| 65 | const user = await storage.getItem(toKey(request.body.handle)); | 66 | const user = await storage.getItem(toKey(request.body.handle)); |
| 66 | 67 | ||
| 67 | if (!user) { | 68 | if (!user) { |
| @@ -85,7 +86,7 @@ router.post('/enable', requireAdminMiddleware, jsonParser, async (request, respo | |||
| 85 | return response.status(400).json({ error: 'Missing required fields' }); | 86 | return response.status(400).json({ error: 'Missing required fields' }); |
| 86 | } | 87 | } |
| 87 | 88 | ||
| 88 | /** @type {import('../users').User} */ | 89 | /** @type {import('../users.js').User} */ |
| 89 | const user = await storage.getItem(toKey(request.body.handle)); | 90 | const user = await storage.getItem(toKey(request.body.handle)); |
| 90 | 91 | ||
| 91 | if (!user) { | 92 | if (!user) { |
| @@ -109,7 +110,7 @@ router.post('/promote', requireAdminMiddleware, jsonParser, async (request, resp | |||
| 109 | return response.status(400).json({ error: 'Missing required fields' }); | 110 | return response.status(400).json({ error: 'Missing required fields' }); |
| 110 | } | 111 | } |
| 111 | 112 | ||
| 112 | /** @type {import('../users').User} */ | 113 | /** @type {import('../users.js').User} */ |
| 113 | const user = await storage.getItem(toKey(request.body.handle)); | 114 | const user = await storage.getItem(toKey(request.body.handle)); |
| 114 | 115 | ||
| 115 | if (!user) { | 116 | if (!user) { |
| @@ -138,7 +139,7 @@ router.post('/demote', requireAdminMiddleware, jsonParser, async (request, respo | |||
| 138 | return response.status(400).json({ error: 'Cannot demote yourself' }); | 139 | return response.status(400).json({ error: 'Cannot demote yourself' }); |
| 139 | } | 140 | } |
| 140 | 141 | ||
| 141 | /** @type {import('../users').User} */ | 142 | /** @type {import('../users.js').User} */ |
| 142 | const user = await storage.getItem(toKey(request.body.handle)); | 143 | const user = await storage.getItem(toKey(request.body.handle)); |
| 143 | 144 | ||
| 144 | if (!user) { | 145 | if (!user) { |
| @@ -249,7 +250,3 @@ router.post('/slugify', requireAdminMiddleware, jsonParser, async (request, resp | |||
| 249 | return response.sendStatus(500); | 250 | return response.sendStatus(500); |
| 250 | } | 251 | } |
| 251 | }); | 252 | }); |
| 252 | |||
| 253 | module.exports = { | ||
| 254 | router, | ||
| 255 | }; | ||
| @@ -1,18 +1,19 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fsPromises = require('fs').promises; | 2 | import { promises as fsPromises } from 'node:fs'; |
| 3 | const storage = require('node-persist'); | 3 | import crypto from 'node:crypto'; |
| 4 | const express = require('express'); | 4 | |
| 5 | const crypto = require('crypto'); | 5 | import storage from 'node-persist'; |
| 6 | const { jsonParser } = require('../express-common'); | 6 | import express from 'express'; |
| 7 | const { getUserAvatar, toKey, getPasswordHash, getPasswordSalt, createBackupArchive, ensurePublicDirectoriesExist, toAvatarKey } = require('../users'); | 7 | |
| 8 | const { SETTINGS_FILE } = require('../constants'); | 8 | import { jsonParser } from '../express-common.js'; |
| 9 | const contentManager = require('./content-manager'); | 9 | import { getUserAvatar, toKey, getPasswordHash, getPasswordSalt, createBackupArchive, ensurePublicDirectoriesExist, toAvatarKey } from '../users.js'; |
| 10 | const { color, Cache } = require('../util'); | 10 | import { SETTINGS_FILE } from '../constants.js'; |
| 11 | const { checkForNewContent } = require('./content-manager'); | 11 | import { checkForNewContent, CONTENT_TYPES } from './content-manager.js'; |
| 12 | import { color, Cache } from '../util.js'; | ||
| 12 | 13 | ||
| 13 | const RESET_CACHE = new Cache(5 * 60 * 1000); | 14 | const RESET_CACHE = new Cache(5 * 60 * 1000); |
| 14 | 15 | ||
| 15 | const router = express.Router(); | 16 | export const router = express.Router(); |
| 16 | 17 | ||
| 17 | router.post('/logout', async (request, response) => { | 18 | router.post('/logout', async (request, response) => { |
| 18 | try { | 19 | try { |
| @@ -70,7 +71,7 @@ router.post('/change-avatar', jsonParser, async (request, response) => { | |||
| 70 | return response.status(400).json({ error: 'Invalid data URL' }); | 71 | return response.status(400).json({ error: 'Invalid data URL' }); |
| 71 | } | 72 | } |
| 72 | 73 | ||
| 73 | /** @type {import('../users').User} */ | 74 | /** @type {import('../users.js').User} */ |
| 74 | const user = await storage.getItem(toKey(request.body.handle)); | 75 | const user = await storage.getItem(toKey(request.body.handle)); |
| 75 | 76 | ||
| 76 | if (!user) { | 77 | if (!user) { |
| @@ -99,7 +100,7 @@ router.post('/change-password', jsonParser, async (request, response) => { | |||
| 99 | return response.status(403).json({ error: 'Unauthorized' }); | 100 | return response.status(403).json({ error: 'Unauthorized' }); |
| 100 | } | 101 | } |
| 101 | 102 | ||
| 102 | /** @type {import('../users').User} */ | 103 | /** @type {import('../users.js').User} */ |
| 103 | const user = await storage.getItem(toKey(request.body.handle)); | 104 | const user = await storage.getItem(toKey(request.body.handle)); |
| 104 | 105 | ||
| 105 | if (!user) { | 106 | if (!user) { |
| @@ -166,7 +167,7 @@ router.post('/reset-settings', jsonParser, async (request, response) => { | |||
| 166 | 167 | ||
| 167 | const pathToFile = path.join(request.user.directories.root, SETTINGS_FILE); | 168 | const pathToFile = path.join(request.user.directories.root, SETTINGS_FILE); |
| 168 | await fsPromises.rm(pathToFile, { force: true }); | 169 | await fsPromises.rm(pathToFile, { force: true }); |
| 169 | await contentManager.checkForNewContent([request.user.directories], [contentManager.CONTENT_TYPES.SETTINGS]); | 170 | await checkForNewContent([request.user.directories], [CONTENT_TYPES.SETTINGS]); |
| 170 | 171 | ||
| 171 | return response.sendStatus(204); | 172 | return response.sendStatus(204); |
| 172 | } catch (error) { | 173 | } catch (error) { |
| @@ -187,7 +188,7 @@ router.post('/change-name', jsonParser, async (request, response) => { | |||
| 187 | return response.status(403).json({ error: 'Unauthorized' }); | 188 | return response.status(403).json({ error: 'Unauthorized' }); |
| 188 | } | 189 | } |
| 189 | 190 | ||
| 190 | /** @type {import('../users').User} */ | 191 | /** @type {import('../users.js').User} */ |
| 191 | const user = await storage.getItem(toKey(request.body.handle)); | 192 | const user = await storage.getItem(toKey(request.body.handle)); |
| 192 | 193 | ||
| 193 | if (!user) { | 194 | if (!user) { |
| @@ -251,7 +252,3 @@ router.post('/reset-step2', jsonParser, async (request, response) => { | |||
| 251 | return response.sendStatus(500); | 252 | return response.sendStatus(500); |
| 252 | } | 253 | } |
| 253 | }); | 254 | }); |
| 254 | |||
| 255 | module.exports = { | ||
| 256 | router, | ||
| 257 | }; | ||
| @@ -1,15 +1,16 @@ | |||
| 1 | const crypto = require('crypto'); | 1 | import crypto from 'node:crypto'; |
| 2 | const storage = require('node-persist'); | 2 | |
| 3 | const express = require('express'); | 3 | import storage from 'node-persist'; |
| 4 | const { RateLimiterMemory, RateLimiterRes } = require('rate-limiter-flexible'); | 4 | import express from 'express'; |
| 5 | const { jsonParser, getIpFromRequest } = require('../express-common'); | 5 | import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; |
| 6 | const { color, Cache, getConfigValue } = require('../util'); | 6 | import { jsonParser, getIpFromRequest } from '../express-common.js'; |
| 7 | const { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } = require('../users'); | 7 | import { color, Cache, getConfigValue } from '../util.js'; |
| 8 | import { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } from '../users.js'; | ||
| 8 | 9 | ||
| 9 | const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false); | 10 | const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false); |
| 10 | const MFA_CACHE = new Cache(5 * 60 * 1000); | 11 | const MFA_CACHE = new Cache(5 * 60 * 1000); |
| 11 | 12 | ||
| 12 | const router = express.Router(); | 13 | export const router = express.Router(); |
| 13 | const loginLimiter = new RateLimiterMemory({ | 14 | const loginLimiter = new RateLimiterMemory({ |
| 14 | points: 5, | 15 | points: 5, |
| 15 | duration: 60, | 16 | duration: 60, |
| @@ -25,10 +26,10 @@ router.post('/list', async (_request, response) => { | |||
| 25 | return response.sendStatus(204); | 26 | return response.sendStatus(204); |
| 26 | } | 27 | } |
| 27 | 28 | ||
| 28 | /** @type {import('../users').User[]} */ | 29 | /** @type {import('../users.js').User[]} */ |
| 29 | const users = await storage.values(x => x.key.startsWith(KEY_PREFIX)); | 30 | const users = await storage.values(x => x.key.startsWith(KEY_PREFIX)); |
| 30 | 31 | ||
| 31 | /** @type {Promise<import('../users').UserViewModel>[]} */ | 32 | /** @type {Promise<import('../users.js').UserViewModel>[]} */ |
| 32 | const viewModelPromises = users | 33 | const viewModelPromises = users |
| 33 | .filter(x => x.enabled) | 34 | .filter(x => x.enabled) |
| 34 | .map(user => new Promise(async (resolve) => { | 35 | .map(user => new Promise(async (resolve) => { |
| @@ -62,7 +63,7 @@ router.post('/login', jsonParser, async (request, response) => { | |||
| 62 | const ip = getIpFromRequest(request); | 63 | const ip = getIpFromRequest(request); |
| 63 | await loginLimiter.consume(ip); | 64 | await loginLimiter.consume(ip); |
| 64 | 65 | ||
| 65 | /** @type {import('../users').User} */ | 66 | /** @type {import('../users.js').User} */ |
| 66 | const user = await storage.getItem(toKey(request.body.handle)); | 67 | const user = await storage.getItem(toKey(request.body.handle)); |
| 67 | 68 | ||
| 68 | if (!user) { | 69 | if (!user) { |
| @@ -110,7 +111,7 @@ router.post('/recover-step1', jsonParser, async (request, response) => { | |||
| 110 | const ip = getIpFromRequest(request); | 111 | const ip = getIpFromRequest(request); |
| 111 | await recoverLimiter.consume(ip); | 112 | await recoverLimiter.consume(ip); |
| 112 | 113 | ||
| 113 | /** @type {import('../users').User} */ | 114 | /** @type {import('../users.js').User} */ |
| 114 | const user = await storage.getItem(toKey(request.body.handle)); | 115 | const user = await storage.getItem(toKey(request.body.handle)); |
| 115 | 116 | ||
| 116 | if (!user) { | 117 | if (!user) { |
| @@ -147,7 +148,7 @@ router.post('/recover-step2', jsonParser, async (request, response) => { | |||
| 147 | return response.status(400).json({ error: 'Missing required fields' }); | 148 | return response.status(400).json({ error: 'Missing required fields' }); |
| 148 | } | 149 | } |
| 149 | 150 | ||
| 150 | /** @type {import('../users').User} */ | 151 | /** @type {import('../users.js').User} */ |
| 151 | const user = await storage.getItem(toKey(request.body.handle)); | 152 | const user = await storage.getItem(toKey(request.body.handle)); |
| 152 | const ip = getIpFromRequest(request); | 153 | const ip = getIpFromRequest(request); |
| 153 | 154 | ||
| @@ -193,7 +194,3 @@ router.post('/recover-step2', jsonParser, async (request, response) => { | |||
| 193 | return response.sendStatus(500); | 194 | return response.sendStatus(500); |
| 194 | } | 195 | } |
| 195 | }); | 196 | }); |
| 196 | |||
| 197 | module.exports = { | ||
| 198 | router, | ||
| 199 | }; | ||
| @@ -1,10 +1,22 @@ | |||
| 1 | const vectra = require('vectra'); | 1 | import path from 'node:path'; |
| 2 | const path = require('path'); | 2 | import fs from 'node:fs'; |
| 3 | const fs = require('fs'); | 3 | |
| 4 | const express = require('express'); | 4 | import vectra from 'vectra'; |
| 5 | const sanitize = require('sanitize-filename'); | 5 | import express from 'express'; |
| 6 | const { jsonParser } = require('../express-common'); | 6 | import sanitize from 'sanitize-filename'; |
| 7 | const { getConfigValue } = require('../util'); | 7 | |
| 8 | import { jsonParser } from '../express-common.js'; | ||
| 9 | import { getConfigValue } from '../util.js'; | ||
| 10 | |||
| 11 | import { getNomicAIBatchVector, getNomicAIVector } from '../vectors/nomicai-vectors.js'; | ||
| 12 | import { getOpenAIVector, getOpenAIBatchVector } from '../vectors/openai-vectors.js'; | ||
| 13 | import { getTransformersVector, getTransformersBatchVector } from '../vectors/embedding.js'; | ||
| 14 | import { getExtrasVector, getExtrasBatchVector } from '../vectors/extras-vectors.js'; | ||
| 15 | import { getMakerSuiteVector, getMakerSuiteBatchVector } from '../vectors/makersuite-vectors.js'; | ||
| 16 | import { getCohereVector, getCohereBatchVector } from '../vectors/cohere-vectors.js'; | ||
| 17 | import { getLlamaCppVector, getLlamaCppBatchVector } from '../vectors/llamacpp-vectors.js'; | ||
| 18 | import { getVllmVector, getVllmBatchVector } from '../vectors/vllm-vectors.js'; | ||
| 19 | import { getOllamaVector, getOllamaBatchVector } from '../vectors/ollama-vectors.js'; | ||
| 8 | 20 | ||
| 9 | // Don't forget to add new sources to the SOURCES array | 21 | // Don't forget to add new sources to the SOURCES array |
| 10 | const SOURCES = [ | 22 | const SOURCES = [ |
| @@ -27,31 +39,31 @@ const SOURCES = [ | |||
| 27 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 39 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| 28 | * @param {string} text - The text to get the vector for | 40 | * @param {string} text - The text to get the vector for |
| 29 | * @param {boolean} isQuery - If the text is a query for embedding search | 41 | * @param {boolean} isQuery - If the text is a query for embedding search |
| 30 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 42 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 31 | * @returns {Promise<number[]>} - The vector for the text | 43 | * @returns {Promise<number[]>} - The vector for the text |
| 32 | */ | 44 | */ |
| 33 | async function getVector(source, sourceSettings, text, isQuery, directories) { | 45 | async function getVector(source, sourceSettings, text, isQuery, directories) { |
| 34 | switch (source) { | 46 | switch (source) { |
| 35 | case 'nomicai': | 47 | case 'nomicai': |
| 36 | return require('../vectors/nomicai-vectors').getNomicAIVector(text, source, directories); | 48 | return getNomicAIVector(text, source, directories); |
| 37 | case 'togetherai': | 49 | case 'togetherai': |
| 38 | case 'mistral': | 50 | case 'mistral': |
| 39 | case 'openai': | 51 | case 'openai': |
| 40 | return require('../vectors/openai-vectors').getOpenAIVector(text, source, directories, sourceSettings.model); | 52 | return getOpenAIVector(text, source, directories, sourceSettings.model); |
| 41 | case 'transformers': | 53 | case 'transformers': |
| 42 | return require('../vectors/embedding').getTransformersVector(text); | 54 | return getTransformersVector(text); |
| 43 | case 'extras': | 55 | case 'extras': |
| 44 | return require('../vectors/extras-vectors').getExtrasVector(text, sourceSettings.extrasUrl, sourceSettings.extrasKey); | 56 | return getExtrasVector(text, sourceSettings.extrasUrl, sourceSettings.extrasKey); |
| 45 | case 'palm': | 57 | case 'palm': |
| 46 | return require('../vectors/makersuite-vectors').getMakerSuiteVector(text, directories); | 58 | return getMakerSuiteVector(text, directories); |
| 47 | case 'cohere': | 59 | case 'cohere': |
| 48 | return require('../vectors/cohere-vectors').getCohereVector(text, isQuery, directories, sourceSettings.model); | 60 | return getCohereVector(text, isQuery, directories, sourceSettings.model); |
| 49 | case 'llamacpp': | 61 | case 'llamacpp': |
| 50 | return require('../vectors/llamacpp-vectors').getLlamaCppVector(text, sourceSettings.apiUrl, directories); | 62 | return getLlamaCppVector(text, sourceSettings.apiUrl, directories); |
| 51 | case 'vllm': | 63 | case 'vllm': |
| 52 | return require('../vectors/vllm-vectors').getVllmVector(text, sourceSettings.apiUrl, sourceSettings.model, directories); | 64 | return getVllmVector(text, sourceSettings.apiUrl, sourceSettings.model, directories); |
| 53 | case 'ollama': | 65 | case 'ollama': |
| 54 | return require('../vectors/ollama-vectors').getOllamaVector(text, sourceSettings.apiUrl, sourceSettings.model, sourceSettings.keep, directories); | 66 | return getOllamaVector(text, sourceSettings.apiUrl, sourceSettings.model, sourceSettings.keep, directories); |
| 55 | } | 67 | } |
| 56 | 68 | ||
| 57 | throw new Error(`Unknown vector source ${source}`); | 69 | throw new Error(`Unknown vector source ${source}`); |
| @@ -63,7 +75,7 @@ async function getVector(source, sourceSettings, text, isQuery, directories) { | |||
| 63 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 75 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| 64 | * @param {string[]} texts - The array of texts to get the vector for | 76 | * @param {string[]} texts - The array of texts to get the vector for |
| 65 | * @param {boolean} isQuery - If the text is a query for embedding search | 77 | * @param {boolean} isQuery - If the text is a query for embedding search |
| 66 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 78 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 67 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 79 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 68 | */ | 80 | */ |
| 69 | async function getBatchVector(source, sourceSettings, texts, isQuery, directories) { | 81 | async function getBatchVector(source, sourceSettings, texts, isQuery, directories) { |
| @@ -74,33 +86,33 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie | |||
| 74 | for (let batch of batches) { | 86 | for (let batch of batches) { |
| 75 | switch (source) { | 87 | switch (source) { |
| 76 | case 'nomicai': | 88 | case 'nomicai': |
| 77 | results.push(...await require('../vectors/nomicai-vectors').getNomicAIBatchVector(batch, source, directories)); | 89 | results.push(...await getNomicAIBatchVector(batch, source, directories)); |
| 78 | break; | 90 | break; |
| 79 | case 'togetherai': | 91 | case 'togetherai': |
| 80 | case 'mistral': | 92 | case 'mistral': |
| 81 | case 'openai': | 93 | case 'openai': |
| 82 | results.push(...await require('../vectors/openai-vectors').getOpenAIBatchVector(batch, source, directories, sourceSettings.model)); | 94 | results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model)); |
| 83 | break; | 95 | break; |
| 84 | case 'transformers': | 96 | case 'transformers': |
| 85 | results.push(...await require('../vectors/embedding').getTransformersBatchVector(batch)); | 97 | results.push(...await getTransformersBatchVector(batch)); |
| 86 | break; | 98 | break; |
| 87 | case 'extras': | 99 | case 'extras': |
| 88 | results.push(...await require('../vectors/extras-vectors').getExtrasBatchVector(batch, sourceSettings.extrasUrl, sourceSettings.extrasKey)); | 100 | results.push(...await getExtrasBatchVector(batch, sourceSettings.extrasUrl, sourceSettings.extrasKey)); |
| 89 | break; | 101 | break; |
| 90 | case 'palm': | 102 | case 'palm': |
| 91 | results.push(...await require('../vectors/makersuite-vectors').getMakerSuiteBatchVector(batch, directories)); | 103 | results.push(...await getMakerSuiteBatchVector(batch, directories)); |
| 92 | break; | 104 | break; |
| 93 | case 'cohere': | 105 | case 'cohere': |
| 94 | results.push(...await require('../vectors/cohere-vectors').getCohereBatchVector(batch, isQuery, directories, sourceSettings.model)); | 106 | results.push(...await getCohereBatchVector(batch, isQuery, directories, sourceSettings.model)); |
| 95 | break; | 107 | break; |
| 96 | case 'llamacpp': | 108 | case 'llamacpp': |
| 97 | results.push(...await require('../vectors/llamacpp-vectors').getLlamaCppBatchVector(batch, sourceSettings.apiUrl, directories)); | 109 | results.push(...await getLlamaCppBatchVector(batch, sourceSettings.apiUrl, directories)); |
| 98 | break; | 110 | break; |
| 99 | case 'vllm': | 111 | case 'vllm': |
| 100 | results.push(...await require('../vectors/vllm-vectors').getVllmBatchVector(batch, sourceSettings.apiUrl, sourceSettings.model, directories)); | 112 | results.push(...await getVllmBatchVector(batch, sourceSettings.apiUrl, sourceSettings.model, directories)); |
| 101 | break; | 113 | break; |
| 102 | case 'ollama': | 114 | case 'ollama': |
| 103 | results.push(...await require('../vectors/ollama-vectors').getOllamaBatchVector(batch, sourceSettings.apiUrl, sourceSettings.model, sourceSettings.keep, directories)); | 115 | results.push(...await getOllamaBatchVector(batch, sourceSettings.apiUrl, sourceSettings.model, sourceSettings.keep, directories)); |
| 104 | break; | 116 | break; |
| 105 | default: | 117 | default: |
| 106 | throw new Error(`Unknown vector source ${source}`); | 118 | throw new Error(`Unknown vector source ${source}`); |
| @@ -183,7 +195,7 @@ function getModelScope(sourceSettings) { | |||
| 183 | 195 | ||
| 184 | /** | 196 | /** |
| 185 | * Gets the index for the vector collection | 197 | * Gets the index for the vector collection |
| 186 | * @param {import('../users').UserDirectoryList} directories - User directories | 198 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 187 | * @param {string} collectionId - The collection ID | 199 | * @param {string} collectionId - The collection ID |
| 188 | * @param {string} source - The source of the vector | 200 | * @param {string} source - The source of the vector |
| 189 | * @param {object} sourceSettings - The model for the source | 201 | * @param {object} sourceSettings - The model for the source |
| @@ -203,7 +215,7 @@ async function getIndex(directories, collectionId, source, sourceSettings) { | |||
| 203 | 215 | ||
| 204 | /** | 216 | /** |
| 205 | * Inserts items into the vector collection | 217 | * Inserts items into the vector collection |
| 206 | * @param {import('../users').UserDirectoryList} directories - User directories | 218 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 207 | * @param {string} collectionId - The collection ID | 219 | * @param {string} collectionId - The collection ID |
| 208 | * @param {string} source - The source of the vector | 220 | * @param {string} source - The source of the vector |
| 209 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 221 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| @@ -227,7 +239,7 @@ async function insertVectorItems(directories, collectionId, source, sourceSettin | |||
| 227 | 239 | ||
| 228 | /** | 240 | /** |
| 229 | * Gets the hashes of the items in the vector collection | 241 | * Gets the hashes of the items in the vector collection |
| 230 | * @param {import('../users').UserDirectoryList} directories - User directories | 242 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 231 | * @param {string} collectionId - The collection ID | 243 | * @param {string} collectionId - The collection ID |
| 232 | * @param {string} source - The source of the vector | 244 | * @param {string} source - The source of the vector |
| 233 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 245 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| @@ -244,7 +256,7 @@ async function getSavedHashes(directories, collectionId, source, sourceSettings) | |||
| 244 | 256 | ||
| 245 | /** | 257 | /** |
| 246 | * Deletes items from the vector collection by hash | 258 | * Deletes items from the vector collection by hash |
| 247 | * @param {import('../users').UserDirectoryList} directories - User directories | 259 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 248 | * @param {string} collectionId - The collection ID | 260 | * @param {string} collectionId - The collection ID |
| 249 | * @param {string} source - The source of the vector | 261 | * @param {string} source - The source of the vector |
| 250 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 262 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| @@ -265,7 +277,7 @@ async function deleteVectorItems(directories, collectionId, source, sourceSettin | |||
| 265 | 277 | ||
| 266 | /** | 278 | /** |
| 267 | * Gets the hashes of the items in the vector collection that match the search text | 279 | * Gets the hashes of the items in the vector collection that match the search text |
| 268 | * @param {import('../users').UserDirectoryList} directories - User directories | 280 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 269 | * @param {string} collectionId - The collection ID | 281 | * @param {string} collectionId - The collection ID |
| 270 | * @param {string} source - The source of the vector | 282 | * @param {string} source - The source of the vector |
| 271 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 283 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| @@ -286,7 +298,7 @@ async function queryCollection(directories, collectionId, source, sourceSettings | |||
| 286 | 298 | ||
| 287 | /** | 299 | /** |
| 288 | * Queries multiple collections for the given search queries. Returns the overall top K results. | 300 | * Queries multiple collections for the given search queries. Returns the overall top K results. |
| 289 | * @param {import('../users').UserDirectoryList} directories - User directories | 301 | * @param {import('../users.js').UserDirectoryList} directories - User directories |
| 290 | * @param {string[]} collectionIds - The collection IDs to query | 302 | * @param {string[]} collectionIds - The collection IDs to query |
| 291 | * @param {string} source - The source of the vector | 303 | * @param {string} source - The source of the vector |
| 292 | * @param {Object} sourceSettings - Settings for the source, if it needs any | 304 | * @param {Object} sourceSettings - Settings for the source, if it needs any |
| @@ -359,7 +371,7 @@ async function regenerateCorruptedIndexErrorHandler(req, res, error) { | |||
| 359 | return res.sendStatus(500); | 371 | return res.sendStatus(500); |
| 360 | } | 372 | } |
| 361 | 373 | ||
| 362 | const router = express.Router(); | 374 | export const router = express.Router(); |
| 363 | 375 | ||
| 364 | router.post('/query', jsonParser, async (req, res) => { | 376 | router.post('/query', jsonParser, async (req, res) => { |
| 365 | try { | 377 | try { |
| @@ -495,5 +507,3 @@ router.post('/purge', jsonParser, async (req, res) => { | |||
| 495 | return res.sendStatus(500); | 507 | return res.sendStatus(500); |
| 496 | } | 508 | } |
| 497 | }); | 509 | }); |
| 498 | |||
| 499 | module.exports = { router }; | ||
| @@ -1,19 +1,20 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const express = require('express'); | ||
| 4 | const sanitize = require('sanitize-filename'); | ||
| 5 | const writeFileAtomicSync = require('write-file-atomic').sync; | ||
| 6 | 3 | ||
| 7 | const { jsonParser, urlencodedParser } = require('../express-common'); | 4 | import express from 'express'; |
| 5 | import sanitize from 'sanitize-filename'; | ||
| 6 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 7 | |||
| 8 | import { jsonParser, urlencodedParser } from '../express-common.js'; | ||
| 8 | 9 | ||
| 9 | /** | 10 | /** |
| 10 | * Reads a World Info file and returns its contents | 11 | * Reads a World Info file and returns its contents |
| 11 | * @param {import('../users').UserDirectoryList} directories User directories | 12 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 12 | * @param {string} worldInfoName Name of the World Info file | 13 | * @param {string} worldInfoName Name of the World Info file |
| 13 | * @param {boolean} allowDummy If true, returns an empty object if the file doesn't exist | 14 | * @param {boolean} allowDummy If true, returns an empty object if the file doesn't exist |
| 14 | * @returns {object} World Info file contents | 15 | * @returns {object} World Info file contents |
| 15 | */ | 16 | */ |
| 16 | function readWorldInfoFile(directories, worldInfoName, allowDummy) { | 17 | export function readWorldInfoFile(directories, worldInfoName, allowDummy) { |
| 17 | const dummyObject = allowDummy ? { entries: {} } : null; | 18 | const dummyObject = allowDummy ? { entries: {} } : null; |
| 18 | 19 | ||
| 19 | if (!worldInfoName) { | 20 | if (!worldInfoName) { |
| @@ -33,7 +34,7 @@ function readWorldInfoFile(directories, worldInfoName, allowDummy) { | |||
| 33 | return worldInfo; | 34 | return worldInfo; |
| 34 | } | 35 | } |
| 35 | 36 | ||
| 36 | const router = express.Router(); | 37 | export const router = express.Router(); |
| 37 | 38 | ||
| 38 | router.post('/get', jsonParser, (request, response) => { | 39 | router.post('/get', jsonParser, (request, response) => { |
| 39 | if (!request.body?.name) { | 40 | if (!request.body?.name) { |
| @@ -122,5 +123,3 @@ router.post('/edit', jsonParser, (request, response) => { | |||
| 122 | 123 | ||
| 123 | return response.send({ ok: true }); | 124 | return response.send({ ok: true }); |
| 124 | }); | 125 | }); |
| 125 | |||
| 126 | module.exports = { router, readWorldInfoFile }; | ||
| @@ -1,28 +1,27 @@ | |||
| 1 | const express = require('express'); | 1 | import express from 'express'; |
| 2 | const ipaddr = require('ipaddr.js'); | 2 | import ipaddr from 'ipaddr.js'; |
| 3 | 3 | ||
| 4 | // Instantiate parser middleware here with application-level size limits | 4 | // Instantiate parser middleware here with application-level size limits |
| 5 | const jsonParser = express.json({ limit: '200mb' }); | 5 | export const jsonParser = express.json({ limit: '200mb' }); |
| 6 | const urlencodedParser = express.urlencoded({ extended: true, limit: '200mb' }); | 6 | export const urlencodedParser = express.urlencoded({ extended: true, limit: '200mb' }); |
| 7 | 7 | ||
| 8 | /** | 8 | /** |
| 9 | * Gets the IP address of the client from the request object. | 9 | * Gets the IP address of the client from the request object. |
| 10 | * @param {import('express'.Request)} req Request object | 10 | * @param {import('express').Request} req Request object |
| 11 | * @returns {string} IP address of the client | 11 | * @returns {string} IP address of the client |
| 12 | */ | 12 | */ |
| 13 | function getIpFromRequest(req) { | 13 | export function getIpFromRequest(req) { |
| 14 | let clientIp = req.connection.remoteAddress; | 14 | let clientIp = req.socket.remoteAddress; |
| 15 | if (!clientIp) { | ||
| 16 | return 'unknown'; | ||
| 17 | } | ||
| 15 | let ip = ipaddr.parse(clientIp); | 18 | let ip = ipaddr.parse(clientIp); |
| 16 | // Check if the IP address is IPv4-mapped IPv6 address | 19 | // Check if the IP address is IPv4-mapped IPv6 address |
| 17 | if (ip.kind() === 'ipv6' && ip instanceof ipaddr.IPv6 && ip.isIPv4MappedAddress()) { | 20 | if (ip.kind() === 'ipv6' && ip instanceof ipaddr.IPv6 && ip.isIPv4MappedAddress()) { |
| 18 | const ipv4 = ip.toIPv4Address().toString(); | 21 | const ipv4 = ip.toIPv4Address().toString(); |
| 19 | clientIp = ipv4; | 22 | clientIp = ipv4; |
| 20 | } else { | 23 | } else { |
| 21 | clientIp = ip; | 24 | clientIp = ip.toString(); |
| 22 | clientIp = clientIp.toString(); | ||
| 23 | } | 25 | } |
| 24 | return clientIp; | 26 | return clientIp; |
| 25 | } | 27 | } |
| 26 | |||
| 27 | |||
| 28 | module.exports = { jsonParser, urlencodedParser, getIpFromRequest }; | ||
| @@ -2,9 +2,10 @@ | |||
| 2 | * When applied, this middleware will ensure the request contains the required header for basic authentication and only | 2 | * When applied, this middleware will ensure the request contains the required header for basic authentication and only |
| 3 | * allow access to the endpoint after successful authentication. | 3 | * allow access to the endpoint after successful authentication. |
| 4 | */ | 4 | */ |
| 5 | const { getAllUserHandles, toKey, getPasswordHash } = require('../users.js'); | 5 | import { Buffer } from 'node:buffer'; |
| 6 | const { getConfig, getConfigValue } = require('../util.js'); | 6 | import storage from 'node-persist'; |
| 7 | const storage = require('node-persist'); | 7 | import { getAllUserHandles, toKey, getPasswordHash } from '../users.js'; |
| 8 | import { getConfig, getConfigValue } from '../util.js'; | ||
| 8 | 9 | ||
| 9 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false); | 10 | const PER_USER_BASIC_AUTH = getConfigValue('perUserBasicAuth', false); |
| 10 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); | 11 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); |
| @@ -49,4 +50,4 @@ const basicAuthMiddleware = async function (request, response, callback) { | |||
| 49 | return unauthorizedResponse(response); | 50 | return unauthorizedResponse(response); |
| 50 | }; | 51 | }; |
| 51 | 52 | ||
| 52 | module.exports = basicAuthMiddleware; | 53 | export default basicAuthMiddleware; |
| @@ -1,3 +1,5 @@ | |||
| 1 | import { Buffer } from 'node:buffer'; | ||
| 2 | |||
| 1 | /** | 3 | /** |
| 2 | * Decodes a file name from Latin1 to UTF-8. | 4 | * Decodes a file name from Latin1 to UTF-8. |
| 3 | * @param {string} str Input string | 5 | * @param {string} str Input string |
| @@ -14,7 +16,7 @@ function decodeFileName(str) { | |||
| 14 | * @param {import('express').Response} _res Response | 16 | * @param {import('express').Response} _res Response |
| 15 | * @param {import('express').NextFunction} next Next middleware | 17 | * @param {import('express').NextFunction} next Next middleware |
| 16 | */ | 18 | */ |
| 17 | function multerMonkeyPatch(req, _res, next) { | 19 | export default function multerMonkeyPatch(req, _res, next) { |
| 18 | try { | 20 | try { |
| 19 | if (req.file) { | 21 | if (req.file) { |
| 20 | req.file.originalname = decodeFileName(req.file.originalname); | 22 | req.file.originalname = decodeFileName(req.file.originalname); |
| @@ -26,5 +28,3 @@ function multerMonkeyPatch(req, _res, next) { | |||
| 26 | next(); | 28 | next(); |
| 27 | } | 29 | } |
| 28 | } | 30 | } |
| 29 | |||
| 30 | module.exports = multerMonkeyPatch; | ||
| @@ -1,9 +1,10 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fs = require('fs'); | 2 | import fs from 'node:fs'; |
| 3 | const ipMatching = require('ip-matching'); | 3 | import process from 'node:process'; |
| 4 | import ipMatching from 'ip-matching'; | ||
| 4 | 5 | ||
| 5 | const { getIpFromRequest } = require('../express-common'); | 6 | import { getIpFromRequest } from '../express-common.js'; |
| 6 | const { color, getConfigValue } = require('../util'); | 7 | import { color, getConfigValue } from '../util.js'; |
| 7 | 8 | ||
| 8 | const whitelistPath = path.join(process.cwd(), './whitelist.txt'); | 9 | const whitelistPath = path.join(process.cwd(), './whitelist.txt'); |
| 9 | const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false); | 10 | const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false); |
| @@ -50,7 +51,7 @@ function getForwardedIp(req) { | |||
| 50 | * @param {boolean} listen If listen mode is enabled via config or command line | 51 | * @param {boolean} listen If listen mode is enabled via config or command line |
| 51 | * @returns {import('express').RequestHandler} The middleware function | 52 | * @returns {import('express').RequestHandler} The middleware function |
| 52 | */ | 53 | */ |
| 53 | function whitelistMiddleware(whitelistMode, listen) { | 54 | export default function whitelistMiddleware(whitelistMode, listen) { |
| 54 | return function (req, res, next) { | 55 | return function (req, res, next) { |
| 55 | const clientIp = getIpFromRequest(req); | 56 | const clientIp = getIpFromRequest(req); |
| 56 | const forwardedIp = getForwardedIp(req); | 57 | const forwardedIp = getForwardedIp(req); |
| @@ -82,5 +83,3 @@ function whitelistMiddleware(whitelistMode, listen) { | |||
| 82 | next(); | 83 | next(); |
| 83 | }; | 84 | }; |
| 84 | } | 85 | } |
| 85 | |||
| 86 | module.exports = whitelistMiddleware; | ||
| @@ -1,8 +1,9 @@ | |||
| 1 | const fs = require('fs'); | 1 | import fs from 'node:fs'; |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const url = require('url'); | 3 | import url from 'node:url'; |
| 4 | const express = require('express'); | 4 | |
| 5 | const { getConfigValue } = require('./util'); | 5 | import express from 'express'; |
| 6 | import { getConfigValue } from './util.js'; | ||
| 6 | const enableServerPlugins = getConfigValue('enableServerPlugins', false); | 7 | const enableServerPlugins = getConfigValue('enableServerPlugins', false); |
| 7 | 8 | ||
| 8 | /** | 9 | /** |
| @@ -16,7 +17,7 @@ const loadedPlugins = new Map(); | |||
| 16 | * @param {string} file Path to file | 17 | * @param {string} file Path to file |
| 17 | * @returns {boolean} True if file is a CommonJS module | 18 | * @returns {boolean} True if file is a CommonJS module |
| 18 | */ | 19 | */ |
| 19 | const isCommonJS = (file) => path.extname(file) === '.js'; | 20 | const isCommonJS = (file) => path.extname(file) === '.js' || path.extname(file) === '.cjs'; |
| 20 | 21 | ||
| 21 | /** | 22 | /** |
| 22 | * Determine if a file is an ECMAScript module. | 23 | * Determine if a file is an ECMAScript module. |
| @@ -32,9 +33,9 @@ const isESModule = (file) => path.extname(file) === '.mjs'; | |||
| 32 | * @returns {Promise<Function>} Promise that resolves when all plugins are loaded. Resolves to a "cleanup" function to | 33 | * @returns {Promise<Function>} Promise that resolves when all plugins are loaded. Resolves to a "cleanup" function to |
| 33 | * be called before the server shuts down. | 34 | * be called before the server shuts down. |
| 34 | */ | 35 | */ |
| 35 | async function loadPlugins(app, pluginsPath) { | 36 | export async function loadPlugins(app, pluginsPath) { |
| 36 | const exitHooks = []; | 37 | const exitHooks = []; |
| 37 | const emptyFn = () => {}; | 38 | const emptyFn = () => { }; |
| 38 | 39 | ||
| 39 | // Server plugins are disabled. | 40 | // Server plugins are disabled. |
| 40 | if (!enableServerPlugins) { | 41 | if (!enableServerPlugins) { |
| @@ -89,19 +90,15 @@ async function loadFromDirectory(app, pluginDirectoryPath, exitHooks) { | |||
| 89 | } | 90 | } |
| 90 | } | 91 | } |
| 91 | 92 | ||
| 92 | // Plugin is a CommonJS module. | 93 | // Plugin is a module file. |
| 93 | const cjsFilePath = path.join(pluginDirectoryPath, 'index.js'); | 94 | const fileTypes = ['index.js', 'index.cjs', 'index.mjs']; |
| 94 | if (fs.existsSync(cjsFilePath)) { | ||
| 95 | if (await loadFromFile(app, cjsFilePath, exitHooks)) { | ||
| 96 | return; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | 95 | ||
| 100 | // Plugin is an ECMAScript module. | 96 | for (const fileType of fileTypes) { |
| 101 | const esmFilePath = path.join(pluginDirectoryPath, 'index.mjs'); | 97 | const filePath = path.join(pluginDirectoryPath, fileType); |
| 102 | if (fs.existsSync(esmFilePath)) { | 98 | if (fs.existsSync(filePath)) { |
| 103 | if (await loadFromFile(app, esmFilePath, exitHooks)) { | 99 | if (await loadFromFile(app, filePath, exitHooks)) { |
| 104 | return; | 100 | return; |
| 101 | } | ||
| 105 | } | 102 | } |
| 106 | } | 103 | } |
| 107 | } | 104 | } |
| @@ -217,7 +214,3 @@ async function initPlugin(app, plugin, exitHooks) { | |||
| 217 | 214 | ||
| 218 | return true; | 215 | return true; |
| 219 | } | 216 | } |
| 220 | |||
| 221 | module.exports = { | ||
| 222 | loadPlugins, | ||
| 223 | }; | ||
| @@ -1,10 +0,0 @@ | |||
| 1 | if (!Array.prototype.findLastIndex) { | ||
| 2 | Array.prototype.findLastIndex = function (callback, thisArg) { | ||
| 3 | for (let i = this.length - 1; i >= 0; i--) { | ||
| 4 | if (callback.call(thisArg, this[i], i, this)) return i; | ||
| 5 | } | ||
| 6 | return -1; | ||
| 7 | }; | ||
| 8 | } | ||
| 9 | |||
| 10 | module.exports = {}; | ||
| @@ -1,6 +1,5 @@ | |||
| 1 | require('./polyfill.js'); | 1 | import crypto from 'node:crypto'; |
| 2 | const { getConfigValue } = require('./util.js'); | 2 | import { getConfigValue } from './util.js'; |
| 3 | const crypto = require('crypto'); | ||
| 4 | 3 | ||
| 5 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); | 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); |
| 6 | 5 | ||
| @@ -17,7 +16,7 @@ const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get start | |||
| 17 | * @returns {string} Prompt for Claude | 16 | * @returns {string} Prompt for Claude |
| 18 | * @copyright Prompt Conversion script taken from RisuAI by kwaroran (GPLv3). | 17 | * @copyright Prompt Conversion script taken from RisuAI by kwaroran (GPLv3). |
| 19 | */ | 18 | */ |
| 20 | function convertClaudePrompt(messages, addAssistantPostfix, addAssistantPrefill, withSysPromptSupport, useSystemPrompt, addSysHumanMsg, excludePrefixes) { | 19 | export function convertClaudePrompt(messages, addAssistantPostfix, addAssistantPrefill, withSysPromptSupport, useSystemPrompt, addSysHumanMsg, excludePrefixes) { |
| 21 | 20 | ||
| 22 | //Prepare messages for claude. | 21 | //Prepare messages for claude. |
| 23 | //When 'Exclude Human/Assistant prefixes' checked, setting messages role to the 'system'(last message is exception). | 22 | //When 'Exclude Human/Assistant prefixes' checked, setting messages role to the 'system'(last message is exception). |
| @@ -96,7 +95,7 @@ function convertClaudePrompt(messages, addAssistantPostfix, addAssistantPrefill, | |||
| 96 | * @param {string} charName Character name | 95 | * @param {string} charName Character name |
| 97 | * @param {string} userName User name | 96 | * @param {string} userName User name |
| 98 | */ | 97 | */ |
| 99 | function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') { | 98 | export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') { |
| 100 | let systemPrompt = []; | 99 | let systemPrompt = []; |
| 101 | if (useSysPrompt) { | 100 | if (useSysPrompt) { |
| 102 | // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array. | 101 | // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array. |
| @@ -279,7 +278,7 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, | |||
| 279 | * @param {string} userName User name | 278 | * @param {string} userName User name |
| 280 | * @returns {{chatHistory: object[]}} Prompt for Cohere | 279 | * @returns {{chatHistory: object[]}} Prompt for Cohere |
| 281 | */ | 280 | */ |
| 282 | function convertCohereMessages(messages, charName = '', userName = '') { | 281 | export function convertCohereMessages(messages, charName = '', userName = '') { |
| 283 | if (messages.length === 0) { | 282 | if (messages.length === 0) { |
| 284 | messages.unshift({ | 283 | messages.unshift({ |
| 285 | role: 'user', | 284 | role: 'user', |
| @@ -333,7 +332,7 @@ function convertCohereMessages(messages, charName = '', userName = '') { | |||
| 333 | * @param {string} userName User name | 332 | * @param {string} userName User name |
| 334 | * @returns {{contents: *[], system_instruction: {parts: {text: string}}}} Prompt for Google MakerSuite models | 333 | * @returns {{contents: *[], system_instruction: {parts: {text: string}}}} Prompt for Google MakerSuite models |
| 335 | */ | 334 | */ |
| 336 | function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '', userName = '') { | 335 | export function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '', userName = '') { |
| 337 | // This is a 1x1 transparent PNG | 336 | // This is a 1x1 transparent PNG |
| 338 | const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; | 337 | const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; |
| 339 | 338 | ||
| @@ -456,7 +455,7 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = ' | |||
| 456 | * @param {string} charName Character name | 455 | * @param {string} charName Character name |
| 457 | * @param {string} userName User name | 456 | * @param {string} userName User name |
| 458 | */ | 457 | */ |
| 459 | function convertAI21Messages(messages, charName = '', userName = '') { | 458 | export function convertAI21Messages(messages, charName = '', userName = '') { |
| 460 | if (!Array.isArray(messages)) { | 459 | if (!Array.isArray(messages)) { |
| 461 | return []; | 460 | return []; |
| 462 | } | 461 | } |
| @@ -528,7 +527,7 @@ function convertAI21Messages(messages, charName = '', userName = '') { | |||
| 528 | * @param {string} charName Character name | 527 | * @param {string} charName Character name |
| 529 | * @param {string} userName User name | 528 | * @param {string} userName User name |
| 530 | */ | 529 | */ |
| 531 | function convertMistralMessages(messages, charName = '', userName = '') { | 530 | export function convertMistralMessages(messages, charName = '', userName = '') { |
| 532 | if (!Array.isArray(messages)) { | 531 | if (!Array.isArray(messages)) { |
| 533 | return []; | 532 | return []; |
| 534 | } | 533 | } |
| @@ -612,7 +611,7 @@ function convertMistralMessages(messages, charName = '', userName = '') { | |||
| 612 | * @param {boolean} strict Enable strict mode: only allow one system message at the start, force user first message | 611 | * @param {boolean} strict Enable strict mode: only allow one system message at the start, force user first message |
| 613 | * @returns {any[]} Merged messages | 612 | * @returns {any[]} Merged messages |
| 614 | */ | 613 | */ |
| 615 | function mergeMessages(messages, charName, userName, strict) { | 614 | export function mergeMessages(messages, charName, userName, strict) { |
| 616 | let mergedMessages = []; | 615 | let mergedMessages = []; |
| 617 | 616 | ||
| 618 | // Remove names from the messages | 617 | // Remove names from the messages |
| @@ -686,7 +685,7 @@ function mergeMessages(messages, charName, userName, strict) { | |||
| 686 | * @param {object[]} messages Array of messages | 685 | * @param {object[]} messages Array of messages |
| 687 | * @returns {string} Prompt for Text Completion API | 686 | * @returns {string} Prompt for Text Completion API |
| 688 | */ | 687 | */ |
| 689 | function convertTextCompletionPrompt(messages) { | 688 | export function convertTextCompletionPrompt(messages) { |
| 690 | if (typeof messages === 'string') { | 689 | if (typeof messages === 'string') { |
| 691 | return messages; | 690 | return messages; |
| 692 | } | 691 | } |
| @@ -705,14 +704,3 @@ function convertTextCompletionPrompt(messages) { | |||
| 705 | }); | 704 | }); |
| 706 | return messageStrings.join('\n') + '\nassistant:'; | 705 | return messageStrings.join('\n') + '\nassistant:'; |
| 707 | } | 706 | } |
| 708 | |||
| 709 | module.exports = { | ||
| 710 | convertClaudePrompt, | ||
| 711 | convertClaudeMessages, | ||
| 712 | convertGooglePrompt, | ||
| 713 | convertTextCompletionPrompt, | ||
| 714 | convertCohereMessages, | ||
| 715 | convertMistralMessages, | ||
| 716 | convertAI21Messages, | ||
| 717 | mergeMessages, | ||
| 718 | }; | ||
| @@ -1,7 +1,8 @@ | |||
| 1 | const http = require('node:http'); | 1 | import process from 'node:process'; |
| 2 | const https = require('node:https'); | 2 | import http from 'node:http'; |
| 3 | 3 | import https from 'node:https'; | |
| 4 | const { isValidUrl, color } = require('./util.js'); | 4 | import { ProxyAgent } from 'proxy-agent'; |
| 5 | import { isValidUrl, color } from './util.js'; | ||
| 5 | 6 | ||
| 6 | const LOG_HEADER = '[Request Proxy]'; | 7 | const LOG_HEADER = '[Request Proxy]'; |
| 7 | 8 | ||
| @@ -13,10 +14,8 @@ const LOG_HEADER = '[Request Proxy]'; | |||
| 13 | * @property {string} url Proxy URL. | 14 | * @property {string} url Proxy URL. |
| 14 | * @property {string[]} bypass List of URLs to bypass proxy. | 15 | * @property {string[]} bypass List of URLs to bypass proxy. |
| 15 | */ | 16 | */ |
| 16 | function initRequestProxy({ enabled, url, bypass }) { | 17 | export default function initRequestProxy({ enabled, url, bypass }) { |
| 17 | try { | 18 | try { |
| 18 | const { ProxyAgent } = require('proxy-agent'); | ||
| 19 | |||
| 20 | // No proxy is enabled, so return | 19 | // No proxy is enabled, so return |
| 21 | if (!enabled) { | 20 | if (!enabled) { |
| 22 | return; | 21 | return; |
| @@ -36,7 +35,6 @@ function initRequestProxy({ enabled, url, bypass }) { | |||
| 36 | // Reference: https://github.com/Rob--W/proxy-from-env | 35 | // Reference: https://github.com/Rob--W/proxy-from-env |
| 37 | process.env.all_proxy = url; | 36 | process.env.all_proxy = url; |
| 38 | 37 | ||
| 39 | |||
| 40 | if (Array.isArray(bypass) && bypass.length > 0) { | 38 | if (Array.isArray(bypass) && bypass.length > 0) { |
| 41 | process.env.no_proxy = bypass.join(','); | 39 | process.env.no_proxy = bypass.join(','); |
| 42 | } | 40 | } |
| @@ -52,5 +50,3 @@ function initRequestProxy({ enabled, url, bypass }) { | |||
| 52 | console.error(color.red(LOG_HEADER), 'Failed to initialize request proxy:', error); | 50 | console.error(color.red(LOG_HEADER), 'Failed to initialize request proxy:', error); |
| 53 | } | 51 | } |
| 54 | } | 52 | } |
| 55 | |||
| 56 | module.exports = initRequestProxy; | ||
| @@ -1,7 +1,10 @@ | |||
| 1 | import { pipeline, env, RawImage, Pipeline } from 'sillytavern-transformers'; | 1 | import path from 'node:path'; |
| 2 | import fs from 'node:fs'; | ||
| 3 | import process from 'node:process'; | ||
| 4 | import { Buffer } from 'node:buffer'; | ||
| 5 | |||
| 6 | import { pipeline, env, RawImage } from 'sillytavern-transformers'; | ||
| 2 | import { getConfigValue } from './util.js'; | 7 | import { getConfigValue } from './util.js'; |
| 3 | import path from 'path'; | ||
| 4 | import fs from 'fs'; | ||
| 5 | 8 | ||
| 6 | configureTransformers(); | 9 | configureTransformers(); |
| 7 | 10 | ||
| @@ -50,7 +53,7 @@ const tasks = { | |||
| 50 | * @param {string} image Base64-encoded image | 53 | * @param {string} image Base64-encoded image |
| 51 | * @returns {Promise<RawImage|null>} Object representing the image | 54 | * @returns {Promise<RawImage|null>} Object representing the image |
| 52 | */ | 55 | */ |
| 53 | async function getRawImage(image) { | 56 | export async function getRawImage(image) { |
| 54 | try { | 57 | try { |
| 55 | const buffer = Buffer.from(image, 'base64'); | 58 | const buffer = Buffer.from(image, 'base64'); |
| 56 | const byteArray = new Uint8Array(buffer); | 59 | const byteArray = new Uint8Array(buffer); |
| @@ -114,9 +117,9 @@ async function migrateCacheToDataDir() { | |||
| 114 | * Gets the transformers.js pipeline for a given task. | 117 | * Gets the transformers.js pipeline for a given task. |
| 115 | * @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for | 118 | * @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for |
| 116 | * @param {string} forceModel The model to use for the pipeline, if any | 119 | * @param {string} forceModel The model to use for the pipeline, if any |
| 117 | * @returns {Promise<Pipeline>} Pipeline for the task | 120 | * @returns {Promise<import('sillytavern-transformers').Pipeline>} The transformers.js pipeline |
| 118 | */ | 121 | */ |
| 119 | async function getPipeline(task, forceModel = '') { | 122 | export async function getPipeline(task, forceModel = '') { |
| 120 | await migrateCacheToDataDir(); | 123 | await migrateCacheToDataDir(); |
| 121 | 124 | ||
| 122 | if (tasks[task].pipeline) { | 125 | if (tasks[task].pipeline) { |
| @@ -134,10 +137,11 @@ async function getPipeline(task, forceModel = '') { | |||
| 134 | const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly }); | 137 | const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly }); |
| 135 | tasks[task].pipeline = instance; | 138 | tasks[task].pipeline = instance; |
| 136 | tasks[task].currentModel = model; | 139 | tasks[task].currentModel = model; |
| 140 | // @ts-ignore | ||
| 137 | return instance; | 141 | return instance; |
| 138 | } | 142 | } |
| 139 | 143 | ||
| 140 | export default { | 144 | export default { |
| 141 | getPipeline, | ||
| 142 | getRawImage, | 145 | getRawImage, |
| 146 | getPipeline, | ||
| 143 | }; | 147 | }; |
| @@ -1,22 +1,25 @@ | |||
| 1 | // Native Node Modules | 1 | // Native Node Modules |
| 2 | const path = require('path'); | 2 | import path from 'node:path'; |
| 3 | const fs = require('fs'); | 3 | import fs from 'node:fs'; |
| 4 | const crypto = require('crypto'); | 4 | import crypto from 'node:crypto'; |
| 5 | const os = require('os'); | 5 | import os from 'node:os'; |
| 6 | import process from 'node:process'; | ||
| 7 | import { Buffer } from 'node:buffer'; | ||
| 6 | 8 | ||
| 7 | // Express and other dependencies | 9 | // Express and other dependencies |
| 8 | const storage = require('node-persist'); | 10 | import storage from 'node-persist'; |
| 9 | const express = require('express'); | 11 | import express from 'express'; |
| 10 | const mime = require('mime-types'); | 12 | import mime from 'mime-types'; |
| 11 | const archiver = require('archiver'); | 13 | import archiver from 'archiver'; |
| 12 | const writeFileAtomicSync = require('write-file-atomic').sync; | 14 | import _ from 'lodash'; |
| 13 | const _ = require('lodash'); | 15 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| 14 | 16 | ||
| 15 | const { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } = require('./constants'); | 17 | import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE } from './constants.js'; |
| 16 | const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = require('./util'); | 18 | import { getConfigValue, color, delay, setConfigValue, generateTimestamp } from './util.js'; |
| 17 | const { readSecret, writeSecret } = require('./endpoints/secrets'); | 19 | import { readSecret, writeSecret } from './endpoints/secrets.js'; |
| 18 | 20 | import { getContentOfType } from './endpoints/content-manager.js'; | |
| 19 | const KEY_PREFIX = 'user:'; | 21 | |
| 22 | export const KEY_PREFIX = 'user:'; | ||
| 20 | const AVATAR_PREFIX = 'avatar:'; | 23 | const AVATAR_PREFIX = 'avatar:'; |
| 21 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); | 24 | const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false); |
| 22 | const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false); | 25 | const AUTHELIA_AUTH = getConfigValue('autheliaAuth', false); |
| @@ -92,9 +95,9 @@ const STORAGE_KEYS = { | |||
| 92 | 95 | ||
| 93 | /** | 96 | /** |
| 94 | * Ensures that the content directories exist. | 97 | * Ensures that the content directories exist. |
| 95 | * @returns {Promise<import('./users').UserDirectoryList[]>} - The list of user directories | 98 | * @returns {Promise<import('./users.js').UserDirectoryList[]>} - The list of user directories |
| 96 | */ | 99 | */ |
| 97 | async function ensurePublicDirectoriesExist() { | 100 | export async function ensurePublicDirectoriesExist() { |
| 98 | for (const dir of Object.values(PUBLIC_DIRECTORIES)) { | 101 | for (const dir of Object.values(PUBLIC_DIRECTORIES)) { |
| 99 | if (!fs.existsSync(dir)) { | 102 | if (!fs.existsSync(dir)) { |
| 100 | fs.mkdirSync(dir, { recursive: true }); | 103 | fs.mkdirSync(dir, { recursive: true }); |
| @@ -115,9 +118,9 @@ async function ensurePublicDirectoriesExist() { | |||
| 115 | 118 | ||
| 116 | /** | 119 | /** |
| 117 | * Gets a list of all user directories. | 120 | * Gets a list of all user directories. |
| 118 | * @returns {Promise<import('./users').UserDirectoryList[]>} - The list of user directories | 121 | * @returns {Promise<import('./users.js').UserDirectoryList[]>} - The list of user directories |
| 119 | */ | 122 | */ |
| 120 | async function getUserDirectoriesList() { | 123 | export async function getUserDirectoriesList() { |
| 121 | const userHandles = await getAllUserHandles(); | 124 | const userHandles = await getAllUserHandles(); |
| 122 | const directoriesList = userHandles.map(handle => getUserDirectories(handle)); | 125 | const directoriesList = userHandles.map(handle => getUserDirectories(handle)); |
| 123 | return directoriesList; | 126 | return directoriesList; |
| @@ -126,7 +129,7 @@ async function getUserDirectoriesList() { | |||
| 126 | /** | 129 | /** |
| 127 | * Perform migration from the old user data format to the new one. | 130 | * Perform migration from the old user data format to the new one. |
| 128 | */ | 131 | */ |
| 129 | async function migrateUserData() { | 132 | export async function migrateUserData() { |
| 130 | const publicDirectory = path.join(process.cwd(), 'public'); | 133 | const publicDirectory = path.join(process.cwd(), 'public'); |
| 131 | 134 | ||
| 132 | // No need to migrate if the characters directory doesn't exists | 135 | // No need to migrate if the characters directory doesn't exists |
| @@ -328,14 +331,13 @@ async function migrateUserData() { | |||
| 328 | console.log(color.green('Migration completed!')); | 331 | console.log(color.green('Migration completed!')); |
| 329 | } | 332 | } |
| 330 | 333 | ||
| 331 | async function migrateSystemPrompts() { | 334 | export async function migrateSystemPrompts() { |
| 332 | /** | 335 | /** |
| 333 | * Gets the default system prompts. | 336 | * Gets the default system prompts. |
| 334 | * @returns {Promise<any[]>} - The list of default system prompts | 337 | * @returns {Promise<any[]>} - The list of default system prompts |
| 335 | */ | 338 | */ |
| 336 | async function getDefaultSystemPrompts() { | 339 | async function getDefaultSystemPrompts() { |
| 337 | try { | 340 | try { |
| 338 | const { getContentOfType } = await import('./endpoints/content-manager.js'); | ||
| 339 | return getContentOfType('sysprompt', 'json'); | 341 | return getContentOfType('sysprompt', 'json'); |
| 340 | } catch { | 342 | } catch { |
| 341 | return []; | 343 | return []; |
| @@ -391,7 +393,7 @@ async function migrateSystemPrompts() { | |||
| 391 | * @param {string} handle User handle | 393 | * @param {string} handle User handle |
| 392 | * @returns {string} The key for the user storage | 394 | * @returns {string} The key for the user storage |
| 393 | */ | 395 | */ |
| 394 | function toKey(handle) { | 396 | export function toKey(handle) { |
| 395 | return `${KEY_PREFIX}${handle}`; | 397 | return `${KEY_PREFIX}${handle}`; |
| 396 | } | 398 | } |
| 397 | 399 | ||
| @@ -400,7 +402,7 @@ function toKey(handle) { | |||
| 400 | * @param {string} handle User handle | 402 | * @param {string} handle User handle |
| 401 | * @returns {string} The key for the avatar storage | 403 | * @returns {string} The key for the avatar storage |
| 402 | */ | 404 | */ |
| 403 | function toAvatarKey(handle) { | 405 | export function toAvatarKey(handle) { |
| 404 | return `${AVATAR_PREFIX}${handle}`; | 406 | return `${AVATAR_PREFIX}${handle}`; |
| 405 | } | 407 | } |
| 406 | 408 | ||
| @@ -409,7 +411,7 @@ function toAvatarKey(handle) { | |||
| 409 | * @param {string} dataRoot The root directory for user data | 411 | * @param {string} dataRoot The root directory for user data |
| 410 | * @returns {Promise<void>} | 412 | * @returns {Promise<void>} |
| 411 | */ | 413 | */ |
| 412 | async function initUserStorage(dataRoot) { | 414 | export async function initUserStorage(dataRoot) { |
| 413 | global.DATA_ROOT = dataRoot; | 415 | global.DATA_ROOT = dataRoot; |
| 414 | console.log('Using data root:', color.green(global.DATA_ROOT)); | 416 | console.log('Using data root:', color.green(global.DATA_ROOT)); |
| 415 | console.log(); | 417 | console.log(); |
| @@ -430,7 +432,7 @@ async function initUserStorage(dataRoot) { | |||
| 430 | * Get the cookie secret from the config. If it doesn't exist, generate a new one. | 432 | * Get the cookie secret from the config. If it doesn't exist, generate a new one. |
| 431 | * @returns {string} The cookie secret | 433 | * @returns {string} The cookie secret |
| 432 | */ | 434 | */ |
| 433 | function getCookieSecret() { | 435 | export function getCookieSecret() { |
| 434 | let secret = getConfigValue(STORAGE_KEYS.cookieSecret); | 436 | let secret = getConfigValue(STORAGE_KEYS.cookieSecret); |
| 435 | 437 | ||
| 436 | if (!secret) { | 438 | if (!secret) { |
| @@ -446,7 +448,7 @@ function getCookieSecret() { | |||
| 446 | * Generates a random password salt. | 448 | * Generates a random password salt. |
| 447 | * @returns {string} The password salt | 449 | * @returns {string} The password salt |
| 448 | */ | 450 | */ |
| 449 | function getPasswordSalt() { | 451 | export function getPasswordSalt() { |
| 450 | return crypto.randomBytes(16).toString('base64'); | 452 | return crypto.randomBytes(16).toString('base64'); |
| 451 | } | 453 | } |
| 452 | 454 | ||
| @@ -454,7 +456,7 @@ function getPasswordSalt() { | |||
| 454 | * Get the session name for the current server. | 456 | * Get the session name for the current server. |
| 455 | * @returns {string} The session name | 457 | * @returns {string} The session name |
| 456 | */ | 458 | */ |
| 457 | function getCookieSessionName() { | 459 | export function getCookieSessionName() { |
| 458 | // Get server hostname and hash it to generate a session suffix | 460 | // Get server hostname and hash it to generate a session suffix |
| 459 | const suffix = crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 8); | 461 | const suffix = crypto.createHash('sha256').update(os.hostname()).digest('hex').slice(0, 8); |
| 460 | return `session-${suffix}`; | 462 | return `session-${suffix}`; |
| @@ -466,7 +468,7 @@ function getCookieSessionName() { | |||
| 466 | * @param {string} salt Salt to use for hashing | 468 | * @param {string} salt Salt to use for hashing |
| 467 | * @returns {string} Hashed password | 469 | * @returns {string} Hashed password |
| 468 | */ | 470 | */ |
| 469 | function getPasswordHash(password, salt) { | 471 | export function getPasswordHash(password, salt) { |
| 470 | return crypto.scryptSync(password.normalize(), salt, 64).toString('base64'); | 472 | return crypto.scryptSync(password.normalize(), salt, 64).toString('base64'); |
| 471 | } | 473 | } |
| 472 | 474 | ||
| @@ -475,7 +477,7 @@ function getPasswordHash(password, salt) { | |||
| 475 | * @param {import('express').Request} [request] HTTP request object | 477 | * @param {import('express').Request} [request] HTTP request object |
| 476 | * @returns {string} The CSRF secret | 478 | * @returns {string} The CSRF secret |
| 477 | */ | 479 | */ |
| 478 | function getCsrfSecret(request) { | 480 | export function getCsrfSecret(request) { |
| 479 | if (!request || !request.user) { | 481 | if (!request || !request.user) { |
| 480 | return ANON_CSRF_SECRET; | 482 | return ANON_CSRF_SECRET; |
| 481 | } | 483 | } |
| @@ -494,7 +496,7 @@ function getCsrfSecret(request) { | |||
| 494 | * Gets a list of all user handles. | 496 | * Gets a list of all user handles. |
| 495 | * @returns {Promise<string[]>} - The list of user handles | 497 | * @returns {Promise<string[]>} - The list of user handles |
| 496 | */ | 498 | */ |
| 497 | async function getAllUserHandles() { | 499 | export async function getAllUserHandles() { |
| 498 | const keys = await storage.keys(x => x.key.startsWith(KEY_PREFIX)); | 500 | const keys = await storage.keys(x => x.key.startsWith(KEY_PREFIX)); |
| 499 | const handles = keys.map(x => x.replace(KEY_PREFIX, '')); | 501 | const handles = keys.map(x => x.replace(KEY_PREFIX, '')); |
| 500 | return handles; | 502 | return handles; |
| @@ -505,7 +507,7 @@ async function getAllUserHandles() { | |||
| 505 | * @param {string} handle User handle | 507 | * @param {string} handle User handle |
| 506 | * @returns {UserDirectoryList} User directories | 508 | * @returns {UserDirectoryList} User directories |
| 507 | */ | 509 | */ |
| 508 | function getUserDirectories(handle) { | 510 | export function getUserDirectories(handle) { |
| 509 | if (DIRECTORIES_CACHE.has(handle)) { | 511 | if (DIRECTORIES_CACHE.has(handle)) { |
| 510 | const cache = DIRECTORIES_CACHE.get(handle); | 512 | const cache = DIRECTORIES_CACHE.get(handle); |
| 511 | if (cache) { | 513 | if (cache) { |
| @@ -526,7 +528,7 @@ function getUserDirectories(handle) { | |||
| 526 | * @param {string} handle User handle | 528 | * @param {string} handle User handle |
| 527 | * @returns {Promise<string>} User avatar URL | 529 | * @returns {Promise<string>} User avatar URL |
| 528 | */ | 530 | */ |
| 529 | async function getUserAvatar(handle) { | 531 | export async function getUserAvatar(handle) { |
| 530 | try { | 532 | try { |
| 531 | // Check if the user has a custom avatar | 533 | // Check if the user has a custom avatar |
| 532 | const avatarKey = toAvatarKey(handle); | 534 | const avatarKey = toAvatarKey(handle); |
| @@ -563,7 +565,7 @@ async function getUserAvatar(handle) { | |||
| 563 | * @param {import('express').Request} request Request object | 565 | * @param {import('express').Request} request Request object |
| 564 | * @returns {boolean} Whether the user should be redirected to the login page | 566 | * @returns {boolean} Whether the user should be redirected to the login page |
| 565 | */ | 567 | */ |
| 566 | function shouldRedirectToLogin(request) { | 568 | export function shouldRedirectToLogin(request) { |
| 567 | return ENABLE_ACCOUNTS && !request.user; | 569 | return ENABLE_ACCOUNTS && !request.user; |
| 568 | } | 570 | } |
| 569 | 571 | ||
| @@ -574,7 +576,7 @@ function shouldRedirectToLogin(request) { | |||
| 574 | * @param {boolean} basicAuthMode If Basic auth mode is enabled | 576 | * @param {boolean} basicAuthMode If Basic auth mode is enabled |
| 575 | * @returns {Promise<boolean>} Whether auto-login was performed | 577 | * @returns {Promise<boolean>} Whether auto-login was performed |
| 576 | */ | 578 | */ |
| 577 | async function tryAutoLogin(request, basicAuthMode) { | 579 | export async function tryAutoLogin(request, basicAuthMode) { |
| 578 | if (!ENABLE_ACCOUNTS || request.user || !request.session) { | 580 | if (!ENABLE_ACCOUNTS || request.user || !request.session) { |
| 579 | return false; | 581 | return false; |
| 580 | } | 582 | } |
| @@ -693,7 +695,7 @@ async function basicUserLogin(request) { | |||
| 693 | * @param {import('express').Response} response Response object | 695 | * @param {import('express').Response} response Response object |
| 694 | * @param {import('express').NextFunction} next Next function | 696 | * @param {import('express').NextFunction} next Next function |
| 695 | */ | 697 | */ |
| 696 | async function setUserDataMiddleware(request, response, next) { | 698 | export async function setUserDataMiddleware(request, response, next) { |
| 697 | // If user accounts are disabled, use the default user | 699 | // If user accounts are disabled, use the default user |
| 698 | if (!ENABLE_ACCOUNTS) { | 700 | if (!ENABLE_ACCOUNTS) { |
| 699 | const handle = DEFAULT_USER.handle; | 701 | const handle = DEFAULT_USER.handle; |
| @@ -751,7 +753,7 @@ async function setUserDataMiddleware(request, response, next) { | |||
| 751 | * @param {import('express').Response} response Response object | 753 | * @param {import('express').Response} response Response object |
| 752 | * @param {import('express').NextFunction} next Next function | 754 | * @param {import('express').NextFunction} next Next function |
| 753 | */ | 755 | */ |
| 754 | function requireLoginMiddleware(request, response, next) { | 756 | export function requireLoginMiddleware(request, response, next) { |
| 755 | if (!request.user) { | 757 | if (!request.user) { |
| 756 | return response.sendStatus(403); | 758 | return response.sendStatus(403); |
| 757 | } | 759 | } |
| @@ -787,7 +789,7 @@ function createRouteHandler(directoryFn) { | |||
| 787 | * @param {import('express').NextFunction} next Next function | 789 | * @param {import('express').NextFunction} next Next function |
| 788 | * @returns {any} | 790 | * @returns {any} |
| 789 | */ | 791 | */ |
| 790 | function requireAdminMiddleware(request, response, next) { | 792 | export function requireAdminMiddleware(request, response, next) { |
| 791 | if (!request.user) { | 793 | if (!request.user) { |
| 792 | return response.sendStatus(403); | 794 | return response.sendStatus(403); |
| 793 | } | 795 | } |
| @@ -806,7 +808,7 @@ function requireAdminMiddleware(request, response, next) { | |||
| 806 | * @param {import('express').Response} response Express response object to write to | 808 | * @param {import('express').Response} response Express response object to write to |
| 807 | * @returns {Promise<void>} Promise that resolves when the archive is created | 809 | * @returns {Promise<void>} Promise that resolves when the archive is created |
| 808 | */ | 810 | */ |
| 809 | async function createBackupArchive(handle, response) { | 811 | export async function createBackupArchive(handle, response) { |
| 810 | const directories = getUserDirectories(handle); | 812 | const directories = getUserDirectories(handle); |
| 811 | 813 | ||
| 812 | console.log('Backup requested for', handle); | 814 | console.log('Backup requested for', handle); |
| @@ -855,7 +857,7 @@ async function getAllUsers() { | |||
| 855 | * Gets all of the enabled users. | 857 | * Gets all of the enabled users. |
| 856 | * @returns {Promise<User[]>} | 858 | * @returns {Promise<User[]>} |
| 857 | */ | 859 | */ |
| 858 | async function getAllEnabledUsers() { | 860 | export async function getAllEnabledUsers() { |
| 859 | const users = await getAllUsers(); | 861 | const users = await getAllUsers(); |
| 860 | return users.filter(x => x.enabled); | 862 | return users.filter(x => x.enabled); |
| 861 | } | 863 | } |
| @@ -863,7 +865,7 @@ async function getAllEnabledUsers() { | |||
| 863 | /** | 865 | /** |
| 864 | * Express router for serving files from the user's directories. | 866 | * Express router for serving files from the user's directories. |
| 865 | */ | 867 | */ |
| 866 | const router = express.Router(); | 868 | export const router = express.Router(); |
| 867 | router.use('/backgrounds/*', createRouteHandler(req => req.user.directories.backgrounds)); | 869 | router.use('/backgrounds/*', createRouteHandler(req => req.user.directories.backgrounds)); |
| 868 | router.use('/characters/*', createRouteHandler(req => req.user.directories.characters)); | 870 | router.use('/characters/*', createRouteHandler(req => req.user.directories.characters)); |
| 869 | router.use('/User%20Avatars/*', createRouteHandler(req => req.user.directories.avatars)); | 871 | router.use('/User%20Avatars/*', createRouteHandler(req => req.user.directories.avatars)); |
| @@ -871,31 +873,3 @@ router.use('/assets/*', createRouteHandler(req => req.user.directories.assets)); | |||
| 871 | router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages)); | 873 | router.use('/user/images/*', createRouteHandler(req => req.user.directories.userImages)); |
| 872 | router.use('/user/files/*', createRouteHandler(req => req.user.directories.files)); | 874 | router.use('/user/files/*', createRouteHandler(req => req.user.directories.files)); |
| 873 | router.use('/scripts/extensions/third-party/*', createRouteHandler(req => req.user.directories.extensions)); | 875 | router.use('/scripts/extensions/third-party/*', createRouteHandler(req => req.user.directories.extensions)); |
| 874 | |||
| 875 | module.exports = { | ||
| 876 | KEY_PREFIX, | ||
| 877 | toKey, | ||
| 878 | toAvatarKey, | ||
| 879 | initUserStorage, | ||
| 880 | ensurePublicDirectoriesExist, | ||
| 881 | getUserDirectoriesList, | ||
| 882 | getAllUserHandles, | ||
| 883 | getUserDirectories, | ||
| 884 | setUserDataMiddleware, | ||
| 885 | requireLoginMiddleware, | ||
| 886 | requireAdminMiddleware, | ||
| 887 | migrateUserData, | ||
| 888 | migrateSystemPrompts, | ||
| 889 | getPasswordSalt, | ||
| 890 | getPasswordHash, | ||
| 891 | getCsrfSecret, | ||
| 892 | getCookieSecret, | ||
| 893 | getCookieSessionName, | ||
| 894 | getUserAvatar, | ||
| 895 | shouldRedirectToLogin, | ||
| 896 | createBackupArchive, | ||
| 897 | tryAutoLogin, | ||
| 898 | getAllUsers, | ||
| 899 | getAllEnabledUsers, | ||
| 900 | router, | ||
| 901 | }; | ||
| @@ -1,13 +1,18 @@ | |||
| 1 | const path = require('path'); | 1 | import path from 'node:path'; |
| 2 | const fs = require('fs'); | 2 | import fs from 'node:fs'; |
| 3 | const commandExistsSync = require('command-exists').sync; | 3 | import http2 from 'node:http2'; |
| 4 | const writeFileAtomicSync = require('write-file-atomic').sync; | 4 | import process from 'node:process'; |
| 5 | const _ = require('lodash'); | 5 | import { Readable } from 'node:stream'; |
| 6 | const yauzl = require('yauzl'); | 6 | import { createRequire } from 'node:module'; |
| 7 | const mime = require('mime-types'); | 7 | import { Buffer } from 'node:buffer'; |
| 8 | const yaml = require('yaml'); | 8 | |
| 9 | const { default: simpleGit } = require('simple-git'); | 9 | import yaml from 'yaml'; |
| 10 | const { Readable } = require('stream'); | 10 | import { sync as commandExistsSync } from 'command-exists'; |
| 11 | import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 12 | import _ from 'lodash'; | ||
| 13 | import yauzl from 'yauzl'; | ||
| 14 | import mime from 'mime-types'; | ||
| 15 | import { default as simpleGit } from 'simple-git'; | ||
| 11 | 16 | ||
| 12 | /** | 17 | /** |
| 13 | * Parsed config object. | 18 | * Parsed config object. |
| @@ -18,7 +23,7 @@ let CACHED_CONFIG = null; | |||
| 18 | * Returns the config object from the config.yaml file. | 23 | * Returns the config object from the config.yaml file. |
| 19 | * @returns {object} Config object | 24 | * @returns {object} Config object |
| 20 | */ | 25 | */ |
| 21 | function getConfig() { | 26 | export function getConfig() { |
| 22 | if (CACHED_CONFIG) { | 27 | if (CACHED_CONFIG) { |
| 23 | return CACHED_CONFIG; | 28 | return CACHED_CONFIG; |
| 24 | } | 29 | } |
| @@ -46,7 +51,7 @@ function getConfig() { | |||
| 46 | * @param {any} defaultValue - Default value to return if the key is not found | 51 | * @param {any} defaultValue - Default value to return if the key is not found |
| 47 | * @returns {any} Value for the given key | 52 | * @returns {any} Value for the given key |
| 48 | */ | 53 | */ |
| 49 | function getConfigValue(key, defaultValue = null) { | 54 | export function getConfigValue(key, defaultValue = null) { |
| 50 | const config = getConfig(); | 55 | const config = getConfig(); |
| 51 | return _.get(config, key, defaultValue); | 56 | return _.get(config, key, defaultValue); |
| 52 | } | 57 | } |
| @@ -56,7 +61,7 @@ function getConfigValue(key, defaultValue = null) { | |||
| 56 | * @param {string} key Key to set | 61 | * @param {string} key Key to set |
| 57 | * @param {any} value Value to set | 62 | * @param {any} value Value to set |
| 58 | */ | 63 | */ |
| 59 | function setConfigValue(key, value) { | 64 | export function setConfigValue(key, value) { |
| 60 | // Reset cache so that the next getConfig call will read the updated config file | 65 | // Reset cache so that the next getConfig call will read the updated config file |
| 61 | CACHED_CONFIG = null; | 66 | CACHED_CONFIG = null; |
| 62 | const config = getConfig(); | 67 | const config = getConfig(); |
| @@ -69,7 +74,7 @@ function setConfigValue(key, value) { | |||
| 69 | * @param {string} auth username:password | 74 | * @param {string} auth username:password |
| 70 | * @returns {string} Basic Auth header value | 75 | * @returns {string} Basic Auth header value |
| 71 | */ | 76 | */ |
| 72 | function getBasicAuthHeader(auth) { | 77 | export function getBasicAuthHeader(auth) { |
| 73 | const encoded = Buffer.from(`${auth}`).toString('base64'); | 78 | const encoded = Buffer.from(`${auth}`).toString('base64'); |
| 74 | return `Basic ${encoded}`; | 79 | return `Basic ${encoded}`; |
| 75 | } | 80 | } |
| @@ -79,7 +84,7 @@ function getBasicAuthHeader(auth) { | |||
| 79 | * Also returns the agent string for the Horde API. | 84 | * Also returns the agent string for the Horde API. |
| 80 | * @returns {Promise<{agent: string, pkgVersion: string, gitRevision: string | null, gitBranch: string | null, commitDate: string | null, isLatest: boolean}>} Version info object | 85 | * @returns {Promise<{agent: string, pkgVersion: string, gitRevision: string | null, gitBranch: string | null, commitDate: string | null, isLatest: boolean}>} Version info object |
| 81 | */ | 86 | */ |
| 82 | async function getVersion() { | 87 | export async function getVersion() { |
| 83 | let pkgVersion = 'UNKNOWN'; | 88 | let pkgVersion = 'UNKNOWN'; |
| 84 | let gitRevision = null; | 89 | let gitRevision = null; |
| 85 | let gitBranch = null; | 90 | let gitBranch = null; |
| @@ -87,9 +92,10 @@ async function getVersion() { | |||
| 87 | let isLatest = true; | 92 | let isLatest = true; |
| 88 | 93 | ||
| 89 | try { | 94 | try { |
| 95 | const require = createRequire(import.meta.url); | ||
| 90 | const pkgJson = require(path.join(process.cwd(), './package.json')); | 96 | const pkgJson = require(path.join(process.cwd(), './package.json')); |
| 91 | pkgVersion = pkgJson.version; | 97 | pkgVersion = pkgJson.version; |
| 92 | if (!process['pkg'] && commandExistsSync('git')) { | 98 | if (commandExistsSync('git')) { |
| 93 | const git = simpleGit(); | 99 | const git = simpleGit(); |
| 94 | const cwd = process.cwd(); | 100 | const cwd = process.cwd(); |
| 95 | gitRevision = await git.cwd(cwd).revparse(['--short', 'HEAD']); | 101 | gitRevision = await git.cwd(cwd).revparse(['--short', 'HEAD']); |
| @@ -117,7 +123,7 @@ async function getVersion() { | |||
| 117 | * @param {number} ms Milliseconds to wait | 123 | * @param {number} ms Milliseconds to wait |
| 118 | * @returns {Promise<void>} Promise that resolves after the given amount of milliseconds | 124 | * @returns {Promise<void>} Promise that resolves after the given amount of milliseconds |
| 119 | */ | 125 | */ |
| 120 | function delay(ms) { | 126 | export function delay(ms) { |
| 121 | return new Promise(resolve => setTimeout(resolve, ms)); | 127 | return new Promise(resolve => setTimeout(resolve, ms)); |
| 122 | } | 128 | } |
| 123 | 129 | ||
| @@ -127,7 +133,7 @@ function delay(ms) { | |||
| 127 | * @returns {string} Random hex string | 133 | * @returns {string} Random hex string |
| 128 | * @example getHexString(8) // 'a1b2c3d4' | 134 | * @example getHexString(8) // 'a1b2c3d4' |
| 129 | */ | 135 | */ |
| 130 | function getHexString(length) { | 136 | export function getHexString(length) { |
| 131 | const chars = '0123456789abcdef'; | 137 | const chars = '0123456789abcdef'; |
| 132 | let result = ''; | 138 | let result = ''; |
| 133 | for (let i = 0; i < length; i++) { | 139 | for (let i = 0; i < length; i++) { |
| @@ -142,7 +148,7 @@ function getHexString(length) { | |||
| 142 | * @param {string} fileExtension File extension to look for | 148 | * @param {string} fileExtension File extension to look for |
| 143 | * @returns {Promise<Buffer|null>} Buffer containing the extracted file. Null if the file was not found. | 149 | * @returns {Promise<Buffer|null>} Buffer containing the extracted file. Null if the file was not found. |
| 144 | */ | 150 | */ |
| 145 | async function extractFileFromZipBuffer(archiveBuffer, fileExtension) { | 151 | export async function extractFileFromZipBuffer(archiveBuffer, fileExtension) { |
| 146 | return await new Promise((resolve, reject) => yauzl.fromBuffer(Buffer.from(archiveBuffer), { lazyEntries: true }, (err, zipfile) => { | 152 | return await new Promise((resolve, reject) => yauzl.fromBuffer(Buffer.from(archiveBuffer), { lazyEntries: true }, (err, zipfile) => { |
| 147 | if (err) { | 153 | if (err) { |
| 148 | reject(err); | 154 | reject(err); |
| @@ -181,7 +187,7 @@ async function extractFileFromZipBuffer(archiveBuffer, fileExtension) { | |||
| 181 | * @param {string} zipFilePath Path to the ZIP archive | 187 | * @param {string} zipFilePath Path to the ZIP archive |
| 182 | * @returns {Promise<[string, Buffer][]>} Array of image buffers | 188 | * @returns {Promise<[string, Buffer][]>} Array of image buffers |
| 183 | */ | 189 | */ |
| 184 | async function getImageBuffers(zipFilePath) { | 190 | export async function getImageBuffers(zipFilePath) { |
| 185 | return new Promise((resolve, reject) => { | 191 | return new Promise((resolve, reject) => { |
| 186 | // Check if the zip file exists | 192 | // Check if the zip file exists |
| 187 | if (!fs.existsSync(zipFilePath)) { | 193 | if (!fs.existsSync(zipFilePath)) { |
| @@ -237,7 +243,7 @@ async function getImageBuffers(zipFilePath) { | |||
| 237 | * @param {any} readableStream Readable stream to read from | 243 | * @param {any} readableStream Readable stream to read from |
| 238 | * @returns {Promise<Buffer[]>} Array of chunks | 244 | * @returns {Promise<Buffer[]>} Array of chunks |
| 239 | */ | 245 | */ |
| 240 | async function readAllChunks(readableStream) { | 246 | export async function readAllChunks(readableStream) { |
| 241 | return new Promise((resolve, reject) => { | 247 | return new Promise((resolve, reject) => { |
| 242 | // Consume the readable stream | 248 | // Consume the readable stream |
| 243 | const chunks = []; | 249 | const chunks = []; |
| @@ -261,7 +267,7 @@ function isObject(item) { | |||
| 261 | return (item && typeof item === 'object' && !Array.isArray(item)); | 267 | return (item && typeof item === 'object' && !Array.isArray(item)); |
| 262 | } | 268 | } |
| 263 | 269 | ||
| 264 | function deepMerge(target, source) { | 270 | export function deepMerge(target, source) { |
| 265 | let output = Object.assign({}, target); | 271 | let output = Object.assign({}, target); |
| 266 | if (isObject(target) && isObject(source)) { | 272 | if (isObject(target) && isObject(source)) { |
| 267 | Object.keys(source).forEach(key => { | 273 | Object.keys(source).forEach(key => { |
| @@ -278,7 +284,7 @@ function deepMerge(target, source) { | |||
| 278 | return output; | 284 | return output; |
| 279 | } | 285 | } |
| 280 | 286 | ||
| 281 | const color = { | 287 | export const color = { |
| 282 | byNum: (mess, fgNum) => { | 288 | byNum: (mess, fgNum) => { |
| 283 | mess = mess || ''; | 289 | mess = mess || ''; |
| 284 | fgNum = fgNum === undefined ? 31 : fgNum; | 290 | fgNum = fgNum === undefined ? 31 : fgNum; |
| @@ -298,7 +304,7 @@ const color = { | |||
| 298 | * Gets a random UUIDv4 string. | 304 | * Gets a random UUIDv4 string. |
| 299 | * @returns {string} A UUIDv4 string | 305 | * @returns {string} A UUIDv4 string |
| 300 | */ | 306 | */ |
| 301 | function uuidv4() { | 307 | export function uuidv4() { |
| 302 | if ('crypto' in global && 'randomUUID' in global.crypto) { | 308 | if ('crypto' in global && 'randomUUID' in global.crypto) { |
| 303 | return global.crypto.randomUUID(); | 309 | return global.crypto.randomUUID(); |
| 304 | } | 310 | } |
| @@ -309,7 +315,7 @@ function uuidv4() { | |||
| 309 | }); | 315 | }); |
| 310 | } | 316 | } |
| 311 | 317 | ||
| 312 | function humanizedISO8601DateTime(date) { | 318 | export function humanizedISO8601DateTime(date) { |
| 313 | let baseDate = typeof date === 'number' ? new Date(date) : new Date(); | 319 | let baseDate = typeof date === 'number' ? new Date(date) : new Date(); |
| 314 | let humanYear = baseDate.getFullYear(); | 320 | let humanYear = baseDate.getFullYear(); |
| 315 | let humanMonth = (baseDate.getMonth() + 1); | 321 | let humanMonth = (baseDate.getMonth() + 1); |
| @@ -322,7 +328,7 @@ function humanizedISO8601DateTime(date) { | |||
| 322 | return HumanizedDateTime; | 328 | return HumanizedDateTime; |
| 323 | } | 329 | } |
| 324 | 330 | ||
| 325 | function tryParse(str) { | 331 | export function tryParse(str) { |
| 326 | try { | 332 | try { |
| 327 | return JSON.parse(str); | 333 | return JSON.parse(str); |
| 328 | } catch { | 334 | } catch { |
| @@ -337,7 +343,7 @@ function tryParse(str) { | |||
| 337 | * @param {string} inputPath The path to be converted. | 343 | * @param {string} inputPath The path to be converted. |
| 338 | * @returns The relative URL path from which the client can access the file. | 344 | * @returns The relative URL path from which the client can access the file. |
| 339 | */ | 345 | */ |
| 340 | function clientRelativePath(root, inputPath) { | 346 | export function clientRelativePath(root, inputPath) { |
| 341 | if (!inputPath.startsWith(root)) { | 347 | if (!inputPath.startsWith(root)) { |
| 342 | throw new Error('Input path does not start with the root directory'); | 348 | throw new Error('Input path does not start with the root directory'); |
| 343 | } | 349 | } |
| @@ -350,11 +356,11 @@ function clientRelativePath(root, inputPath) { | |||
| 350 | * @param {string} filename The file name to remove the extension from. | 356 | * @param {string} filename The file name to remove the extension from. |
| 351 | * @returns The file name, sans extension | 357 | * @returns The file name, sans extension |
| 352 | */ | 358 | */ |
| 353 | function removeFileExtension(filename) { | 359 | export function removeFileExtension(filename) { |
| 354 | return filename.replace(/\.[^.]+$/, ''); | 360 | return filename.replace(/\.[^.]+$/, ''); |
| 355 | } | 361 | } |
| 356 | 362 | ||
| 357 | function generateTimestamp() { | 363 | export function generateTimestamp() { |
| 358 | const now = new Date(); | 364 | const now = new Date(); |
| 359 | const year = now.getFullYear(); | 365 | const year = now.getFullYear(); |
| 360 | const month = String(now.getMonth() + 1).padStart(2, '0'); | 366 | const month = String(now.getMonth() + 1).padStart(2, '0'); |
| @@ -371,7 +377,7 @@ function generateTimestamp() { | |||
| 371 | * @param {string} directory The root directory to remove backups from. | 377 | * @param {string} directory The root directory to remove backups from. |
| 372 | * @param {string} prefix File prefix to filter backups by. | 378 | * @param {string} prefix File prefix to filter backups by. |
| 373 | */ | 379 | */ |
| 374 | function removeOldBackups(directory, prefix) { | 380 | export function removeOldBackups(directory, prefix) { |
| 375 | const MAX_BACKUPS = Number(getConfigValue('numberOfBackups', 50)); | 381 | const MAX_BACKUPS = Number(getConfigValue('numberOfBackups', 50)); |
| 376 | 382 | ||
| 377 | let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); | 383 | let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); |
| @@ -389,7 +395,7 @@ function removeOldBackups(directory, prefix) { | |||
| 389 | * @param {'name' | 'date'} sortBy Sort images by name or date | 395 | * @param {'name' | 'date'} sortBy Sort images by name or date |
| 390 | * @returns {string[]} List of image file names | 396 | * @returns {string[]} List of image file names |
| 391 | */ | 397 | */ |
| 392 | function getImages(directoryPath, sortBy = 'name') { | 398 | export function getImages(directoryPath, sortBy = 'name') { |
| 393 | function getSortFunction() { | 399 | function getSortFunction() { |
| 394 | switch (sortBy) { | 400 | switch (sortBy) { |
| 395 | case 'name': | 401 | case 'name': |
| @@ -415,7 +421,7 @@ function getImages(directoryPath, sortBy = 'name') { | |||
| 415 | * @param {import('node-fetch').Response} from The Fetch API response to pipe from. | 421 | * @param {import('node-fetch').Response} from The Fetch API response to pipe from. |
| 416 | * @param {import('express').Response} to The Express response to pipe to. | 422 | * @param {import('express').Response} to The Express response to pipe to. |
| 417 | */ | 423 | */ |
| 418 | function forwardFetchResponse(from, to) { | 424 | export function forwardFetchResponse(from, to) { |
| 419 | let statusCode = from.status; | 425 | let statusCode = from.status; |
| 420 | let statusText = from.statusText; | 426 | let statusText = from.statusText; |
| 421 | 427 | ||
| @@ -434,17 +440,22 @@ function forwardFetchResponse(from, to) { | |||
| 434 | 440 | ||
| 435 | to.statusCode = statusCode; | 441 | to.statusCode = statusCode; |
| 436 | to.statusMessage = statusText; | 442 | to.statusMessage = statusText; |
| 437 | from.body.pipe(to); | ||
| 438 | 443 | ||
| 439 | to.socket.on('close', function () { | 444 | if (from.body && to.socket) { |
| 440 | if (from.body instanceof Readable) from.body.destroy(); // Close the remote stream | 445 | from.body.pipe(to); |
| 441 | to.end(); // End the Express response | 446 | |
| 442 | }); | 447 | to.socket.on('close', function () { |
| 448 | if (from.body instanceof Readable) from.body.destroy(); // Close the remote stream | ||
| 449 | to.end(); // End the Express response | ||
| 450 | }); | ||
| 443 | 451 | ||
| 444 | from.body.on('end', function () { | 452 | from.body.on('end', function () { |
| 445 | console.log('Streaming request finished'); | 453 | console.log('Streaming request finished'); |
| 454 | to.end(); | ||
| 455 | }); | ||
| 456 | } else { | ||
| 446 | to.end(); | 457 | to.end(); |
| 447 | }); | 458 | } |
| 448 | } | 459 | } |
| 449 | 460 | ||
| 450 | /** | 461 | /** |
| @@ -457,10 +468,9 @@ function forwardFetchResponse(from, to) { | |||
| 457 | * @param {object} headers Request headers | 468 | * @param {object} headers Request headers |
| 458 | * @returns {Promise<string>} Response body | 469 | * @returns {Promise<string>} Response body |
| 459 | */ | 470 | */ |
| 460 | function makeHttp2Request(endpoint, method, body, headers) { | 471 | export function makeHttp2Request(endpoint, method, body, headers) { |
| 461 | return new Promise((resolve, reject) => { | 472 | return new Promise((resolve, reject) => { |
| 462 | try { | 473 | try { |
| 463 | const http2 = require('http2'); | ||
| 464 | const url = new URL(endpoint); | 474 | const url = new URL(endpoint); |
| 465 | const client = http2.connect(url.origin); | 475 | const client = http2.connect(url.origin); |
| 466 | 476 | ||
| @@ -511,7 +521,7 @@ function makeHttp2Request(endpoint, method, body, headers) { | |||
| 511 | * @param {string} yamlString YAML-serialized object | 521 | * @param {string} yamlString YAML-serialized object |
| 512 | * @returns | 522 | * @returns |
| 513 | */ | 523 | */ |
| 514 | function mergeObjectWithYaml(obj, yamlString) { | 524 | export function mergeObjectWithYaml(obj, yamlString) { |
| 515 | if (!yamlString) { | 525 | if (!yamlString) { |
| 516 | return; | 526 | return; |
| 517 | } | 527 | } |
| @@ -540,7 +550,7 @@ function mergeObjectWithYaml(obj, yamlString) { | |||
| 540 | * @param {string} yamlString YAML-serialized array | 550 | * @param {string} yamlString YAML-serialized array |
| 541 | * @returns {void} Nothing | 551 | * @returns {void} Nothing |
| 542 | */ | 552 | */ |
| 543 | function excludeKeysByYaml(obj, yamlString) { | 553 | export function excludeKeysByYaml(obj, yamlString) { |
| 544 | if (!yamlString) { | 554 | if (!yamlString) { |
| 545 | return; | 555 | return; |
| 546 | } | 556 | } |
| @@ -569,14 +579,14 @@ function excludeKeysByYaml(obj, yamlString) { | |||
| 569 | * @param {string} str Input string | 579 | * @param {string} str Input string |
| 570 | * @returns {string} Trimmed string | 580 | * @returns {string} Trimmed string |
| 571 | */ | 581 | */ |
| 572 | function trimV1(str) { | 582 | export function trimV1(str) { |
| 573 | return String(str ?? '').replace(/\/$/, '').replace(/\/v1$/, ''); | 583 | return String(str ?? '').replace(/\/$/, '').replace(/\/v1$/, ''); |
| 574 | } | 584 | } |
| 575 | 585 | ||
| 576 | /** | 586 | /** |
| 577 | * Simple TTL memory cache. | 587 | * Simple TTL memory cache. |
| 578 | */ | 588 | */ |
| 579 | class Cache { | 589 | export class Cache { |
| 580 | /** | 590 | /** |
| 581 | * @param {number} ttl Time to live in milliseconds | 591 | * @param {number} ttl Time to live in milliseconds |
| 582 | */ | 592 | */ |
| @@ -633,7 +643,7 @@ class Cache { | |||
| 633 | * @param {string} text Text with color formatting | 643 | * @param {string} text Text with color formatting |
| 634 | * @returns {string} Text without color formatting | 644 | * @returns {string} Text without color formatting |
| 635 | */ | 645 | */ |
| 636 | function removeColorFormatting(text) { | 646 | export function removeColorFormatting(text) { |
| 637 | // ANSI escape codes for colors are usually in the format \x1b[<codes>m | 647 | // ANSI escape codes for colors are usually in the format \x1b[<codes>m |
| 638 | return text.replace(/\x1b\[\d{1,2}(;\d{1,2})*m/g, ''); | 648 | return text.replace(/\x1b\[\d{1,2}(;\d{1,2})*m/g, ''); |
| 639 | } | 649 | } |
| @@ -643,7 +653,7 @@ function removeColorFormatting(text) { | |||
| 643 | * @param {number} n Number of times to repeat the separator | 653 | * @param {number} n Number of times to repeat the separator |
| 644 | * @returns {string} Separator string | 654 | * @returns {string} Separator string |
| 645 | */ | 655 | */ |
| 646 | function getSeparator(n) { | 656 | export function getSeparator(n) { |
| 647 | return '='.repeat(n); | 657 | return '='.repeat(n); |
| 648 | } | 658 | } |
| 649 | 659 | ||
| @@ -652,7 +662,7 @@ function getSeparator(n) { | |||
| 652 | * @param {string} url String to check | 662 | * @param {string} url String to check |
| 653 | * @returns {boolean} If the URL is valid | 663 | * @returns {boolean} If the URL is valid |
| 654 | */ | 664 | */ |
| 655 | function isValidUrl(url) { | 665 | export function isValidUrl(url) { |
| 656 | try { | 666 | try { |
| 657 | new URL(url); | 667 | new URL(url); |
| 658 | return true; | 668 | return true; |
| @@ -660,35 +670,3 @@ function isValidUrl(url) { | |||
| 660 | return false; | 670 | return false; |
| 661 | } | 671 | } |
| 662 | } | 672 | } |
| 663 | |||
| 664 | module.exports = { | ||
| 665 | getConfig, | ||
| 666 | getConfigValue, | ||
| 667 | setConfigValue, | ||
| 668 | getVersion, | ||
| 669 | getBasicAuthHeader, | ||
| 670 | extractFileFromZipBuffer, | ||
| 671 | getImageBuffers, | ||
| 672 | readAllChunks, | ||
| 673 | delay, | ||
| 674 | deepMerge, | ||
| 675 | color, | ||
| 676 | uuidv4, | ||
| 677 | humanizedISO8601DateTime, | ||
| 678 | tryParse, | ||
| 679 | clientRelativePath, | ||
| 680 | removeFileExtension, | ||
| 681 | generateTimestamp, | ||
| 682 | removeOldBackups, | ||
| 683 | getImages, | ||
| 684 | forwardFetchResponse, | ||
| 685 | getHexString, | ||
| 686 | mergeObjectWithYaml, | ||
| 687 | excludeKeysByYaml, | ||
| 688 | trimV1, | ||
| 689 | Cache, | ||
| 690 | makeHttp2Request, | ||
| 691 | removeColorFormatting, | ||
| 692 | getSeparator, | ||
| 693 | isValidUrl, | ||
| 694 | }; | ||
| @@ -5,7 +5,7 @@ | |||
| 5 | * | 5 | * |
| 6 | * @link https://github.com/malfoyslastname/character-card-spec-v2 | 6 | * @link https://github.com/malfoyslastname/character-card-spec-v2 |
| 7 | */ | 7 | */ |
| 8 | class TavernCardValidator { | 8 | export class TavernCardValidator { |
| 9 | /** | 9 | /** |
| 10 | * @type {string|null} | 10 | * @type {string|null} |
| 11 | */ | 11 | */ |
| @@ -167,5 +167,3 @@ class TavernCardValidator { | |||
| 167 | return true; | 167 | return true; |
| 168 | } | 168 | } |
| 169 | } | 169 | } |
| 170 | |||
| 171 | module.exports = { TavernCardValidator }; | ||
| @@ -1,15 +1,15 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { SECRET_KEYS, readSecret } = require('../endpoints/secrets'); | 2 | import { SECRET_KEYS, readSecret } from '../endpoints/secrets.js'; |
| 3 | 3 | ||
| 4 | /** | 4 | /** |
| 5 | * Gets the vector for the given text batch from an OpenAI compatible endpoint. | 5 | * Gets the vector for the given text batch from an OpenAI compatible endpoint. |
| 6 | * @param {string[]} texts - The array of texts to get the vector for | 6 | * @param {string[]} texts - The array of texts to get the vector for |
| 7 | * @param {boolean} isQuery - If the text is a query for embedding search | 7 | * @param {boolean} isQuery - If the text is a query for embedding search |
| 8 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 8 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 9 | * @param {string} model - The model to use for the embedding | 9 | * @param {string} model - The model to use for the embedding |
| 10 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 10 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 11 | */ | 11 | */ |
| 12 | async function getCohereBatchVector(texts, isQuery, directories, model) { | 12 | export async function getCohereBatchVector(texts, isQuery, directories, model) { |
| 13 | const key = readSecret(directories, SECRET_KEYS.COHERE); | 13 | const key = readSecret(directories, SECRET_KEYS.COHERE); |
| 14 | 14 | ||
| 15 | if (!key) { | 15 | if (!key) { |
| @@ -38,6 +38,7 @@ async function getCohereBatchVector(texts, isQuery, directories, model) { | |||
| 38 | throw new Error('API request failed'); | 38 | throw new Error('API request failed'); |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | /** @type {any} */ | ||
| 41 | const data = await response.json(); | 42 | const data = await response.json(); |
| 42 | if (!Array.isArray(data?.embeddings?.float)) { | 43 | if (!Array.isArray(data?.embeddings?.float)) { |
| 43 | console.log('API response was not an array'); | 44 | console.log('API response was not an array'); |
| @@ -51,16 +52,12 @@ async function getCohereBatchVector(texts, isQuery, directories, model) { | |||
| 51 | * Gets the vector for the given text from an OpenAI compatible endpoint. | 52 | * Gets the vector for the given text from an OpenAI compatible endpoint. |
| 52 | * @param {string} text - The text to get the vector for | 53 | * @param {string} text - The text to get the vector for |
| 53 | * @param {boolean} isQuery - If the text is a query for embedding search | 54 | * @param {boolean} isQuery - If the text is a query for embedding search |
| 54 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 55 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 55 | * @param {string} model - The model to use for the embedding | 56 | * @param {string} model - The model to use for the embedding |
| 56 | * @returns {Promise<number[]>} - The vector for the text | 57 | * @returns {Promise<number[]>} - The vector for the text |
| 57 | */ | 58 | */ |
| 58 | async function getCohereVector(text, isQuery, directories, model) { | 59 | export async function getCohereVector(text, isQuery, directories, model) { |
| 59 | const vectors = await getCohereBatchVector([text], isQuery, directories, model); | 60 | const vectors = await getCohereBatchVector([text], isQuery, directories, model); |
| 60 | return vectors[0]; | 61 | return vectors[0]; |
| 61 | } | 62 | } |
| 62 | 63 | ||
| 63 | module.exports = { | ||
| 64 | getCohereBatchVector, | ||
| 65 | getCohereVector, | ||
| 66 | }; | ||
| @@ -1,3 +1,4 @@ | |||
| 1 | import { getPipeline } from '../transformers.mjs'; | ||
| 1 | const TASK = 'feature-extraction'; | 2 | const TASK = 'feature-extraction'; |
| 2 | 3 | ||
| 3 | /** | 4 | /** |
| @@ -5,9 +6,8 @@ const TASK = 'feature-extraction'; | |||
| 5 | * @param {string} text - The text to vectorize | 6 | * @param {string} text - The text to vectorize |
| 6 | * @returns {Promise<number[]>} - The vectorized text in form of an array of numbers | 7 | * @returns {Promise<number[]>} - The vectorized text in form of an array of numbers |
| 7 | */ | 8 | */ |
| 8 | async function getTransformersVector(text) { | 9 | export async function getTransformersVector(text) { |
| 9 | const module = await import('../transformers.mjs'); | 10 | const pipe = await getPipeline(TASK); |
| 10 | const pipe = await module.default.getPipeline(TASK); | ||
| 11 | const result = await pipe(text, { pooling: 'mean', normalize: true }); | 11 | const result = await pipe(text, { pooling: 'mean', normalize: true }); |
| 12 | const vector = Array.from(result.data); | 12 | const vector = Array.from(result.data); |
| 13 | return vector; | 13 | return vector; |
| @@ -18,15 +18,10 @@ async function getTransformersVector(text) { | |||
| 18 | * @param {string[]} texts - The texts to vectorize | 18 | * @param {string[]} texts - The texts to vectorize |
| 19 | * @returns {Promise<number[][]>} - The vectorized texts in form of an array of arrays of numbers | 19 | * @returns {Promise<number[][]>} - The vectorized texts in form of an array of arrays of numbers |
| 20 | */ | 20 | */ |
| 21 | async function getTransformersBatchVector(texts) { | 21 | export async function getTransformersBatchVector(texts) { |
| 22 | const result = []; | 22 | const result = []; |
| 23 | for (const text of texts) { | 23 | for (const text of texts) { |
| 24 | result.push(await getTransformersVector(text)); | 24 | result.push(await getTransformersVector(text)); |
| 25 | } | 25 | } |
| 26 | return result; | 26 | return result; |
| 27 | } | 27 | } |
| 28 | |||
| 29 | module.exports = { | ||
| 30 | getTransformersVector, | ||
| 31 | getTransformersBatchVector, | ||
| 32 | }; | ||
| @@ -1,4 +1,4 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | 2 | ||
| 3 | /** | 3 | /** |
| 4 | * Gets the vector for the given text from SillyTavern-extras | 4 | * Gets the vector for the given text from SillyTavern-extras |
| @@ -7,7 +7,7 @@ const fetch = require('node-fetch').default; | |||
| 7 | * @param {string} apiKey - The Extras API key, or empty string if API key not enabled | 7 | * @param {string} apiKey - The Extras API key, or empty string if API key not enabled |
| 8 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 8 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 9 | */ | 9 | */ |
| 10 | async function getExtrasBatchVector(texts, apiUrl, apiKey) { | 10 | export async function getExtrasBatchVector(texts, apiUrl, apiKey) { |
| 11 | return getExtrasVectorImpl(texts, apiUrl, apiKey); | 11 | return getExtrasVectorImpl(texts, apiUrl, apiKey); |
| 12 | } | 12 | } |
| 13 | 13 | ||
| @@ -18,7 +18,7 @@ async function getExtrasBatchVector(texts, apiUrl, apiKey) { | |||
| 18 | * @param {string} apiKey - The Extras API key, or empty string if API key not enabled | 18 | * @param {string} apiKey - The Extras API key, or empty string if API key not enabled |
| 19 | * @returns {Promise<number[]>} - The vector for the text | 19 | * @returns {Promise<number[]>} - The vector for the text |
| 20 | */ | 20 | */ |
| 21 | async function getExtrasVector(text, apiUrl, apiKey) { | 21 | export async function getExtrasVector(text, apiUrl, apiKey) { |
| 22 | return getExtrasVectorImpl(text, apiUrl, apiKey); | 22 | return getExtrasVectorImpl(text, apiUrl, apiKey); |
| 23 | } | 23 | } |
| 24 | 24 | ||
| @@ -66,13 +66,9 @@ async function getExtrasVectorImpl(text, apiUrl, apiKey) { | |||
| 66 | throw new Error('Extras request failed'); | 66 | throw new Error('Extras request failed'); |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | /** @type {any} */ | ||
| 69 | const data = await response.json(); | 70 | const data = await response.json(); |
| 70 | const vector = data.embedding; // `embedding`: number[] (one text item), or number[][] (multiple text items). | 71 | const vector = data.embedding; // `embedding`: number[] (one text item), or number[][] (multiple text items). |
| 71 | 72 | ||
| 72 | return vector; | 73 | return vector; |
| 73 | } | 74 | } |
| 74 | |||
| 75 | module.exports = { | ||
| 76 | getExtrasVector, | ||
| 77 | getExtrasBatchVector, | ||
| 78 | }; | ||
| @@ -1,15 +1,15 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { setAdditionalHeadersByType } = require('../additional-headers'); | 2 | import { setAdditionalHeadersByType } from '../additional-headers.js'; |
| 3 | const { TEXTGEN_TYPES } = require('../constants'); | 3 | import { TEXTGEN_TYPES } from '../constants.js'; |
| 4 | 4 | ||
| 5 | /** | 5 | /** |
| 6 | * Gets the vector for the given text from LlamaCpp | 6 | * Gets the vector for the given text from LlamaCpp |
| 7 | * @param {string[]} texts - The array of texts to get the vectors for | 7 | * @param {string[]} texts - The array of texts to get the vectors for |
| 8 | * @param {string} apiUrl - The API URL | 8 | * @param {string} apiUrl - The API URL |
| 9 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 9 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 10 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 10 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 11 | */ | 11 | */ |
| 12 | async function getLlamaCppBatchVector(texts, apiUrl, directories) { | 12 | export async function getLlamaCppBatchVector(texts, apiUrl, directories) { |
| 13 | const url = new URL(apiUrl); | 13 | const url = new URL(apiUrl); |
| 14 | url.pathname = '/v1/embeddings'; | 14 | url.pathname = '/v1/embeddings'; |
| 15 | 15 | ||
| @@ -30,6 +30,7 @@ async function getLlamaCppBatchVector(texts, apiUrl, directories) { | |||
| 30 | throw new Error(`LlamaCpp: Failed to get vector for text: ${response.statusText} ${responseText}`); | 30 | throw new Error(`LlamaCpp: Failed to get vector for text: ${response.statusText} ${responseText}`); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | /** @type {any} */ | ||
| 33 | const data = await response.json(); | 34 | const data = await response.json(); |
| 34 | 35 | ||
| 35 | if (!Array.isArray(data?.data)) { | 36 | if (!Array.isArray(data?.data)) { |
| @@ -47,15 +48,10 @@ async function getLlamaCppBatchVector(texts, apiUrl, directories) { | |||
| 47 | * Gets the vector for the given text from LlamaCpp | 48 | * Gets the vector for the given text from LlamaCpp |
| 48 | * @param {string} text - The text to get the vector for | 49 | * @param {string} text - The text to get the vector for |
| 49 | * @param {string} apiUrl - The API URL | 50 | * @param {string} apiUrl - The API URL |
| 50 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 51 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 51 | * @returns {Promise<number[]>} - The vector for the text | 52 | * @returns {Promise<number[]>} - The vector for the text |
| 52 | */ | 53 | */ |
| 53 | async function getLlamaCppVector(text, apiUrl, directories) { | 54 | export async function getLlamaCppVector(text, apiUrl, directories) { |
| 54 | const vectors = await getLlamaCppBatchVector([text], apiUrl, directories); | 55 | const vectors = await getLlamaCppBatchVector([text], apiUrl, directories); |
| 55 | return vectors[0]; | 56 | return vectors[0]; |
| 56 | } | 57 | } |
| 57 | |||
| 58 | module.exports = { | ||
| 59 | getLlamaCppBatchVector, | ||
| 60 | getLlamaCppVector, | ||
| 61 | }; | ||
| @@ -1,14 +1,14 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { SECRET_KEYS, readSecret } = require('../endpoints/secrets'); | 2 | import { SECRET_KEYS, readSecret } from '../endpoints/secrets.js'; |
| 3 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; | 3 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; |
| 4 | 4 | ||
| 5 | /** | 5 | /** |
| 6 | * Gets the vector for the given text from gecko model | 6 | * Gets the vector for the given text from gecko model |
| 7 | * @param {string[]} texts - The array of texts to get the vector for | 7 | * @param {string[]} texts - The array of texts to get the vector for |
| 8 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 8 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 9 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 9 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 10 | */ | 10 | */ |
| 11 | async function getMakerSuiteBatchVector(texts, directories) { | 11 | export async function getMakerSuiteBatchVector(texts, directories) { |
| 12 | const promises = texts.map(text => getMakerSuiteVector(text, directories)); | 12 | const promises = texts.map(text => getMakerSuiteVector(text, directories)); |
| 13 | return await Promise.all(promises); | 13 | return await Promise.all(promises); |
| 14 | } | 14 | } |
| @@ -16,10 +16,10 @@ async function getMakerSuiteBatchVector(texts, directories) { | |||
| 16 | /** | 16 | /** |
| 17 | * Gets the vector for the given text from Gemini API text-embedding-004 model | 17 | * Gets the vector for the given text from Gemini API text-embedding-004 model |
| 18 | * @param {string} text - The text to get the vector for | 18 | * @param {string} text - The text to get the vector for |
| 19 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 19 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 20 | * @returns {Promise<number[]>} - The vector for the text | 20 | * @returns {Promise<number[]>} - The vector for the text |
| 21 | */ | 21 | */ |
| 22 | async function getMakerSuiteVector(text, directories) { | 22 | export async function getMakerSuiteVector(text, directories) { |
| 23 | const key = readSecret(directories, SECRET_KEYS.MAKERSUITE); | 23 | const key = readSecret(directories, SECRET_KEYS.MAKERSUITE); |
| 24 | 24 | ||
| 25 | if (!key) { | 25 | if (!key) { |
| @@ -52,12 +52,8 @@ async function getMakerSuiteVector(text, directories) { | |||
| 52 | throw new Error('Google AI Studio request failed'); | 52 | throw new Error('Google AI Studio request failed'); |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | /** @type {any} */ | ||
| 55 | const data = await response.json(); | 56 | const data = await response.json(); |
| 56 | // noinspection JSValidateTypes | 57 | // noinspection JSValidateTypes |
| 57 | return data['embedding']['values']; | 58 | return data['embedding']['values']; |
| 58 | } | 59 | } |
| 59 | |||
| 60 | module.exports = { | ||
| 61 | getMakerSuiteVector, | ||
| 62 | getMakerSuiteBatchVector, | ||
| 63 | }; | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { SECRET_KEYS, readSecret } = require('../endpoints/secrets'); | 2 | import { SECRET_KEYS, readSecret } from '../endpoints/secrets.js'; |
| 3 | 3 | ||
| 4 | const SOURCES = { | 4 | const SOURCES = { |
| 5 | 'nomicai': { | 5 | 'nomicai': { |
| @@ -13,10 +13,10 @@ const SOURCES = { | |||
| 13 | * Gets the vector for the given text batch from an OpenAI compatible endpoint. | 13 | * Gets the vector for the given text batch from an OpenAI compatible endpoint. |
| 14 | * @param {string[]} texts - The array of texts to get the vector for | 14 | * @param {string[]} texts - The array of texts to get the vector for |
| 15 | * @param {string} source - The source of the vector | 15 | * @param {string} source - The source of the vector |
| 16 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 16 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 17 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 17 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 18 | */ | 18 | */ |
| 19 | async function getNomicAIBatchVector(texts, source, directories) { | 19 | export async function getNomicAIBatchVector(texts, source, directories) { |
| 20 | const config = SOURCES[source]; | 20 | const config = SOURCES[source]; |
| 21 | 21 | ||
| 22 | if (!config) { | 22 | if (!config) { |
| @@ -51,6 +51,7 @@ async function getNomicAIBatchVector(texts, source, directories) { | |||
| 51 | throw new Error('API request failed'); | 51 | throw new Error('API request failed'); |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | /** @type {any} */ | ||
| 54 | const data = await response.json(); | 55 | const data = await response.json(); |
| 55 | if (!Array.isArray(data?.embeddings)) { | 56 | if (!Array.isArray(data?.embeddings)) { |
| 56 | console.log('API response was not an array'); | 57 | console.log('API response was not an array'); |
| @@ -64,15 +65,10 @@ async function getNomicAIBatchVector(texts, source, directories) { | |||
| 64 | * Gets the vector for the given text from an OpenAI compatible endpoint. | 65 | * Gets the vector for the given text from an OpenAI compatible endpoint. |
| 65 | * @param {string} text - The text to get the vector for | 66 | * @param {string} text - The text to get the vector for |
| 66 | * @param {string} source - The source of the vector | 67 | * @param {string} source - The source of the vector |
| 67 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 68 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 68 | * @returns {Promise<number[]>} - The vector for the text | 69 | * @returns {Promise<number[]>} - The vector for the text |
| 69 | */ | 70 | */ |
| 70 | async function getNomicAIVector(text, source, directories) { | 71 | export async function getNomicAIVector(text, source, directories) { |
| 71 | const vectors = await getNomicAIBatchVector([text], source, directories); | 72 | const vectors = await getNomicAIBatchVector([text], source, directories); |
| 72 | return vectors[0]; | 73 | return vectors[0]; |
| 73 | } | 74 | } |
| 74 | |||
| 75 | module.exports = { | ||
| 76 | getNomicAIVector, | ||
| 77 | getNomicAIBatchVector, | ||
| 78 | }; | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { setAdditionalHeadersByType } = require('../additional-headers'); | 2 | import { setAdditionalHeadersByType } from '../additional-headers.js'; |
| 3 | const { TEXTGEN_TYPES } = require('../constants'); | 3 | import { TEXTGEN_TYPES } from '../constants.js'; |
| 4 | 4 | ||
| 5 | /** | 5 | /** |
| 6 | * Gets the vector for the given text from Ollama | 6 | * Gets the vector for the given text from Ollama |
| @@ -8,10 +8,10 @@ const { TEXTGEN_TYPES } = require('../constants'); | |||
| 8 | * @param {string} apiUrl - The API URL | 8 | * @param {string} apiUrl - The API URL |
| 9 | * @param {string} model - The model to use | 9 | * @param {string} model - The model to use |
| 10 | * @param {boolean} keep - Keep the model loaded in memory | 10 | * @param {boolean} keep - Keep the model loaded in memory |
| 11 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 11 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 12 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 12 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 13 | */ | 13 | */ |
| 14 | async function getOllamaBatchVector(texts, apiUrl, model, keep, directories) { | 14 | export async function getOllamaBatchVector(texts, apiUrl, model, keep, directories) { |
| 15 | const result = []; | 15 | const result = []; |
| 16 | for (const text of texts) { | 16 | for (const text of texts) { |
| 17 | const vector = await getOllamaVector(text, apiUrl, model, keep, directories); | 17 | const vector = await getOllamaVector(text, apiUrl, model, keep, directories); |
| @@ -26,10 +26,10 @@ async function getOllamaBatchVector(texts, apiUrl, model, keep, directories) { | |||
| 26 | * @param {string} apiUrl - The API URL | 26 | * @param {string} apiUrl - The API URL |
| 27 | * @param {string} model - The model to use | 27 | * @param {string} model - The model to use |
| 28 | * @param {boolean} keep - Keep the model loaded in memory | 28 | * @param {boolean} keep - Keep the model loaded in memory |
| 29 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 29 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 30 | * @returns {Promise<number[]>} - The vector for the text | 30 | * @returns {Promise<number[]>} - The vector for the text |
| 31 | */ | 31 | */ |
| 32 | async function getOllamaVector(text, apiUrl, model, keep, directories) { | 32 | export async function getOllamaVector(text, apiUrl, model, keep, directories) { |
| 33 | const url = new URL(apiUrl); | 33 | const url = new URL(apiUrl); |
| 34 | url.pathname = '/api/embeddings'; | 34 | url.pathname = '/api/embeddings'; |
| 35 | 35 | ||
| @@ -54,6 +54,7 @@ async function getOllamaVector(text, apiUrl, model, keep, directories) { | |||
| 54 | throw new Error(`Ollama: Failed to get vector for text: ${response.statusText} ${responseText}`); | 54 | throw new Error(`Ollama: Failed to get vector for text: ${response.statusText} ${responseText}`); |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | /** @type {any} */ | ||
| 57 | const data = await response.json(); | 58 | const data = await response.json(); |
| 58 | 59 | ||
| 59 | if (!Array.isArray(data?.embedding)) { | 60 | if (!Array.isArray(data?.embedding)) { |
| @@ -62,8 +63,3 @@ async function getOllamaVector(text, apiUrl, model, keep, directories) { | |||
| 62 | 63 | ||
| 63 | return data.embedding; | 64 | return data.embedding; |
| 64 | } | 65 | } |
| 65 | |||
| 66 | module.exports = { | ||
| 67 | getOllamaBatchVector, | ||
| 68 | getOllamaVector, | ||
| 69 | }; | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { SECRET_KEYS, readSecret } = require('../endpoints/secrets'); | 2 | import { SECRET_KEYS, readSecret } from '../endpoints/secrets.js'; |
| 3 | 3 | ||
| 4 | const SOURCES = { | 4 | const SOURCES = { |
| 5 | 'togetherai': { | 5 | 'togetherai': { |
| @@ -23,11 +23,11 @@ const SOURCES = { | |||
| 23 | * Gets the vector for the given text batch from an OpenAI compatible endpoint. | 23 | * Gets the vector for the given text batch from an OpenAI compatible endpoint. |
| 24 | * @param {string[]} texts - The array of texts to get the vector for | 24 | * @param {string[]} texts - The array of texts to get the vector for |
| 25 | * @param {string} source - The source of the vector | 25 | * @param {string} source - The source of the vector |
| 26 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 26 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 27 | * @param {string} model - The model to use for the embedding | 27 | * @param {string} model - The model to use for the embedding |
| 28 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 28 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 29 | */ | 29 | */ |
| 30 | async function getOpenAIBatchVector(texts, source, directories, model = '') { | 30 | export async function getOpenAIBatchVector(texts, source, directories, model = '') { |
| 31 | const config = SOURCES[source]; | 31 | const config = SOURCES[source]; |
| 32 | 32 | ||
| 33 | if (!config) { | 33 | if (!config) { |
| @@ -61,6 +61,7 @@ async function getOpenAIBatchVector(texts, source, directories, model = '') { | |||
| 61 | throw new Error('API request failed'); | 61 | throw new Error('API request failed'); |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | /** @type {any} */ | ||
| 64 | const data = await response.json(); | 65 | const data = await response.json(); |
| 65 | 66 | ||
| 66 | if (!Array.isArray(data?.data)) { | 67 | if (!Array.isArray(data?.data)) { |
| @@ -79,16 +80,11 @@ async function getOpenAIBatchVector(texts, source, directories, model = '') { | |||
| 79 | * Gets the vector for the given text from an OpenAI compatible endpoint. | 80 | * Gets the vector for the given text from an OpenAI compatible endpoint. |
| 80 | * @param {string} text - The text to get the vector for | 81 | * @param {string} text - The text to get the vector for |
| 81 | * @param {string} source - The source of the vector | 82 | * @param {string} source - The source of the vector |
| 82 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 83 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 83 | * @param {string} model - The model to use for the embedding | 84 | * @param {string} model - The model to use for the embedding |
| 84 | * @returns {Promise<number[]>} - The vector for the text | 85 | * @returns {Promise<number[]>} - The vector for the text |
| 85 | */ | 86 | */ |
| 86 | async function getOpenAIVector(text, source, directories, model = '') { | 87 | export async function getOpenAIVector(text, source, directories, model = '') { |
| 87 | const vectors = await getOpenAIBatchVector([text], source, directories, model); | 88 | const vectors = await getOpenAIBatchVector([text], source, directories, model); |
| 88 | return vectors[0]; | 89 | return vectors[0]; |
| 89 | } | 90 | } |
| 90 | |||
| 91 | module.exports = { | ||
| 92 | getOpenAIVector, | ||
| 93 | getOpenAIBatchVector, | ||
| 94 | }; | ||
| @@ -1,16 +1,16 @@ | |||
| 1 | const fetch = require('node-fetch').default; | 1 | import fetch from 'node-fetch'; |
| 2 | const { setAdditionalHeadersByType } = require('../additional-headers'); | 2 | import { setAdditionalHeadersByType } from '../additional-headers.js'; |
| 3 | const { TEXTGEN_TYPES } = require('../constants'); | 3 | import { TEXTGEN_TYPES } from '../constants.js'; |
| 4 | 4 | ||
| 5 | /** | 5 | /** |
| 6 | * Gets the vector for the given text from VLLM | 6 | * Gets the vector for the given text from VLLM |
| 7 | * @param {string[]} texts - The array of texts to get the vectors for | 7 | * @param {string[]} texts - The array of texts to get the vectors for |
| 8 | * @param {string} apiUrl - The API URL | 8 | * @param {string} apiUrl - The API URL |
| 9 | * @param {string} model - The model to use | 9 | * @param {string} model - The model to use |
| 10 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 10 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 11 | * @returns {Promise<number[][]>} - The array of vectors for the texts | 11 | * @returns {Promise<number[][]>} - The array of vectors for the texts |
| 12 | */ | 12 | */ |
| 13 | async function getVllmBatchVector(texts, apiUrl, model, directories) { | 13 | export async function getVllmBatchVector(texts, apiUrl, model, directories) { |
| 14 | const url = new URL(apiUrl); | 14 | const url = new URL(apiUrl); |
| 15 | url.pathname = '/v1/embeddings'; | 15 | url.pathname = '/v1/embeddings'; |
| 16 | 16 | ||
| @@ -31,6 +31,7 @@ async function getVllmBatchVector(texts, apiUrl, model, directories) { | |||
| 31 | throw new Error(`VLLM: Failed to get vector for text: ${response.statusText} ${responseText}`); | 31 | throw new Error(`VLLM: Failed to get vector for text: ${response.statusText} ${responseText}`); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | /** @type {any} */ | ||
| 34 | const data = await response.json(); | 35 | const data = await response.json(); |
| 35 | 36 | ||
| 36 | if (!Array.isArray(data?.data)) { | 37 | if (!Array.isArray(data?.data)) { |
| @@ -49,15 +50,10 @@ async function getVllmBatchVector(texts, apiUrl, model, directories) { | |||
| 49 | * @param {string} text - The text to get the vector for | 50 | * @param {string} text - The text to get the vector for |
| 50 | * @param {string} apiUrl - The API URL | 51 | * @param {string} apiUrl - The API URL |
| 51 | * @param {string} model - The model to use | 52 | * @param {string} model - The model to use |
| 52 | * @param {import('../users').UserDirectoryList} directories - The directories object for the user | 53 | * @param {import('../users.js').UserDirectoryList} directories - The directories object for the user |
| 53 | * @returns {Promise<number[]>} - The vector for the text | 54 | * @returns {Promise<number[]>} - The vector for the text |
| 54 | */ | 55 | */ |
| 55 | async function getVllmVector(text, apiUrl, model, directories) { | 56 | export async function getVllmVector(text, apiUrl, model, directories) { |
| 56 | const vectors = await getVllmBatchVector([text], apiUrl, model, directories); | 57 | const vectors = await getVllmBatchVector([text], apiUrl, model, directories); |
| 57 | return vectors[0]; | 58 | return vectors[0]; |
| 58 | } | 59 | } |
| 59 | |||
| 60 | module.exports = { | ||
| 61 | getVllmBatchVector, | ||
| 62 | getVllmVector, | ||
| 63 | }; | ||