#21 Add basic upload of world infos. Add info Note

This commit is contained in:
SillyLossy
2023-02-10 18:12:19 +02:00
parent fc8dad5c3f
commit 1e48220711
4 changed files with 151 additions and 11 deletions

View File

@@ -1524,6 +1524,9 @@
}
});
}
if (popup_type === 'world_imported') {
// switch to a new world
}
if(popup_type == 'new_chat' && this_chid != undefined && menu_type != "create"){//Fix it; New chat doesn't create while open create character menu
clearChat();
chat.length = 0;
@@ -1549,6 +1552,7 @@
$("#dialogue_popup_cancel").css("display", "none");
break;
case 'world_imported':
case 'new_chat':
$("#dialogue_popup_ok").css("background-color", "#191b31CC");
@@ -2042,7 +2046,7 @@
main_api = 'kobold';
$('#max_context_block').css('display', 'block');
$('#amount_gen_block').css('display', 'block');
$('#world_info').css('display', 'block');
$('#world_info_block').css('display', 'block');
}
if($('#main_api').find(":selected").val() == 'novel'){
$('#kobold_api').css("display", "none");
@@ -2050,7 +2054,7 @@
main_api = 'novel';
$('#max_context_block').css('display', 'none');
$('#amount_gen_block').css('display', 'none');
$('#world_info').css('display', 'none');
$('#world_info_block').css('display', 'none');
}
updateWorldStatus();
@@ -2080,7 +2084,7 @@
}
function updateWorldStatus() {
if($('#world_info').is(':visible') && kobold_world) {
if($('#world_info_block').is(':visible') && kobold_world) {
$('#world_status').show();
if (kobold_world_synced) {
@@ -2887,6 +2891,48 @@
$('#load_select_chat_div').css('display', 'block');
});
//**************************WORLD INFO IMPORT EXPORT*************************//
$("#world_import_button" ).click(function() {
$("#world_import_file").click();
});
$("#world_import_file").on("change", function(e) {
var file = e.target.files[0];
if (!file) {
return;
}
const ext = file.name.match(/\.(\w+)$/);
if (!ext || (ext[1].toLowerCase() !== "json")){
return;
}
var formData = new FormData($("#form_world_import").get(0));
jQuery.ajax({
type: 'POST',
url: '/importworld',
data: formData,
beforeSend: () => {},
cache: false,
contentType: false,
processData: false,
success: function(data){
if (data.name) {
// TODO reload list of custom worlds to allow selection then offer a popup
// popup_type = 'world_imported';
// callPopup('<h3>World imported successfully! Select it now?</h3>');
}
},
error: (jqXHR, exception) => {},
});
// Will allow to select the same file twice in a row
$('#form_world_import').trigger("reset");
});
});
    </script>
<title>Tavern.AI</title>
@@ -3094,14 +3140,22 @@
<h4>Repetition Penalty Range</h4><h5 id="rep_pen_size_counter">select</h5>
<input type="range" id="rep_pen_size" name="volume" min="0" max="2048" step="1">
</div>
<h4>World Info</h4><!-- <h5>More info (<a href="http://example.com" target="_blank">?</a>)</h5> -->
<div>
<h4 id="world_info_block">World Info</h4><h5>How to use (<a href="/notes/13" target="_blank">?</a>)</h5>
<div id="rm_world_import" class="right_menu" style="display: none;">
<form id="form_world_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
<input type="file" id="world_import_file" accept=".json" name="avatar">
</form>
</div>
<select id="world_info" class="option_select_right_menu">
<option value="None">None</option>
</select>
<div id="world_import_button" class="right_menu_button"><h2>+Import</h2></div>
<div id="world_status">
<div id="world_status_indicator"></div><div id="world_status_text"></div>
</div>
</div>
</div>
<div id="novel_api" style="display: none;position: relative;">
<div style="position: absolute; right:152px; top:-25px; opacity:0.25;"><a href="https://novelai.net/" target="_blank"><img src="img/novelai.png" style="width:auto;height:22px;"></a></div>
<form action="javascript:void(null);" method="post" enctype="multipart/form-data">

43
public/notes/13.html Normal file
View File

