mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
#21 Add basic upload of world infos. Add info Note
This commit is contained in:
@@ -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,12 +3140,20 @@
|
||||
<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> -->
|
||||
<select id="world_info" class="option_select_right_menu">
|
||||
<option value="None">None</option>
|
||||
</select>
|
||||
<div id="world_status">
|
||||
<div id="world_status_indicator"></div><div id="world_status_text"></div>
|
||||
<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;">
|
||||
|
43
public/notes/13.html
Normal file
43
public/notes/13.html
Normal 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>
|
@@ -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;
|
||||
|
37
server.js
37
server.js
@@ -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.`);
|
||||
|
Reference in New Issue
Block a user