mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'dev' of https://github.com/SillyLossy/TavernAI into dev
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,4 +18,5 @@ public/settings.json
|
||||
whitelist.txt
|
||||
.vscode
|
||||
secrets.json
|
||||
/dist
|
||||
/dist
|
||||
poe_device.json
|
||||
|
||||
@@ -3,4 +3,5 @@ node_modules/
|
||||
.DS_Store
|
||||
/thumbnails
|
||||
secrets.json
|
||||
/dist
|
||||
/dist
|
||||
poe_device.json
|
||||
|
||||
1166
public/i18n.json
1166
public/i18n.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -80,6 +80,7 @@ import {
|
||||
is_get_status_openai,
|
||||
openai_messages_count,
|
||||
getTokenCountOpenAI,
|
||||
chat_completion_sources,
|
||||
} from "./scripts/openai.js";
|
||||
|
||||
import {
|
||||
@@ -349,7 +350,7 @@ const system_messages = {
|
||||
'<i>(Not endorsed, your discretion is advised)</i>',
|
||||
'<ol>',
|
||||
'<li><a target="_blank" href="https://discord.gg/pygmalionai">Pygmalion AI Discord</a></li>',
|
||||
'<li><a target="_blank" href="https://www.characterhub.org/">CharacterHub (NSFW)</a></li>',
|
||||
'<li><a target="_blank" href="https://chub.ai/">Chub (NSFW)</a></li>',
|
||||
'</ol>',
|
||||
'<hr>',
|
||||
'<h3>Where can I get help?</h3>',
|
||||
@@ -1399,6 +1400,20 @@ function getStoppingStrings(isImpersonate, addSpace) {
|
||||
return addSpace ? result.map(x => `${x} `) : result;
|
||||
}
|
||||
|
||||
|
||||
// Background prompt generation
|
||||
export async function generateQuietPrompt(quiet_prompt) {
|
||||
return await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await Generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processCommands(message, type) {
|
||||
if (type == "regenerate" || type == "swipe" || type == 'quiet') {
|
||||
return null;
|
||||
@@ -3808,7 +3823,7 @@ function changeMainAPI() {
|
||||
main_api = selectedVal;
|
||||
online_status = "no_connection";
|
||||
|
||||
if (main_api == 'openai' && oai_settings.use_window_ai) {
|
||||
if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
$('#api_button_openai').trigger('click');
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,14 @@ import {
|
||||
send_on_enter_options,
|
||||
} from "./power-user.js";
|
||||
|
||||
import { LoadLocal, SaveLocal, ClearLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js";
|
||||
import { LoadLocal, SaveLocal, CheckLocal, LoadLocalBool } from "./f-localStorage.js";
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups } from "./group-chats.js";
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
|
||||
@@ -263,7 +264,7 @@ export function RA_CountCharTokens() {
|
||||
}
|
||||
// display the counted tokens
|
||||
if (count_tokens < 1024 && perm_tokens < 1024) {
|
||||
$("#result_info").html(count_tokens + " Tokens (" + perm_tokens + " Permanent Tokens)"); //display normal if both counts are under 1024
|
||||
$("#result_info").html(count_tokens + " Tokens (" + perm_tokens + " Permanent)"); //display normal if both counts are under 1024
|
||||
} else {
|
||||
$("#result_info").html(`
|
||||
<span class="neutral_warning">${count_tokens}</span> Tokens (<span class="neutral_warning">${perm_tokens}</span><span> Permanent Tokens)
|
||||
@@ -388,7 +389,7 @@ function RA_autoconnect(PrevApi) {
|
||||
}
|
||||
break;
|
||||
case 'openai':
|
||||
if (secret_state[SECRET_KEYS.OPENAI]) {
|
||||
if (secret_state[SECRET_KEYS.OPENAI] || secret_state[SECRET_KEYS.CLAUDE] || oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
$("#api_button_openai").click();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { generateQuietPrompt } from "../../../script.js";
|
||||
import { getContext, saveMetadataDebounced } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { stringFormat } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = 'backgrounds';
|
||||
@@ -87,6 +90,30 @@ function onSelectBackgroundClick() {
|
||||
}
|
||||
}
|
||||
|
||||
const autoBgPrompt = `Pause your roleplay and choose a location ONLY from the provided list that is the most suitable for the current scene. Do not output any other text:\n{0}`;
|
||||
|
||||
async function autoBackgroundCommand() {
|
||||
const options = Array.from(document.querySelectorAll('.BGSampleTitle')).map(x => ({ element: x, text: x.innerText.trim() })).filter(x => x.text.length > 0);
|
||||
if (options.length == 0) {
|
||||
toastr.warning('No backgrounds to choose from. Please upload some images to the "backgrounds" folder.');
|
||||
return;
|
||||
}
|
||||
|
||||
const list = options.map(option => `- ${option.text}`).join('\n');
|
||||
const prompt = stringFormat(autoBgPrompt, list);
|
||||
const reply = await generateQuietPrompt(prompt);
|
||||
const fuse = new Fuse(options, { keys: ['text'] });
|
||||
const bestMatch = fuse.search(reply, { limit: 1 });
|
||||
|
||||
if (bestMatch.length == 0) {
|
||||
toastr.warning('No match found. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('Automatically choosing background:', bestMatch);
|
||||
bestMatch[0].item.element.click();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
function addSettings() {
|
||||
const html = `
|
||||
@@ -111,6 +138,16 @@ $(document).ready(function () {
|
||||
Any background image selected while lock is engaged will be saved automatically.
|
||||
</small>
|
||||
</div>
|
||||
<div class="background_controls">
|
||||
<div id="auto_background" class="menu_button">
|
||||
<i class="fa-solid fa-wand-magic"></i>
|
||||
Auto
|
||||
</div>
|
||||
<small>
|
||||
Automatically select a background based on the chat context.<br>
|
||||
Respects the "Lock" setting state.
|
||||
</small>
|
||||
</div>
|
||||
<div>Preview</div>
|
||||
<div id="custom_bg_preview">
|
||||
</div>
|
||||
@@ -122,8 +159,12 @@ $(document).ready(function () {
|
||||
$('#lock_background').on('click', onLockBackgroundClick);
|
||||
$('#unlock_background').on('click', onUnlockBackgroundClick);
|
||||
$(document).on("click", ".bg_example", onSelectBackgroundClick);
|
||||
$('#auto_background').on("click", autoBackgroundCommand);
|
||||
}
|
||||
|
||||
addSettings();
|
||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||
registerSlashCommand('lock', onLockBackgroundClick, [], " – locks a background for the currently selected chat", true, true);
|
||||
registerSlashCommand('unlock', onUnlockBackgroundClick, [], ' – unlocks a background for the currently selected chat', true, true);
|
||||
registerSlashCommand('autobg', autoBackgroundCommand, ['bgauto'], ' – automatically changes the background based on the chat context', true, true);
|
||||
});
|
||||
|
||||
@@ -904,7 +904,9 @@ function setExpressionOverrideHtml(forceClear = false) {
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<p class="offline_mode">You are in offline mode. Click on the image below to set the expression.</p>
|
||||
<div class="offline_mode">
|
||||
<small>You are in offline mode. Click on the image below to set the expression.</small>
|
||||
</div>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
|
||||
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
|
||||
|
||||
@@ -143,11 +143,6 @@ img.expression.default {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.expression_settings p {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.expression_settings label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -171,4 +166,4 @@ img.expression.default {
|
||||
div.expression {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
send_date: 0,
|
||||
}
|
||||
);
|
||||
newChat.push(...queriedMessages.map(m => JSON.parse(m.meta)));
|
||||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
@@ -437,7 +437,7 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
if (selectedStrategy === 'original') {
|
||||
//removes .length # messages from the start of 'kept messages'
|
||||
//replaces them with chromaDB results (with no separator)
|
||||
newChat.push(...queriedMessages.map(m => JSON.parse(m.meta)));
|
||||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
chat.splice(0, messagesToStore.length, ...newChat);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import {
|
||||
substituteParams,
|
||||
eventSource,
|
||||
event_types,
|
||||
generateQuietPrompt,
|
||||
} from "../../../script.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
|
||||
const MODULE_NAME = "Objective"
|
||||
|
||||
@@ -12,7 +14,7 @@ const MODULE_NAME = "Objective"
|
||||
let globalObjective = ""
|
||||
let globalTasks = []
|
||||
let currentChatId = ""
|
||||
let currentTask = {}
|
||||
let currentTask = null
|
||||
let checkCounter = 0
|
||||
|
||||
|
||||
@@ -38,90 +40,70 @@ const objectivePrompts = {
|
||||
`
|
||||
}
|
||||
|
||||
const injectPrompts = {
|
||||
"task": "Your current task is [{{task}}]. Balance existing roleplay with completing this task."
|
||||
}
|
||||
const extensionPrompt = "Your current task is [{{task}}]. Balance existing roleplay with completing this task."
|
||||
|
||||
// Background prompt generation
|
||||
async function generateQuietPrompt(quiet_prompt) {
|
||||
return await new Promise(
|
||||
async function promptPromise(resolve, reject) {
|
||||
try {
|
||||
await getContext().generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
|
||||
}
|
||||
catch {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//###############################//
|
||||
//# Task Management #//
|
||||
//###############################//
|
||||
|
||||
// Accepts optional position. Defaults to adding to end of list.
|
||||
function addTask(description, position = null) {
|
||||
position = position != null ? position : position = globalTasks.length
|
||||
globalTasks.splice(position, 0, {
|
||||
"description": description,
|
||||
"completed": false
|
||||
})
|
||||
// Accepts optional index. Defaults to adding to end of list.
|
||||
function addTask(description, index = null) {
|
||||
index = index != null ? index: index = globalTasks.length
|
||||
globalTasks.splice(index, 0, new ObjectiveTask(
|
||||
{description: description}
|
||||
))
|
||||
saveState()
|
||||
}
|
||||
|
||||
// Get a task either by index or task description. Return current task if none specified
|
||||
function getTask(index = null, taskDescription = null) {
|
||||
let task = {}
|
||||
if (index == null && taskDescription == null) {
|
||||
task = currentTask
|
||||
} else if (index != null) {
|
||||
task = globalObjective[index]
|
||||
} else if (taskDescription != null) {
|
||||
task = globalTasks.find(task => {
|
||||
return task.description == description ? true : false
|
||||
})
|
||||
// Return the task and index or throw an error
|
||||
function getTaskById(taskId){
|
||||
if (taskId == null) {
|
||||
throw `Null task id`
|
||||
}
|
||||
const index = globalTasks.findIndex((task) => task.id === taskId);
|
||||
if (index !== -1) {
|
||||
return { task: globalTasks[index], index: index };
|
||||
} else {
|
||||
throw `Cannot find task with ${taskId}`
|
||||
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
function deleteTask(index) {
|
||||
function deleteTask(taskId){
|
||||
const { task, index } = getTaskById(taskId)
|
||||
|
||||
globalTasks.splice(index, 1)
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
}
|
||||
|
||||
// Complete the current task, setting next task to next incomplete task
|
||||
function completeTask(task) {
|
||||
task.completed = true
|
||||
console.info(`Task successfully completed: ${JSON.stringify(task)}`)
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
}
|
||||
|
||||
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
|
||||
async function generateTasks() {
|
||||
const prompt = substituteParams(objectivePrompts["createTask"].replace(/{{objective}}/gi, globalObjective));
|
||||
console.log(`Generating tasks for objective with prompt`)
|
||||
toastr.info('Generating tasks for objective', 'Please wait...');
|
||||
const taskResponse = await generateQuietPrompt(prompt)
|
||||
|
||||
// Clear all existing global tasks when generating
|
||||
globalTasks = []
|
||||
const numberedListPattern = /^\d+\./
|
||||
|
||||
// Add numbered tasks, store them without the numbers.
|
||||
// Create tasks from generated task list
|
||||
for (const task of taskResponse.split('\n').map(x => x.trim())) {
|
||||
if (task.match(numberedListPattern) != null) {
|
||||
addTask(task.replace(numberedListPattern, '').trim())
|
||||
addTask(task.replace(numberedListPattern,"").trim())
|
||||
}
|
||||
}
|
||||
updateUiTaskList()
|
||||
console.info(`Response for Objective: '${globalObjective}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks, null, 2)} `)
|
||||
console.info(`Response for Objective: '${globalObjective}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
|
||||
toastr.success(`Generated ${globalTasks.length} tasks`, 'Done!');
|
||||
}
|
||||
|
||||
// Call Quiet Generate to check if a task is completed
|
||||
async function checkTaskCompleted() {
|
||||
// Make sure there are tasks
|
||||
if (Object.keys(currentTask).length == 0) {
|
||||
// Make sure there are tasks
|
||||
if (jQuery.isEmptyObject(currentTask)) {
|
||||
return
|
||||
}
|
||||
checkCounter = $('#objective-check-frequency').val()
|
||||
@@ -131,8 +113,8 @@ async function checkTaskCompleted() {
|
||||
|
||||
// Check response if task complete
|
||||
if (taskResponse.includes("true")) {
|
||||
console.info(`Character determined task '${JSON.stringify(currentTask)} is completed.`)
|
||||
completeTask(getTask())
|
||||
console.info(`Character determined task '${JSON.stringify(currentTask.toSaveState())} is completed.`)
|
||||
currentTask.completeTask()
|
||||
} else if (!(taskResponse.includes("false"))) {
|
||||
console.warn(`checkTaskCompleted response did not contain true or false. taskResponse: ${taskResponse}`)
|
||||
} else {
|
||||
@@ -142,21 +124,26 @@ async function checkTaskCompleted() {
|
||||
|
||||
|
||||
// Set a task in extensionPrompt context. Defaults to first incomplete
|
||||
function setCurrentTask(index = null) {
|
||||
function setCurrentTask(taskId = null) {
|
||||
const context = getContext();
|
||||
currentTask = {};
|
||||
|
||||
if (index === null) {
|
||||
// Set current task to either the next incomplete task, or the index
|
||||
if (taskId === null) {
|
||||
currentTask = globalTasks.find(task => !task.completed) || {};
|
||||
} else if (index >= 0 && index < globalTasks.length) {
|
||||
} else {
|
||||
const { _, index } = getTaskById(taskId)
|
||||
currentTask = globalTasks[index];
|
||||
}
|
||||
|
||||
const { description } = currentTask;
|
||||
const injectPromptsTask = injectPrompts["task"].replace(/{{task}}/gi, description);
|
||||
// Get the task description and add to extension prompt
|
||||
const description = currentTask.description || null;
|
||||
|
||||
// Now update the extension prompt
|
||||
|
||||
if (description) {
|
||||
context.setExtensionPrompt(MODULE_NAME, injectPromptsTask, 1, $('#objective-chat-depth').val());
|
||||
const extensionPromptText = extensionPrompt.replace(/{{task}}/gi, description);
|
||||
context.setExtensionPrompt(MODULE_NAME, extensionPromptText, 1, $('#objective-chat-depth').val());
|
||||
console.info(`Current task in context.extensionPrompts.Objective is ${JSON.stringify(context.extensionPrompts.Objective)}`);
|
||||
} else {
|
||||
context.setExtensionPrompt(MODULE_NAME, '');
|
||||
@@ -166,7 +153,114 @@ function setCurrentTask(index = null) {
|
||||
saveState();
|
||||
}
|
||||
|
||||
let taskIdCounter = 0
|
||||
function getNextTaskId(){
|
||||
// Make sure id does not exist
|
||||
while (globalTasks.find(task => task.id == taskIdCounter) != undefined) {
|
||||
taskIdCounter += 1
|
||||
}
|
||||
const nextId = taskIdCounter
|
||||
console.log(`TaskID assigned: ${nextId}`)
|
||||
taskIdCounter += 1
|
||||
return nextId
|
||||
}
|
||||
class ObjectiveTask {
|
||||
id
|
||||
description
|
||||
completed
|
||||
parent
|
||||
children
|
||||
|
||||
// UI Elements
|
||||
taskHtml
|
||||
descriptionSpan
|
||||
completedCheckbox
|
||||
deleteTaskButton
|
||||
addTaskButton
|
||||
|
||||
constructor ({id=undefined, description, completed=false, parent=null}) {
|
||||
this.description = description
|
||||
this.parent = parent
|
||||
this.children = []
|
||||
this.completed = completed
|
||||
|
||||
// Generate a new ID if none specified
|
||||
if (id==undefined){
|
||||
this.id = getNextTaskId()
|
||||
} else {
|
||||
this.id=id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Complete the current task, setting next task to next incomplete task
|
||||
completeTask() {
|
||||
this.completed = true
|
||||
console.info(`Task successfully completed: ${JSON.stringify(this.description)}`)
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
}
|
||||
|
||||
// Add a single task to the UI and attach event listeners for user edits
|
||||
addUiElement() {
|
||||
const template = `
|
||||
<div id="objective-task-label-${this.id}" class="flex1 checkbox_label">
|
||||
<input id="objective-task-complete-${this.id}" type="checkbox">
|
||||
<span class="text_pole" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span>
|
||||
<div id="objective-task-delete-${this.id}" class="objective-task-button fa-solid fa-xmark fa-2x" title="Delete Task"></div>
|
||||
<div id="objective-task-add-${this.id}" class="objective-task-button fa-solid fa-plus fa-2x" title="Add Task"></div>
|
||||
</div><br>
|
||||
`;
|
||||
|
||||
// Add the filled out template
|
||||
$('#objective-tasks').append(template);
|
||||
|
||||
this.completedCheckbox = $(`#objective-task-complete-${this.id}`);
|
||||
this.descriptionSpan = $(`#objective-task-description-${this.id}`);
|
||||
this.addButton = $(`#objective-task-add-${this.id}`);
|
||||
this.deleteButton = $(`#objective-task-delete-${this.id}`);
|
||||
|
||||
// Add event listeners and set properties
|
||||
$(`#objective-task-complete-${this.id}`).prop('checked', this.completed);
|
||||
$(`#objective-task-complete-${this.id}`).on('click', () => (this.onCompleteClick()));
|
||||
$(`#objective-task-description-${this.id}`).on('keyup', () => (this.onDescriptionUpdate()));
|
||||
$(`#objective-task-description-${this.id}`).on('focusout', () => (this.onDescriptionFocusout()));
|
||||
$(`#objective-task-delete-${this.id}`).on('click', () => (this.onDeleteClick()));
|
||||
$(`#objective-task-add-${this.id}`).on('click', () => (this.onAddClick()));
|
||||
}
|
||||
|
||||
onCompleteClick(){
|
||||
this.completed = this.completedCheckbox.prop('checked')
|
||||
setCurrentTask();
|
||||
}
|
||||
|
||||
onDescriptionUpdate(){
|
||||
this.description = this.descriptionSpan.text();
|
||||
}
|
||||
onDescriptionFocusout(){
|
||||
setCurrentTask();
|
||||
}
|
||||
|
||||
onDeleteClick(){
|
||||
deleteTask(this.id);
|
||||
}
|
||||
|
||||
onAddClick(){
|
||||
const {_, index} = getTaskById(this.id)
|
||||
addTask("", index + 1);
|
||||
setCurrentTask();
|
||||
updateUiTaskList();
|
||||
}
|
||||
|
||||
toSaveState() {
|
||||
return {
|
||||
"id":this.id,
|
||||
"description":this.description,
|
||||
"completed":this.completed,
|
||||
"parent": this.parent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//###############################//
|
||||
//# UI AND Settings #//
|
||||
@@ -194,9 +288,12 @@ function saveState() {
|
||||
currentChatId = context.chatId
|
||||
}
|
||||
|
||||
// Convert globalTasks for saving
|
||||
const tasks = globalTasks.map(task => {return task.toSaveState()})
|
||||
|
||||
chat_metadata['objective'] = {
|
||||
objective: globalObjective,
|
||||
tasks: globalTasks,
|
||||
tasks: tasks,
|
||||
checkFrequency: $('#objective-check-frequency').val(),
|
||||
chatDepth: $('#objective-chat-depth').val(),
|
||||
hideTasks: $('#objective-hide-tasks').prop('checked'),
|
||||
@@ -208,11 +305,11 @@ function saveState() {
|
||||
// Dump core state
|
||||
function debugObjectiveExtension() {
|
||||
console.log(JSON.stringify({
|
||||
"currentTask": currentTask,
|
||||
"currentTask": currentTask.toSaveState(),
|
||||
"currentChatId": currentChatId,
|
||||
"checkCounter": checkCounter,
|
||||
"globalObjective": globalObjective,
|
||||
"globalTasks": globalTasks,
|
||||
"globalTasks": globalTasks.map(v => {return v.toSaveState()}),
|
||||
"extension_settings": chat_metadata['objective'],
|
||||
}, null, 2))
|
||||
}
|
||||
@@ -220,55 +317,13 @@ function debugObjectiveExtension() {
|
||||
window.debugObjectiveExtension = debugObjectiveExtension
|
||||
|
||||
|
||||
// Add a single task to the UI and attach event listeners for user edits
|
||||
function addUiTask(taskIndex, taskComplete, taskDescription) {
|
||||
const template = `
|
||||
<div id="objective-task-label-${taskIndex}" class="flex1 checkbox_label">
|
||||
<span>${taskIndex}</span>
|
||||
<input id="objective-task-complete-${taskIndex}" type="checkbox">
|
||||
<span class="text_pole" style="display: block" id="objective-task-description-${taskIndex}" contenteditable>${taskDescription}</span>
|
||||
<div id="objective-task-delete-${taskIndex}" class="objective-task-button fa-solid fa-xmark fa-2x" title="Delete Task"></div>
|
||||
<div id="objective-task-add-${taskIndex}" class="objective-task-button fa-solid fa-plus fa-2x" title="Add Task"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add the filled out template
|
||||
$('#objective-tasks').append(template);
|
||||
|
||||
// Add event listeners and set properties
|
||||
$(`#objective-task-complete-${taskIndex}`).prop('checked', taskComplete);
|
||||
$(`#objective-task-complete-${taskIndex}`).on('click', event => {
|
||||
const index = Number(event.target.id.split('-').pop());
|
||||
globalTasks[index].completed = event.target.checked;
|
||||
setCurrentTask();
|
||||
});
|
||||
$(`#objective-task-description-${taskIndex}`).on('keyup', event => {
|
||||
const index = Number(event.target.id.split('-').pop());
|
||||
globalTasks[index].description = event.target.textContent;
|
||||
});
|
||||
$(`#objective-task-delete-${taskIndex}`).on('click', event => {
|
||||
const index = Number(event.target.id.split('-').pop());
|
||||
deleteTask(index)
|
||||
});
|
||||
$(`#objective-task-add-${taskIndex}`).on('click', event => {
|
||||
const index = Number(event.target.id.split('-').pop()) + 1;
|
||||
addTask("", index)
|
||||
setCurrentTask()
|
||||
updateUiTaskList()
|
||||
});
|
||||
}
|
||||
|
||||
// Populate UI task list
|
||||
function updateUiTaskList() {
|
||||
$('#objective-tasks').empty()
|
||||
// Show tasks if there are any
|
||||
if (globalTasks.length > 0) {
|
||||
for (const index in globalTasks) {
|
||||
addUiTask(
|
||||
index,
|
||||
globalTasks[index].completed,
|
||||
globalTasks[index].description
|
||||
)
|
||||
if (globalTasks.length > 0){
|
||||
for (const task of globalTasks) {
|
||||
task.addUiElement()
|
||||
}
|
||||
} else {
|
||||
// Show button to add tasks if there are none
|
||||
@@ -283,14 +338,6 @@ function updateUiTaskList() {
|
||||
}
|
||||
}
|
||||
|
||||
function addManualTaskCheck() {
|
||||
$('#extensionsMenu').prepend(`
|
||||
<div id="objective-task-manual-check-menu-item" class="list-group-item flex-container flexGap5">
|
||||
<div id="objective-task-manual-check" class="extensionsMenuExtensionButton fa-regular fa-square-check"/></div>
|
||||
Manual Task Check
|
||||
</div>`)
|
||||
$('#objective-task-manual-check-menu-item').attr('title', 'Trigger AI check of completed tasks').on('click', checkTaskCompleted)
|
||||
}
|
||||
|
||||
// Trigger creation of new tasks with given objective.
|
||||
async function onGenerateObjectiveClick() {
|
||||
@@ -338,7 +385,14 @@ function loadSettings() {
|
||||
|
||||
// Update globals
|
||||
globalObjective = chat_metadata['objective'].objective
|
||||
globalTasks = chat_metadata['objective'].tasks
|
||||
globalTasks = chat_metadata['objective'].tasks.map(task => {
|
||||
return new ObjectiveTask({
|
||||
id: task.id,
|
||||
description: task.description,
|
||||
completed: task.completed,
|
||||
parent: task.parent,
|
||||
})
|
||||
});
|
||||
checkCounter = chat_metadata['objective'].checkFrequency
|
||||
|
||||
// Update UI elements
|
||||
@@ -352,6 +406,15 @@ function loadSettings() {
|
||||
setCurrentTask()
|
||||
}
|
||||
|
||||
function addManualTaskCheckUi() {
|
||||
$('#extensionsMenu').prepend(`
|
||||
<div id="objective-task-manual-check-menu-item" class="list-group-item flex-container flexGap5">
|
||||
<div id="objective-task-manual-check" class="extensionsMenuExtensionButton fa-regular fa-square-check"/></div>
|
||||
Manual Task Check
|
||||
</div>`)
|
||||
$('#objective-task-manual-check-menu-item').attr('title', 'Trigger AI check of completed tasks').on('click', checkTaskCompleted)
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
const settingsHtml = `
|
||||
<div class="objective-settings">
|
||||
@@ -364,18 +427,19 @@ jQuery(() => {
|
||||
<label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
|
||||
<textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
|
||||
<div class="objective_block flex-container">
|
||||
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
|
||||
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
|
||||
<label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="objective-tasks"> </div>
|
||||
<div class="objective_block margin-bot-10px">
|
||||
<div class="objective_block objective_block_control flex1 flexFlowColumn">
|
||||
<label for="objective-chat-depth">Position in Chat</label>
|
||||
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
<small>Depth</small>
|
||||
</div>
|
||||
<div class="objective_block objective_block_control flex1 flexFlowColumn">
|
||||
<br>
|
||||
<div class="objective_block objective_block_control flex1">
|
||||
|
||||
<label for="objective-check-frequency">Task Check Frequency</label>
|
||||
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
|
||||
<small>(0 = disabled)</small>
|
||||
@@ -386,7 +450,7 @@ jQuery(() => {
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
addManualTaskCheck()
|
||||
addManualTaskCheckUi()
|
||||
$('#extensions_settings').append(settingsHtml);
|
||||
$('#objective-generate').on('click', onGenerateObjectiveClick)
|
||||
$('#objective-chat-depth').on('input', onChatDepthInput)
|
||||
@@ -412,4 +476,6 @@ jQuery(() => {
|
||||
setCurrentTask();
|
||||
$('#objective-counter').text(checkCounter)
|
||||
});
|
||||
|
||||
registerSlashCommand('taskcheck', checkTaskCompleted, [], ' – checks if the current task is completed', true, true);
|
||||
});
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
|
||||
.objective_block {
|
||||
display: flex;
|
||||
/* flex-direction: row; */
|
||||
align-items: center;
|
||||
column-gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.objective_block_control {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.objective_block_control small,
|
||||
.objective_block_control label {
|
||||
width: max-content;
|
||||
@@ -39,4 +42,4 @@
|
||||
#objective-tasks span {
|
||||
margin: unset;
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
}
|
||||
|
||||
2240
public/scripts/fuse.js
Normal file
2240
public/scripts/fuse.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -81,11 +81,21 @@ const default_bias_presets = {
|
||||
const gpt3_max = 4095;
|
||||
const gpt4_max = 8191;
|
||||
const gpt4_32k_max = 32767;
|
||||
const claude_max = 7500;
|
||||
const claude_100k_max = 99000;
|
||||
const unlocked_max = 100 * 1024;
|
||||
const oai_max_temp = 2.0;
|
||||
const claude_max_temp = 1.0;
|
||||
|
||||
let biasCache = undefined;
|
||||
const tokenCache = {};
|
||||
|
||||
export const chat_completion_sources = {
|
||||
OPENAI: 'openai',
|
||||
WINDOWAI: 'windowai',
|
||||
CLAUDE: 'claude',
|
||||
};
|
||||
|
||||
const default_settings = {
|
||||
preset_settings_openai: 'Default',
|
||||
temp_openai: 0.9,
|
||||
@@ -108,10 +118,11 @@ const default_settings = {
|
||||
bias_presets: default_bias_presets,
|
||||
wi_format: default_wi_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
legacy_streaming: false,
|
||||
use_window_ai: false,
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
max_context_unlocked: false,
|
||||
};
|
||||
|
||||
@@ -137,10 +148,11 @@ const oai_settings = {
|
||||
bias_presets: default_bias_presets,
|
||||
wi_format: default_wi_format,
|
||||
openai_model: 'gpt-3.5-turbo',
|
||||
claude_model: 'claude-instant-v1',
|
||||
jailbreak_system: false,
|
||||
reverse_proxy: '',
|
||||
legacy_streaming: false,
|
||||
use_window_ai: false,
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
max_context_unlocked: false,
|
||||
};
|
||||
|
||||
@@ -505,6 +517,7 @@ function tryParseStreamingError(str) {
|
||||
checkQuotaError(data);
|
||||
|
||||
if (data.error) {
|
||||
toastr.error(response.statusText, 'API returned an error');
|
||||
throw new Error(data);
|
||||
}
|
||||
}
|
||||
@@ -624,24 +637,27 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
}
|
||||
|
||||
let logit_bias = {};
|
||||
const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
|
||||
const stream = type !== 'quiet' && oai_settings.stream_openai;
|
||||
|
||||
// If we're using the window.ai extension, use that instead
|
||||
// Doesn't support logit bias yet
|
||||
if (oai_settings.use_window_ai) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
return sendWindowAIRequest(openai_msgs_tosend, signal, stream);
|
||||
}
|
||||
|
||||
if (oai_settings.bias_preset_selected
|
||||
&& !isClaude // Claude doesn't support logit bias
|
||||
&& Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
|
||||
&& oai_settings.bias_presets[oai_settings.bias_preset_selected].length) {
|
||||
logit_bias = biasCache || await calculateLogitBias();
|
||||
biasCache = logit_bias;
|
||||
}
|
||||
|
||||
const model = isClaude ? oai_settings.claude_model : oai_settings.openai_model;
|
||||
const generate_data = {
|
||||
"messages": openai_msgs_tosend,
|
||||
"model": oai_settings.openai_model,
|
||||
"model": model,
|
||||
"temperature": parseFloat(oai_settings.temp_openai),
|
||||
"frequency_penalty": parseFloat(oai_settings.freq_pen_openai),
|
||||
"presence_penalty": parseFloat(oai_settings.pres_pen_openai),
|
||||
@@ -650,6 +666,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
"stream": stream,
|
||||
"reverse_proxy": oai_settings.reverse_proxy,
|
||||
"logit_bias": logit_bias,
|
||||
"use_claude": isClaude,
|
||||
};
|
||||
|
||||
const generate_url = '/generate_openai';
|
||||
@@ -670,6 +687,11 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
const { done, value } = await reader.read();
|
||||
let response = decoder.decode(value);
|
||||
|
||||
// Claude's streaming SSE messages are separated by \r
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
response = response.replace(/\r/g, "");
|
||||
}
|
||||
|
||||
tryParseStreamingError(response);
|
||||
|
||||
let eventList = [];
|
||||
@@ -693,7 +715,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
}
|
||||
let data = JSON.parse(event.substring(6));
|
||||
// the first and last messages are undefined, protect against that
|
||||
getMessage += data.choices[0]["delta"]["content"] || "";
|
||||
getMessage = getStreamingReply(getMessage, data);
|
||||
yield getMessage;
|
||||
}
|
||||
|
||||
@@ -709,6 +731,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
checkQuotaError(data);
|
||||
|
||||
if (data.error) {
|
||||
toastr.error(response.statusText, 'API returned an error');
|
||||
throw new Error(data);
|
||||
}
|
||||
|
||||
@@ -716,6 +739,15 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
}
|
||||
}
|
||||
|
||||
function getStreamingReply(getMessage, data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
getMessage = data.completion || "";
|
||||
} else{
|
||||
getMessage += data.choices[0]["delta"]["content"] || "";
|
||||
}
|
||||
return getMessage;
|
||||
}
|
||||
|
||||
function handleWindowError(err) {
|
||||
const text = parseWindowError(err);
|
||||
toastr.error(text, 'Window.ai returned an error');
|
||||
@@ -833,10 +865,17 @@ function countTokens(messages, full = false) {
|
||||
token_count += cachedCount;
|
||||
}
|
||||
else {
|
||||
let model = oai_settings.openai_model;
|
||||
|
||||
// We don't have a Claude tokenizer for JS yet. Turbo 3.5 should be able to handle this.
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
model = 'gpt-3.5-turbo';
|
||||
}
|
||||
|
||||
jQuery.ajax({
|
||||
async: false,
|
||||
type: 'POST', //
|
||||
url: `/tokenize_openai?model=${oai_settings.openai_model}`,
|
||||
url: `/tokenize_openai?model=${model}`,
|
||||
data: JSON.stringify([message]),
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
@@ -882,10 +921,10 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.bias_preset_selected = settings.bias_preset_selected ?? default_settings.bias_preset_selected;
|
||||
oai_settings.bias_presets = settings.bias_presets ?? default_settings.bias_presets;
|
||||
oai_settings.legacy_streaming = settings.legacy_streaming ?? default_settings.legacy_streaming;
|
||||
oai_settings.use_window_ai = settings.use_window_ai ?? default_settings.use_window_ai;
|
||||
oai_settings.max_context_unlocked = settings.max_context_unlocked ?? default_settings.max_context_unlocked;
|
||||
oai_settings.nsfw_avoidance_prompt = settings.nsfw_avoidance_prompt ?? default_settings.nsfw_avoidance_prompt;
|
||||
oai_settings.wi_format = settings.wi_format ?? default_settings.wi_format;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
|
||||
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
|
||||
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
|
||||
@@ -897,7 +936,8 @@ function loadOpenAISettings(data, settings) {
|
||||
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
|
||||
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true).trigger('change');
|
||||
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
|
||||
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||
$('#openai_max_context_counter').text(`${oai_settings.openai_max_context}`);
|
||||
|
||||
@@ -951,14 +991,13 @@ function loadOpenAISettings(data, settings) {
|
||||
}
|
||||
$('#openai_logit_bias_preset').trigger('change');
|
||||
|
||||
$('#use_window_ai').prop('checked', oai_settings.use_window_ai);
|
||||
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
|
||||
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
|
||||
$('#openai_form').toggle(!oai_settings.use_window_ai);
|
||||
}
|
||||
|
||||
async function getStatusOpen() {
|
||||
if (is_get_status_openai) {
|
||||
if (oai_settings.use_window_ai) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
let status;
|
||||
|
||||
if ('ai' in window) {
|
||||
@@ -973,6 +1012,12 @@ async function getStatusOpen() {
|
||||
return resultCheckStatusOpen();
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
let status = 'I can\'t validate your key, but I hope it is legit.';
|
||||
setOnlineStatus(status);
|
||||
return resultCheckStatusOpen();
|
||||
}
|
||||
|
||||
let data = {
|
||||
reverse_proxy: oai_settings.reverse_proxy,
|
||||
};
|
||||
@@ -1063,6 +1108,8 @@ async function saveOpenAIPreset(name, settings) {
|
||||
max_context_unlocked: settings.max_context_unlocked,
|
||||
nsfw_avoidance_prompt: settings.nsfw_avoidance_prompt,
|
||||
wi_format: settings.wi_format,
|
||||
claude_model: settings.claude_model,
|
||||
chat_completion_source: settings.chat_completion_source,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -1310,12 +1357,14 @@ function onSettingsPresetChange() {
|
||||
const updateCheckbox = (selector, value) => $(selector).prop('checked', value).trigger('input');
|
||||
|
||||
const settingsToUpdate = {
|
||||
chat_completion_source: ['#chat_completion_source', 'chat_completion_source', false],
|
||||
temperature: ['#temp_openai', 'temp_openai', false],
|
||||
frequency_penalty: ['#freq_pen_openai', 'freq_pen_openai', false],
|
||||
presence_penalty: ['#pres_pen_openai', 'pres_pen_openai', false],
|
||||
top_p: ['#top_p_openai', 'top_p_openai', false],
|
||||
max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true],
|
||||
openai_model: ['#model_openai_select', 'openai_model', false],
|
||||
claude_model: ['#model_claude_select', 'claude_model', false],
|
||||
openai_max_context: ['#openai_max_context', 'openai_max_context', false],
|
||||
openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false],
|
||||
nsfw_toggle: ['#nsfw_toggle', 'nsfw_toggle', true],
|
||||
@@ -1352,21 +1401,48 @@ function onSettingsPresetChange() {
|
||||
|
||||
function onModelChange() {
|
||||
const value = $(this).val();
|
||||
oai_settings.openai_model = value;
|
||||
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
}
|
||||
else if (value == 'gpt-4' || value == 'gpt-4-0314') {
|
||||
$('#openai_max_context').attr('max', gpt4_max);
|
||||
}
|
||||
else if (value == 'gpt-4-32k') {
|
||||
$('#openai_max_context').attr('max', gpt4_32k_max);
|
||||
}
|
||||
else {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, gpt3_max);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
oai_settings.claude_model = value;
|
||||
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
}
|
||||
else if (value.endsWith('100k')) {
|
||||
$('#openai_max_context').attr('max', claude_100k_max);
|
||||
}
|
||||
else {
|
||||
$('#openai_max_context').attr('max', claude_max);
|
||||
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, claude_max);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
}
|
||||
|
||||
$('#openai_reverse_proxy').attr('placeholder', 'https://api.anthropic.com/v1');
|
||||
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
} else {
|
||||
oai_settings.openai_model = value;
|
||||
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
}
|
||||
else if (value == 'gpt-4' || value == 'gpt-4-0314') {
|
||||
$('#openai_max_context').attr('max', gpt4_max);
|
||||
}
|
||||
else if (value == 'gpt-4-32k') {
|
||||
$('#openai_max_context').attr('max', gpt4_32k_max);
|
||||
}
|
||||
else {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, gpt3_max);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
}
|
||||
|
||||
$('#openai_reverse_proxy').attr('placeholder', 'https://api.openai.com/v1');
|
||||
|
||||
oai_settings.temp_openai = Math.min(oai_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
@@ -1396,21 +1472,36 @@ function onReverseProxyInput() {
|
||||
async function onConnectButtonClick(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
if (oai_settings.use_window_ai) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
is_get_status_openai = true;
|
||||
is_api_button_press_openai = true;
|
||||
return await getStatusOpen();
|
||||
}
|
||||
|
||||
const api_key_openai = $('#api_key_openai').val().trim();
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
const api_key_claude = $('#api_key_claude').val().trim();
|
||||
|
||||
if (api_key_openai.length) {
|
||||
await writeSecret(SECRET_KEYS.OPENAI, api_key_openai);
|
||||
if (api_key_claude.length) {
|
||||
await writeSecret(SECRET_KEYS.CLAUDE, api_key_claude);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.CLAUDE]) {
|
||||
console.log('No secret key saved for Claude');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.OPENAI]) {
|
||||
console.log('No secret key saved for OpenAI');
|
||||
return;
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.OPENAI) {
|
||||
const api_key_openai = $('#api_key_openai').val().trim();
|
||||
|
||||
if (api_key_openai.length) {
|
||||
await writeSecret(SECRET_KEYS.OPENAI, api_key_openai);
|
||||
}
|
||||
|
||||
if (!secret_state[SECRET_KEYS.OPENAI]) {
|
||||
console.log('No secret key saved for OpenAI');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$("#api_loading_openai").css("display", 'inline-block');
|
||||
@@ -1421,6 +1512,17 @@ async function onConnectButtonClick(e) {
|
||||
await getStatusOpen();
|
||||
}
|
||||
|
||||
function toggleChatCompletionForms() {
|
||||
$("#claude_form").toggle(oai_settings.chat_completion_source == chat_completion_sources.CLAUDE);
|
||||
$("#openai_form").toggle(oai_settings.chat_completion_source == chat_completion_sources.OPENAI);
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
$('#model_claude_select').trigger('change');
|
||||
} else {
|
||||
$('#model_openai_select').trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$(document).on('input', '#temp_openai', function () {
|
||||
oai_settings.temp_openai = $(this).val();
|
||||
@@ -1589,9 +1691,9 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#use_window_ai').on('input', function () {
|
||||
oai_settings.use_window_ai = !!$(this).prop('checked');
|
||||
$('#openai_form').toggle(!oai_settings.use_window_ai);
|
||||
$('#chat_completion_source').on('change', function () {
|
||||
oai_settings.chat_completion_source = $(this).find(":selected").val();
|
||||
toggleChatCompletionForms();
|
||||
setOnlineStatus('no_connection');
|
||||
resultCheckStatusOpen();
|
||||
$('#api_button_openai').trigger('click');
|
||||
@@ -1607,6 +1709,7 @@ $(document).ready(function () {
|
||||
$("#api_button_openai").on("click", onConnectButtonClick);
|
||||
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
|
||||
$("#model_openai_select").on("change", onModelChange);
|
||||
$("#model_claude_select").on("change", onModelChange);
|
||||
$("#settings_perset_openai").on("change", onSettingsPresetChange);
|
||||
$("#new_oai_preset").on("click", onNewPresetClick);
|
||||
$("#delete_oai_preset").on("click", onDeletePresetClick);
|
||||
|
||||
@@ -5,6 +5,7 @@ export const SECRET_KEYS = {
|
||||
OPENAI: 'api_key_openai',
|
||||
POE: 'api_key_poe',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
@@ -12,6 +13,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.OPENAI]: '#api_key_openai',
|
||||
[SECRET_KEYS.POE]: '#poe_token',
|
||||
[SECRET_KEYS.NOVEL]: '#api_key_novel',
|
||||
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
@@ -20,12 +22,13 @@ async function clearSecret() {
|
||||
secret_state[key] = false;
|
||||
updateSecretDisplay();
|
||||
$(INPUT_MAP[key]).val('');
|
||||
$('#main_api').trigger('change');
|
||||
}
|
||||
|
||||
function updateSecretDisplay() {
|
||||
for (const [secret_key, input_selector] of Object.entries(INPUT_MAP)) {
|
||||
const validSecret = !!secret_state[secret_key];
|
||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
||||
const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';
|
||||
$(input_selector).attr('placeholder', placeholder);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +53,7 @@ async function viewSecrets() {
|
||||
const table = document.createElement('table');
|
||||
table.classList.add('responsiveTable');
|
||||
$(table).append('<thead><th>Key</th><th>Value</th></thead>');
|
||||
|
||||
|
||||
for (const [key,value] of Object.entries(data)) {
|
||||
$(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`);
|
||||
}
|
||||
@@ -100,4 +103,4 @@ export async function readSecretState() {
|
||||
jQuery(() => {
|
||||
$('#viewSecrets').on('click', viewSecrets);
|
||||
$(document).on('click', '.clear-api-key', clearSecret);
|
||||
});
|
||||
});
|
||||
|
||||
136
public/style.css
136
public/style.css
@@ -241,6 +241,10 @@ body.tts .mes[is_system="true"] .mes_narrate {
|
||||
display: none;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--grey70);
|
||||
}
|
||||
|
||||
.mes.smallSysMes {
|
||||
padding: 5px !important;
|
||||
font-size: calc(var(--mainFontSize)* 0.9);
|
||||
@@ -581,18 +585,20 @@ hr {
|
||||
padding-bottom: 5px;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
/* align-items: center; */
|
||||
column-gap: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
#extensionsMenu>div {
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
#extensionsMenu>div,
|
||||
.options-content a,
|
||||
.list-group-item {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#extensionsMenu>div:hover {
|
||||
filter: brightness(1.2);
|
||||
#extensionsMenu>div:hover,
|
||||
.options-content a:hover,
|
||||
.list-group-item:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -611,9 +617,6 @@ hr {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.options-content a:hover {
|
||||
background-color: var(--SmartThemeEmColor);
|
||||
}
|
||||
|
||||
.auto_hide {
|
||||
content-visibility: auto;
|
||||
@@ -681,7 +684,7 @@ hr {
|
||||
.ui-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
#avatars-style .range-block-range,
|
||||
@@ -848,7 +851,7 @@ textarea {
|
||||
background-color: var(--black30a);
|
||||
outline: none;
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 10px;
|
||||
border-radius: 7px;
|
||||
color: var(--SmartThemeBodyColor);
|
||||
font-size: var(--mainFontSize);
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
@@ -881,7 +884,7 @@ select {
|
||||
|
||||
|
||||
.text_pole::placeholder {
|
||||
color: rgb(92, 90, 90);
|
||||
color: rgb(139, 139, 139);
|
||||
}
|
||||
|
||||
#send_textarea::placeholder {
|
||||
@@ -894,7 +897,8 @@ select {
|
||||
min-height: 175px;
|
||||
}
|
||||
|
||||
.margin-bot-10px {
|
||||
.margin-bot-10px,
|
||||
.marginBot10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -902,6 +906,14 @@ select {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.marginBot5 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.marginTop5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.marginTopBot5 {
|
||||
margin: 5px 0;
|
||||
}
|
||||
@@ -935,9 +947,9 @@ select {
|
||||
background-color: var(--black30a);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 10px;
|
||||
border-radius: 7px;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
padding: 7px;
|
||||
padding: 3px 5px;
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
height: fit-content;
|
||||
@@ -1138,11 +1150,11 @@ input[type="file"] {
|
||||
|
||||
select {
|
||||
|
||||
padding: 7px;
|
||||
padding: 3px 2px;
|
||||
background-color: var(--black30a);
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 7px;
|
||||
margin-bottom: 5px;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
@@ -1249,20 +1261,26 @@ select option:not(:checked) {
|
||||
column-gap: 5px;
|
||||
}
|
||||
|
||||
#form_character_search_form .menu_button {
|
||||
#form_character_search_form .menu_button,
|
||||
#GroupFavDelOkBack .menu_button {
|
||||
margin: 0;
|
||||
height: fit-content;
|
||||
padding: 5px;
|
||||
border-radius: 7px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
#character_sort_order {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
height: auto;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
#character_search_bar {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
padding-left: 0.75em;
|
||||
/* padding-left: 0.75em; */
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
input[type=search]::-webkit-search-cancel-button {
|
||||
@@ -1348,12 +1366,11 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
}
|
||||
|
||||
#bg_menu_content {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: calc(var(--sheldWidth) - 10px);
|
||||
max-width: 100svw;
|
||||
justify-content: space-evenly;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bg_example {
|
||||
@@ -1444,7 +1461,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
|
||||
#user-settings-block h4,
|
||||
#user-settings-block h3 {
|
||||
margin: 10px 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
#avatar-and-name-block {
|
||||
@@ -1519,8 +1536,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
.form_create_bottom_buttons_block {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
grid-gap: 10px;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
flex-wrap: wrap;
|
||||
@@ -1674,8 +1690,8 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black50a);
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 7px;
|
||||
padding: 3px 5px;
|
||||
width: min-content;
|
||||
cursor: pointer;
|
||||
margin: 5px 0;
|
||||
@@ -1692,10 +1708,10 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
height: 40px;
|
||||
height: 35px;
|
||||
filter: grayscale(0.5);
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-size: 20px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
@@ -2136,7 +2152,7 @@ input[type='checkbox']:not(#nav-toggle):not(#rm_button_panel_pin):not(#lm_button
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 5px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@@ -2233,7 +2249,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#tips_popup {
|
||||
/* #tips_popup {
|
||||
|
||||
width: 500px;
|
||||
height: 600px;
|
||||
@@ -2253,7 +2269,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
border-radius: 10px;
|
||||
padding-top: 30px;
|
||||
}
|
||||
} */
|
||||
|
||||
.nice-link {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
@@ -2456,7 +2472,7 @@ input[type="range"]::-webkit-slider-thumb {
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 10px 0;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@@ -2676,14 +2692,16 @@ h5 {
|
||||
.tag_controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag_view_item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
gap: 15px;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tag_view_name {
|
||||
@@ -2708,7 +2726,7 @@ h5 {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
background-color: var(--black30a);
|
||||
border-color: var(--white50a);
|
||||
padding: 0.2rem 0.3rem;
|
||||
padding: 0.1rem 0.2rem;
|
||||
font-size: calc(var(--mainFontSize) - 5%);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -2722,6 +2740,13 @@ h5 {
|
||||
|
||||
.tag.actionable {
|
||||
border-radius: 50%;
|
||||
aspect-ratio: 1 / 1;
|
||||
min-height: calc(var(--mainFontSize) * 2);
|
||||
min-width: calc(var(--mainFontSize) * 2);
|
||||
font-size: calc(var(--mainFontSize) * 1);
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.margin-right-10px {
|
||||
@@ -2745,8 +2770,8 @@ h5 {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
column-gap: 0.2rem;
|
||||
row-gap: 0.2rem;
|
||||
gap: 0.2rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
#tagList.tags {
|
||||
@@ -2792,8 +2817,11 @@ h5 {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
|
||||
.tags_view {
|
||||
.tags_view,
|
||||
.open_alternate_greetings {
|
||||
margin: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.tag.selected {
|
||||
@@ -3368,13 +3396,9 @@ a {
|
||||
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
|
||||
}
|
||||
|
||||
.list-group-item:hover {
|
||||
background-color: var(--SmartThemeEmColor);
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
color: var(--SmartThemeBodyColor);
|
||||
padding: 0.75rem 1.25rem;
|
||||
padding: 5px 7px;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
@@ -3403,7 +3427,7 @@ a {
|
||||
border-left: 1px solid var(--grey30a);
|
||||
border-bottom: 1px solid var(--grey30a);
|
||||
box-shadow: none;
|
||||
border-radius: 0 0 0 20px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
scrollbar-width: thin;
|
||||
flex-flow: column;
|
||||
@@ -3692,7 +3716,7 @@ label[for="extensions_autoconnect"] {
|
||||
background-color: var(--SmartThemeBlurTintColor);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid var(--grey30a);
|
||||
box-shadow: 0 0 20px black;
|
||||
min-width: 450px;
|
||||
@@ -3747,12 +3771,18 @@ label[for="extensions_autoconnect"] {
|
||||
}
|
||||
|
||||
toolcool-color-picker {
|
||||
margin-right: 10px;
|
||||
/* margin-right: 10px; */
|
||||
}
|
||||
|
||||
.settingsSectionWrap {
|
||||
border: 1px solid var(--white30a);
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -3903,6 +3933,14 @@ toolcool-color-picker {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/*used to fix smallness of certain FontAwesome glyph which break button squareness*/
|
||||
/*currently used on: CharList Import*/
|
||||
|
||||
.faSmallFontSquareFix {
|
||||
font-size: calc(var(--mainFontSize) *1.25);
|
||||
width: calc(var(--mainFontSize) *1.95);
|
||||
}
|
||||
|
||||
.openai_logit_bias_preset_form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
105
server.js
105
server.js
@@ -109,6 +109,7 @@ const poe = require('./src/poe-client');
|
||||
let api_server = "http://0.0.0.0:5000";
|
||||
let api_novelai = "https://api.novelai.net";
|
||||
let api_openai = "https://api.openai.com/v1";
|
||||
let api_claude = "https://api.anthropic.com/v1";
|
||||
let main_api = "kobold";
|
||||
|
||||
let response_generate_novel;
|
||||
@@ -759,7 +760,7 @@ function charaFormatData(data) {
|
||||
[d => Array.isArray(d.alternate_greetings), d => d.alternate_greetings],
|
||||
[d => typeof d.alternate_greetings === 'string', d => [d.alternate_greetings]],
|
||||
[_.stubTrue, _.constant([])]
|
||||
])(data);
|
||||
])(data);
|
||||
|
||||
// Spec V1 fields
|
||||
_.set(char, 'name', data.ch_name);
|
||||
@@ -2694,14 +2695,111 @@ app.post("/deletepreset_openai", jsonParser, function (request, response) {
|
||||
return response.send({ error: true });
|
||||
});
|
||||
|
||||
// Prompt Conversion script taken from RisuAI by @kwaroran (GPLv3).
|
||||
function convertClaudePrompt(messages) {
|
||||
let requestPrompt = messages.map((v) => {
|
||||
let prefix = '';
|
||||
switch (v.role) {
|
||||
case "assistant":
|
||||
prefix = "\n\nAssistant: ";
|
||||
break
|
||||
case "user":
|
||||
prefix = "\n\nHuman: ";
|
||||
break
|
||||
case "system":
|
||||
prefix = "\n\nSystem: ";
|
||||
break
|
||||
}
|
||||
return prefix + v.content;
|
||||
}).join('') + '\n\nAssistant: ';
|
||||
return requestPrompt;
|
||||
}
|
||||
|
||||
async function sendClaudeRequest(request, response) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const api_url = new URL(request.body.reverse_proxy || api_claude).toString();
|
||||
const api_key_claude = readSecret(SECRET_KEYS.CLAUDE);
|
||||
|
||||
if (!api_key_claude) {
|
||||
return response.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
request.socket.removeAllListeners('close');
|
||||
request.socket.on('close', function () {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const requestPrompt = convertClaudePrompt(request.body.messages);
|
||||
console.log('Claude request:', requestPrompt);
|
||||
|
||||
const generateResponse = await fetch(api_url + '/complete', {
|
||||
method: "POST",
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
prompt: "\n\nHuman: " + requestPrompt,
|
||||
model: request.body.model,
|
||||
max_tokens_to_sample: request.body.max_tokens,
|
||||
stop_sequences: ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"],
|
||||
temperature: request.body.temperature,
|
||||
stream: request.body.stream,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": api_key_claude,
|
||||
}
|
||||
});
|
||||
|
||||
if (request.body.stream) {
|
||||
// Pipe remote SSE stream to Express response
|
||||
generateResponse.body.pipe(response);
|
||||
|
||||
request.socket.on('close', function () {
|
||||
generateResponse.body.destroy(); // Close the remote stream
|
||||
response.end(); // End the Express response
|
||||
});
|
||||
|
||||
generateResponse.body.on('end', function () {
|
||||
console.log("Streaming request finished");
|
||||
response.end();
|
||||
});
|
||||
} else {
|
||||
if (!generateResponse.ok) {
|
||||
console.log(`Claude API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
|
||||
return response.status(generateResponse.status).send({ error: true });
|
||||
}
|
||||
|
||||
const generateResponseJson = await generateResponse.json();
|
||||
const responseText = generateResponseJson.completion;
|
||||
console.log('Claude response:', responseText);
|
||||
|
||||
// Wrap it back to OAI format
|
||||
const reply = { choices: [{ "message": { "content": responseText, } }] };
|
||||
return response.send(reply);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error communicating with Claude: ', error);
|
||||
if (!response.headersSent) {
|
||||
return response.status(500).send({ error: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
|
||||
if (!request.body) return response_generate_openai.sendStatus(400);
|
||||
if (!request.body) return response_generate_openai.status(400).send({ error: true });
|
||||
|
||||
if (request.body.use_claude) {
|
||||
return sendClaudeRequest(request, response_generate_openai);
|
||||
}
|
||||
|
||||
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
|
||||
const api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
|
||||
if (!api_key_openai) {
|
||||
return response_generate_openai.sendStatus(401);
|
||||
return response_generate_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
@@ -3009,6 +3107,7 @@ const SECRET_KEYS = {
|
||||
OPENAI: 'api_key_openai',
|
||||
POE: 'api_key_poe',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
|
||||
@@ -25,7 +25,36 @@ const path = require('path');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
const parent_path = path.resolve(__dirname);
|
||||
const directory = __dirname;
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
const getSavedDeviceId = (userId) => {
|
||||
const device_id_path = 'poe_device.json';
|
||||
let device_ids = {};
|
||||
|
||||
if (fs.existsSync(device_id_path)) {
|
||||
device_ids = JSON.parse(fs.readFileSync(device_id_path, 'utf8'));
|
||||
}
|
||||
|
||||
if (device_ids.hasOwnProperty(userId)) {
|
||||
return device_ids[userId];
|
||||
}
|
||||
|
||||
const device_id = uuidv4();
|
||||
device_ids[userId] = device_id;
|
||||
fs.writeFileSync(device_id_path, JSON.stringify(device_ids, null, 2));
|
||||
|
||||
return device_id;
|
||||
};
|
||||
|
||||
const parent_path = path.resolve(directory);
|
||||
const queries_path = path.join(parent_path, "poe_graphql");
|
||||
let queries = {};
|
||||
|
||||
@@ -203,6 +232,18 @@ function md5() {
|
||||
return m;
|
||||
}
|
||||
|
||||
function generateNonce(length = 16) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters[randomIndex];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function load_queries() {
|
||||
const files = fs.readdirSync(queries_path);
|
||||
for (const filename of files) {
|
||||
@@ -257,6 +298,7 @@ class Client {
|
||||
ws_connected = false;
|
||||
auto_reconnect = false;
|
||||
use_cached_bots = false;
|
||||
device_id = null;
|
||||
|
||||
constructor(auto_reconnect = false, use_cached_bots = false) {
|
||||
this.auto_reconnect = auto_reconnect;
|
||||
@@ -302,11 +344,20 @@ class Client {
|
||||
"poe-tchannel": this.channel["channel"],
|
||||
...this.headers,
|
||||
};
|
||||
if (this.device_id === null) {
|
||||
this.device_id = this.get_device_id();
|
||||
}
|
||||
await this.subscribe();
|
||||
await this.connect_ws();
|
||||
console.log('Client initialized.');
|
||||
}
|
||||
|
||||
get_device_id() {
|
||||
const user_id = this.viewer["poeUser"]["id"];
|
||||
const device_id = getSavedDeviceId(user_id);
|
||||
return device_id;
|
||||
}
|
||||
|
||||
async get_next_data() {
|
||||
logger.info('Downloading next_data...');
|
||||
|
||||
@@ -569,6 +620,8 @@ class Client {
|
||||
"query": message,
|
||||
"chatId": this.bots[chatbot]["chatId"],
|
||||
"source": null,
|
||||
"clientNonce": generateNonce(),
|
||||
"sdid": this.device_id,
|
||||
"withChatBreak": with_chat_break
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user