@@ -0,0 +1,43 @@
<html>
<head>
<title>World Info</title>
<link rel="stylesheet" href="/css/notes.css">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="main">
<div id="content">
<h2>World Info</h2>
<h4>World Info enhances AI's understanding of the details in your world.</h4>
<p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries when keywords associated with the entries are present in the message text.</p>
<p>The KoboldAI engine activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.</p>
<p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does not guarantee its appearance in the generated output messages.</i></p>
<h3>Pro Tips</h3>
<ul>
<li>The AI does not insert keywords into context, so each World Info entry should be a comprehensive, standalone description.</li>
<li>To create a rich and detailed world lore, entries can be interlinked and reference one another.</li>
<li>To conserve tokens, it is advisable to keep entry contents concise, with a general recommended limit of 50 tokens per entry.</li>
</ul>
<h3>Entry Fields Explained</h3>
<dl>
<dt>Key</dt>
<dd>A list of keywords that trigger the activation of a World Info entry.</dd>
<dt>Secondary Key</dt>
<dd>A list of supplementary keywords that are used in conjunction with the main keywords (see <a href="#Selective">Selective</a>).</dd>
<dt>Content</dt>
<dd>The text that is inserted into the prompt upon entry activation.</dd>
<dt>Comment</dt>
<dd>A supplemental text comment for the your convenience, which is not utilized by the AI.</dd>
<dt>Constant</dt>
<dd>If enabled, the entry would always be present in the prompt. <em>Currently, this is unsupported!</em></dd>
<dt id="Selective">Selective</dt>
<dd>If enabled, the entry would only be inserted when both a Key <b>AND</b> a Secondary Key have been activated. <em>Currently, this is unsupported!</em></dd>
</dl>
</div>
</div>
</body>
</html>

View File

@@ -973,6 +973,16 @@ input[type=button] {
margin-left: 4px;
display: inline-block;
}
#world_import_button{
cursor: pointer;
display: inline-block;
}
#world_import_button h2{
margin-top: auto;
margin-bottom: auto;
font-size: 17px;
color: rgb(188, 193, 200, 0.5);
}
.del_checkbox{
display: none;

View File

@@ -49,6 +49,7 @@ var is_colab = false;
const jsonParser = express.json({limit: '100mb'});
const urlencodedParser = express.urlencoded({extended: true, limit: '100mb'});
const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
const directories = { worlds: 'public/KoboldAI Worlds/' };
app.use(function (req, res, next) { //Security
const clientIp = req.connection.remoteAddress.split(':').pop();
@@ -630,7 +631,7 @@ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's cod
);
const worldFiles = fs
.readdirSync('public/KoboldAI Worlds')
.readdirSync(directories.worlds)
.filter(file => path.extname(file).toLowerCase() === '.json')
.sort((a, b) => a < b);
const koboldai_world_names = worldFiles.map(item => path.parse(item).name);
@@ -1070,6 +1071,38 @@ app.post("/importchat", urlencodedParser, function(request, response){
});
app.post('/importworld', urlencodedParser, (request, response) => {
if(!request.file) return response.sendStatus(400);
const filename = request.file.originalname;
if (path.parse(filename).ext.toLowerCase() !== '.json') {
return response.status(400).send('Only JSON files are supported.')
}
const pathToUpload = path.join('./uploads/' + request.file.filename);
const fileContents = fs.readFileSync(pathToUpload, 'utf8');
try {
const worldContent = JSON.parse(fileContents);
if (!('entries' in worldContent)) {
throw new Error('File must contain a world info entries list');
}
} catch (err) {
return response.status(400).send('Is not a valid world info file');
}
const pathToNewFile = path.join(directories.worlds, filename);
const worldName = path.parse(pathToNewFile).name;
if (!worldName) {
return response.status(400).send('World file must have a name');
}
fs.writeFileSync(pathToNewFile, fileContents);
return response.send({ name: worldName });
});
function findTavernWorldEntry(info, key) {
for (const entryId in info.entries) {
const entry = info.entries[entryId];
@@ -1115,7 +1148,7 @@ function readWorldInfoFile(worldInfoName) {
const koboldFolderName = getKoboldWorldInfoName(worldInfoName);
const filename = `${worldInfoName}.json`;
const pathToWorldInfo = path.join('public/KoboldAI Worlds/', filename);
const pathToWorldInfo = path.join(directories.worlds, filename);
if (!fs.existsSync(pathToWorldInfo)) {
throw new Error(`World info file ${filename} doesn't exist.`);