diff --git a/public/index.html b/public/index.html
index 3b1c73f4d..54484517c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -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('
World imported successfully! Select it now? ');
+ }
+ },
+ error: (jqXHR, exception) => {},
+ });
+
+ // Will allow to select the same file twice in a row
+ $('#form_world_import').trigger("reset");
+ });
+
});
Tavern.AI
@@ -3094,12 +3140,20 @@
Repetition Penalty Range select
- World Info
-
-
-
+
+
World Info How to use (? )
+
+
+
+
diff --git a/public/notes/13.html b/public/notes/13.html
new file mode 100644
index 000000000..c15c35341
--- /dev/null
+++ b/public/notes/13.html
@@ -0,0 +1,43 @@
+
+
+
World Info
+
+
+
+
+
+
+
+
World Info
+
World Info enhances AI's understanding of the details in your world.
+
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.
+
The KoboldAI engine activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.
+
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.
+
+
Pro Tips
+
+ The AI does not insert keywords into context, so each World Info entry should be a comprehensive, standalone description.
+ To create a rich and detailed world lore, entries can be interlinked and reference one another.
+ To conserve tokens, it is advisable to keep entry contents concise, with a general recommended limit of 50 tokens per entry.
+
+
+
Entry Fields Explained
+
+ Key
+ A list of keywords that trigger the activation of a World Info entry.
+ Secondary Key
+ A list of supplementary keywords that are used in conjunction with the main keywords (see Selective ).
+ Content
+ The text that is inserted into the prompt upon entry activation.
+ Comment
+ A supplemental text comment for the your convenience, which is not utilized by the AI.
+ Constant
+ If enabled, the entry would always be present in the prompt. Currently, this is unsupported!
+ Selective
+ If enabled, the entry would only be inserted when both a Key AND a Secondary Key have been activated. Currently, this is unsupported!
+
+
+
+
+
+
diff --git a/public/style.css b/public/style.css
index fa81fdeca..990b9d309 100644
--- a/public/style.css
+++ b/public/style.css
@@ -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;
diff --git a/server.js b/server.js
index e9720102e..967328f2c 100644
--- a/server.js
+++ b/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.`);