Merge branch 'SillyTavern:dev' into dev

This commit is contained in:
BlipRanger
2023-07-09 20:16:37 -04:00
committed by GitHub
7 changed files with 215 additions and 26 deletions

7
.github/readme.md vendored
View File

@@ -65,7 +65,7 @@ Get in touch with the developers directly:
* Chat bookmarks / branching (duplicates the dialogue in its current state) * Chat bookmarks / branching (duplicates the dialogue in its current state)
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets * Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
* World Info support: create rich lore or save tokens on your character card * World Info support: create rich lore or save tokens on your character card
* Window AI browser extension support (run models like Claude, GPT 4): https://windowai.io/ * Window AI browser extension support (run models like Claude, GPT 4): <https://windowai.io/>
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection * [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
* [AI Horde](https://horde.koboldai.net/) connection * [AI Horde](https://horde.koboldai.net/) connection
* [Poe.com](https://poe.com) (ChatGPT / Claude) connection * [Poe.com](https://poe.com) (ChatGPT / Claude) connection
@@ -224,7 +224,7 @@ If you (or someone else) want to connect to your hosted ST while not being on th
* While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device. * While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device.
### 3. Connect the remote device to the ST host machine. ### 3. Connect the remote device to the ST host machine
Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser. Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.
@@ -303,6 +303,7 @@ GNU Affero General Public License for more details.**
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/> * KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
* Noto Sans font by Google (OFL license) * Noto Sans font by Google (OFL license)
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) * Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde * AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>
* Linux startup script by AlpinDale * Linux startup script by AlpinDale
* Thanks paniphons for providing a FAQ document * Thanks paniphons for providing a FAQ document
* 10K Discord Users Celebratory Background by @kallmeflocc

2
.gitignore vendored
View File

@@ -21,3 +21,5 @@ secrets.json
/dist /dist
poe_device.json poe_device.json
/backups/ /backups/
poe-error.log
poe-success.log

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

View File

@@ -19,6 +19,7 @@ import {
system_message_types, system_message_types,
replaceCurrentChat, replaceCurrentChat,
setCharacterId, setCharacterId,
generateQuietPrompt,
} from "../script.js"; } from "../script.js";
import { humanizedDateTime } from "./RossAscends-mods.js"; import { humanizedDateTime } from "./RossAscends-mods.js";
import { resetSelectedGroup } from "./group-chats.js"; import { resetSelectedGroup } from "./group-chats.js";
@@ -114,6 +115,7 @@ parser.addCommand('bubble', setBubbleModeCallback, ['bubbles'], ' sets the m
parser.addCommand('flat', setFlatModeCallback, ['default'], ' sets the message style to flat chat mode', true, true); parser.addCommand('flat', setFlatModeCallback, ['default'], ' sets the message style to flat chat mode', true, true);
parser.addCommand('continue', continueChatCallback, ['cont'], ' continues the last message in the chat', true, true); parser.addCommand('continue', continueChatCallback, ['cont'], ' continues the last message in the chat', true, true);
parser.addCommand('go', goToCharacterCallback, ['char'], '<span class="monospace">(name)</span> opens up a chat with the character by its name', true, true); parser.addCommand('go', goToCharacterCallback, ['char'], '<span class="monospace">(name)</span> opens up a chat with the character by its name', true, true);
parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">(prompt)</span> generates a system message using a specified prompt', true, true);
const NARRATOR_NAME_KEY = 'narrator_name'; const NARRATOR_NAME_KEY = 'narrator_name';
const NARRATOR_NAME_DEFAULT = 'System'; const NARRATOR_NAME_DEFAULT = 'System';
@@ -166,6 +168,20 @@ function continueChatCallback() {
$('#option_continue').trigger('click', { fromSlashCommand: true }); $('#option_continue').trigger('click', { fromSlashCommand: true });
} }
async function generateSystemMessage(_, prompt) {
$('#send_textarea').val('');
if (!prompt) {
console.warn('WARN: No prompt provided for /sysgen command');
toastr.warning('You must provide a prompt for the system message');
return;
}
toastr.info('Please wait', 'Generating...');
const message = await generateQuietPrompt(prompt);
sendNarratorMessage(_, message);
}
function syncCallback() { function syncCallback() {
$('#sync_name_button').trigger('click'); $('#sync_name_button').trigger('click');
} }

View File

@@ -3725,13 +3725,23 @@ app.post('/viewsecrets', jsonParser, async (_, response) => {
}); });
app.post('/horde_samplers', jsonParser, async (_, response) => { app.post('/horde_samplers', jsonParser, async (_, response) => {
const samplers = Object.values(ai_horde.ModelGenerationInputStableSamplers); try {
response.send(samplers); const samplers = Object.values(ai_horde.ModelGenerationInputStableSamplers);
response.send(samplers);
} catch (error) {
console.error(error);
response.sendStatus(500);
}
}); });
app.post('/horde_models', jsonParser, async (_, response) => { app.post('/horde_models', jsonParser, async (_, response) => {
const models = await ai_horde.getModels(); try {
response.send(models); const models = await ai_horde.getModels();
response.send(models);
} catch (error) {
console.error(error);
response.sendStatus(500);
}
}); });
app.post('/horde_userinfo', jsonParser, async (_, response) => { app.post('/horde_userinfo', jsonParser, async (_, response) => {

View File

@@ -266,25 +266,82 @@ function generate_payload(query, variables) {
} }
async function request_with_retries(method, attempts = 10) { async function request_with_retries(method, attempts = 10) {
const url = '';
for (let i = 0; i < attempts; i++) { for (let i = 0; i < attempts; i++) {
//console.log(method)
try { try {
const response = await method(); const response = await method();
if (response.status === 200) { if (response.status === 200) {
const circularReference = new Set();
const responseString = JSON.stringify(response, function (key, value) {
if (typeof value === 'object' && value !== null) {
if (circularReference.has(value)) {
return;
}
circularReference.add(value);
}
if (key === 'data' && typeof value === 'object' && value !== null) {
return '[removed data spam]';
}
if (typeof value === 'object' && value !== null) {
return Array.isArray(value) ? value : { ...value };
}
if (key === "Cookie" || key === "set-cookie" || key === "Set-Cookie") {
return "[PB COOKIE DATA REDACTED BY ST CODE]"
}
if (typeof value === 'string' && value.includes('p-b=')) {
const startIndex = value.indexOf('p-b=');
const endIndex = value.indexOf(';', startIndex);
if (endIndex === -1) {
return value.substring(0, startIndex) + '[P-B COOKIE REDACTED BY ST]';
}
return value.substring(0, startIndex) + '[P-B COOKIE REDACTED BY ST]' + value.substring(endIndex);
}
if (typeof value === 'string' && value.includes('__cf_bm=')) {
const startIndex = value.indexOf('__cf_bm=');
const endIndex = value.indexOf(';', startIndex);
if (endIndex === -1) {
return value.substring(0, startIndex) + '[Cloudflare COOKIE REDACTED BY ST]';
}
return value.substring(0, startIndex) + '[CloudFlare COOKIE REDACTED BY ST]' + value.substring(endIndex);
}
return value;
}, 4);
fs.writeFile('poe-success.log', responseString, 'utf-8', () => {
//console.log('Successful query logged to poe-success.log');
});
return response; return response;
} }
logger.warn(`Server returned a status code of ${response.status} while downloading ${url}. Retrying (${i + 1}/${attempts})...`);
//this never actually gets seen as any non-200 response jumps to the catch code
logger.warn(`Server returned a status code of ${response.status} while downloading. Retrying (${i + 1}/${attempts})...`);
} catch (err) {
const circularReference = new Set();
const errString = JSON.stringify(err, function (key, value) {
if (key === 'data' && Array.isArray(value)) {
return '[removed data spam]';
} else if (typeof value === 'object' && value !== null) {
if (circularReference.has(value)) {
return '[Circular]';
}
circularReference.add(value);
}
if (key === "Cookie") {
return "[COOKIE REDACTED BY ST CODE]"
}
return value;
}, 4);
fs.writeFile('poe-error.log', errString, 'utf-8', (err) => {
if (err) throw err;
console.log('Error saved to poe-error.log');
});
await delay(100)
} }
catch (err) {
//console.log(`-------------------ERROR-------------------`)
//console.log(logObjectStructure(err, 0, 2));
console.log(`Error on request attempt ${i + 1}`)
//console.log(`-------------------------------------------`)
}
await delay(100)
} }
throw new Error(`Failed to download ${url} too many times.`); throw new Error(`Failed to download too many times.`);
} }
function findKey(obj, key, path = []) { function findKey(obj, key, path = []) {
@@ -303,17 +360,16 @@ function findKey(obj, key, path = []) {
} }
function logObjectStructure(obj, indent = 0, depth = Infinity) { function logObjectStructure(obj, indent = 0, depth = Infinity) {
let result = "";
const keys = Object.keys(obj); const keys = Object.keys(obj);
keys.forEach((key) => { keys.forEach((key) => {
console.log(`${' '.repeat(indent)}${key}`); result += `${' '.repeat(indent)}${key}\n`;
if (typeof obj[key] === 'object' && obj[key] !== null && indent < depth) { if (typeof obj[key] === 'object' && obj[key] !== null && indent < depth) {
logObjectStructure(obj[key], indent + 1, depth); result += logObjectStructure(obj[key], indent + 1, depth);
} }
}); });
return result;
} }
class Client { class Client {
gql_url = "https://poe.com/api/gql_POST"; gql_url = "https://poe.com/api/gql_POST";
gql_recv_url = "https://poe.com/api/receive_POST"; gql_recv_url = "https://poe.com/api/receive_POST";
@@ -372,13 +428,13 @@ class Client {
}; };
this.session.defaults.headers.common = this.headers; this.session.defaults.headers.common = this.headers;
[this.next_data, this.channel] = await Promise.all([this.get_next_data(), this.get_channel_data()]); [this.next_data, this.channel] = await Promise.all([this.get_next_data(), this.get_channel_data()]);
this.bots = await this.get_bots();
this.bot_names = this.get_bot_names();
this.gql_headers = { this.gql_headers = {
"poe-formkey": this.formkey, "poe-formkey": this.formkey,
"poe-tchannel": this.channel["channel"], "poe-tchannel": this.channel["channel"],
...this.headers, ...this.headers,
}; };
this.bots = await this.get_bots();
this.bot_names = this.get_bot_names();
if (this.device_id === null) { if (this.device_id === null) {
this.device_id = this.get_device_id(); this.device_id = this.get_device_id();
} }
@@ -463,10 +519,25 @@ class Client {
throw new Error('Invalid token.'); throw new Error('Invalid token.');
} }
const botList = viewer.availableBotsConnection.edges.map(x => x.node); const botList = viewer.availableBotsConnection.edges.map(x => x.node);
try {
const botsQuery = await this.send_query('BotSwitcherModalQuery', {});
botsQuery.data.viewer.availableBotsConnection.edges.forEach(edge => {
const bot = edge.node;
if (botList.findIndex(x => x.id === bot.id) === -1) {
botList.push(bot);
}
});
} catch (e) {
console.log(e);
}
const retries = 2; const retries = 2;
const bots = {}; const bots = {};
const promises = []; const promises = [];
for (const bot of botList.filter(x => x.deletionState == 'not_deleted')) { for (const bot of botList.filter(x => x.deletionState == 'not_deleted')) {
await delay(300)
const promise = new Promise(async (resolve, reject) => { const promise = new Promise(async (resolve, reject) => {
try { try {
const url = `https://poe.com/_next/data/${this.next_data.buildId}/${bot.displayName}.json`; const url = `https://poe.com/_next/data/${this.next_data.buildId}/${bot.displayName}.json`;
@@ -537,7 +608,6 @@ class Client {
//console.log(`------GQL HEADERS-----`) //console.log(`------GQL HEADERS-----`)
//console.log(this.gql_headers) //console.log(this.gql_headers)
//console.log(`----------------------`) //console.log(`----------------------`)
//console.log('sending query..')
const r = await request_with_retries(() => this.session.post(this.gql_url, payload, { headers: this.gql_headers })); const r = await request_with_retries(() => this.session.post(this.gql_url, payload, { headers: this.gql_headers }));
if (!(r?.data?.data)) { if (!(r?.data?.data)) {
logger.warn(`${queryName} returned an error | Retrying (${i + 1}/20)`); logger.warn(`${queryName} returned an error | Retrying (${i + 1}/20)`);

View File

@@ -0,0 +1,90 @@
query BotSwitcherModalQuery {
viewer {
...BotSwitcherModalInner_viewer
id
}
}
fragment BotHeader_bot on Bot {
displayName
isLimitedAccess
...BotImage_bot
...BotLink_bot
...IdAnnotation_node
...botHelpers_useViewerCanAccessPrivateBot
...botHelpers_useDeletion_bot
}
fragment BotHeader_viewer on Viewer {
hasActiveSubscription
}
fragment BotImage_bot on Bot {
displayName
...botHelpers_useDeletion_bot
...BotImage_useProfileImage_bot
}
fragment BotImage_useProfileImage_bot on Bot {
image {
__typename
... on LocalBotImage {
localName
}
... on UrlBotImage {
url
}
}
...botHelpers_useDeletion_bot
}
fragment BotLink_bot on Bot {
handle
}
fragment BotNavItem_bot on Bot {
botId
handle
id
...BotHeader_bot
}
fragment BotNavItem_viewer on Viewer {
...BotHeader_viewer
}
fragment BotSwitcherModalInner_viewer on Viewer {
...BotNavItem_viewer
availableBotsConnection(first: 11) {
edges {
node {
id
handle
...BotNavItem_bot
__typename
}
cursor
id
}
pageInfo {
endCursor
hasNextPage
}
id
}
}
fragment IdAnnotation_node on Node {
__isNode: __typename
id
}
fragment botHelpers_useDeletion_bot on Bot {
deletionState
}
fragment botHelpers_useViewerCanAccessPrivateBot on Bot {
isPrivateBot
viewerIsCreator
isSystemBot
}