varkarrus

Modified Zynj EWI

Oct 20th, 2020
83
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.96 KB | None | 0 0
  1. ////////////// Shared Library
  2.  
  3. //https://stackoverflow.com/questions/273789/is-there-a-version-of-javascripts-string-indexof-that-allows-for-regular-expr#273810
  4. String.prototype.regexLastIndexOf = function (regex, startpos) { regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : "")); if (typeof (startpos) == "undefined") { startpos = this.length; } else if (startpos < 0) { startpos = 0; } let stringToWorkWith = this.substring(0, startpos + 1); let lastIndexOf = -1; let nextStop = 0; while ((result = regex.exec(stringToWorkWith)) != null) { lastIndexOf = result.index; regex.lastIndex = ++nextStop; } return lastIndexOf; }
  5.  
  6. function shuffleArray(array) {
  7. for (var i = array.length - 1; i > 0; i--) {
  8. var j = Math.floor(Math.random() * (i + 1));
  9. var temp = array[i];
  10. array[i] = array[j];
  11. array[j] = temp;
  12. }
  13. return array;
  14. }
  15.  
  16. const calcLine = (list,ind) => {
  17. ind = Math.abs(ind);
  18. for (var i =1; i <= Math.min(list.length,ind); i++){
  19. console.log("CalcLine: ("+i+") "+list[list.length-i])
  20. if(list[list.length-i] == ""){
  21. ind++;
  22. }
  23. }
  24. return ind;
  25. }
  26.  
  27. const getHeader = (type) => {
  28. for(var i = 0 ; i < worldEntries.length;i++){
  29. if(worldEntries[i]["keys"].match(/(?<=h=)\d+/) == type){
  30. return "\n==\n"+worldEntries[i]["entry"]+"\n";
  31. }
  32. }
  33. return "";
  34. }
  35.  
  36. const deformatKeys = (text) =>{
  37. text = text.replace(/ *\[[^)]*\] */g, "");
  38. text = text.replace(/\$/g,"");
  39. text = text.replace(/\|/g,",");
  40. text = text.replace(/,(?=[^\s])/g, ", ");
  41. if(text.endsWith(", ")){text = text.slice(0,-2)}
  42. if(text.endsWith(",")){text = text.slice(0,-1)}
  43. return text;
  44. }
  45.  
  46. const reformatKeys = (text, type) =>{
  47. text = "$"+text.replace(/, /g,", $");
  48. text+=", [t="+type+"]";
  49. return text;
  50. }
  51.  
  52. const getRawHistoryString = (chars) => history.map(element => element["text"]).join(' ').slice(chars);
  53. const getHistoryString = (turns) => history.slice(turns).map(element => element["text"]).join(' ') // Returns a single string of the text.
  54. const getHistoryText = (turns) => history.slice(turns).map(element => element["text"]) // Returns an array of text.
  55. const getActionTypes = (turns) => history.slice(turns).map(element => element["type"]) // Returns the action types of the previous turns in an array.
  56. const hasAttributes = (keys) => {const attrib = keys.match(/([a-z](=\d+)?)/g); if(attrib) {return attrib.map(attrib => attrib.split('='))}} // Pass it a bracket-encapsulated string and it returns an array of [attribute, value] pairs if possible.
  57. const everyCheck = (entry, string) => // an AND/OR check for keywords. Not foolproof, but should be fine as long as proper syntax is abided.
  58. {
  59. string = string.toLowerCase().trim()
  60.  
  61. const keys = entry["keys"].replace(/\$/g, '').replace(/\[(.+)?\]/g, '').toLowerCase().trim().split(',')
  62. let anyArray = keys.filter(element => element.includes('|'))
  63. const anyCheck = anyArray.every(element => element.split('|').some(key => string.includes(key.trim())))
  64.  
  65. const everyArray = keys.filter(element => !element.includes('|')).every(key => string.includes(key.trim()))
  66. console.log(`Keys: ${keys}, Any: ${anyArray}, Every: ${everyArray}`)
  67. if (everyArray && anyCheck) {
  68. return true
  69. }
  70. }
  71. String.prototype.extractString = function(a, b) {return this.slice(this.indexOf(a), this.indexOf(b) +1)} // Slightly cleaner to read and write than doing the indexing yourself.
  72.  
  73. const getTypeEntries = (type) => {const t = type; return worldEntries.filter(a => a["keys"].match(/(?<=t=)\d+/) == t) }
  74.  
  75. const spliceLines = (string, pos, req = 1) => { if (lines.length > req) {lines.splice(pos, 0, string.replace(/[\{\}]/g,""))}} // This is run on each of the additional context to position them accordingly.
  76.  
  77. const spliceMiddle = (array, pos, req = 1) => {
  78. array = array.sort((a,b) => {return a.value - b.value})
  79. if(array.map(x => x.text).join("").length > 700){
  80. do{
  81. contextStacks["backMemory"][0].push({text:array[0].text,value:array[0].value});
  82. array.shift();
  83. }while(array.map(x => x.text).join("").length > 700)
  84.  
  85. }
  86. lines.splice(lines.length-calcLine(lines,pos), 0, array.map(x => x.text).join("").replace(/[\{\}]/g,""))
  87. }
  88.  
  89. //const spliceMemory = (string, pos, req = 1) => contextMemory.splice(pos, 0, string.replace(/[\{\}]/g,"")+"\n==\n")
  90.  
  91. const spliceMemory = (array, pos, req = 1) => {
  92. array = array.sort((a,b) => {return a.value - b.value})
  93. console.log(JSON.stringify(array));
  94. if(array.map(x=>x.text).join("").length > 600){
  95. do{array.shift();}
  96. while(array.map(x=>x.text).join("").length > 600)
  97. }
  98. console.log("post clean");
  99. console.log(JSON.stringify(array));
  100. contextMemory.splice(pos,0,array.map(x=>x.text).join("").replace(/[\{\}]/g,"")+"\n==\n")
  101.  
  102. }
  103.  
  104.  
  105.  
  106. const getMemory = (text) => {return info.memoryLength ? text.slice(0, info.memoryLength - 1) : ''} // If memoryLength is set then slice of the beginning until the end of memoryLength, else return an empty string.
  107. const getContext = (text) => {return info.memoryLength ? text.slice(info.memoryLength).replace(/^[^A-Z].+[.]/, '') : text} // If memoryLength is set then slice from the end of memory to the end of text, else return the entire text.
  108. // This basically took a 'just make it work 4Head approach', not happy with it, but it *does* work.
  109. // The "challenge" is to insert the words on their last appearance rather than simply inserting on linebreaks.
  110. // Whole-word match should be used, but I'm not keen on re-iterating the same searches in alernative means several times.
  111. // Adding parenthesized descriptors should be done indepentendly of the 'lastInputString' search, but that'd somewhat conflict with the current attribute system (I'll look into it).
  112. // The issue with having a ton of semi- persistent parenthesized enapsulations is that the AI will begin using them too (consider doing last mention before previous action?)
  113. // Though with the intended use- case (short descriptors) it shouldn't be too much of an issue e.g John (the hero) or David (party member); this is one of the reason why I've placed a limiter of utilizing the first sentence only.
  114. const addDescription = (entry, value = 0, ind = 0) =>
  115. {
  116. const searchKeys = entry["keys"].replace(/\$/g, '').replace(/\[(.+)?\]/g, '').replace(/\|/g, ',').split(',').filter(element => element.trim().length >= 1)
  117. let finalIndex = null;
  118. let keyPhrase = null;
  119. searchKeys.forEach(key => { if(!assignedDescriptors.includes(key)) {const regEx = new RegExp(`\\b${key.trim()}\\b`,"gi"); const keyIndex = context.toLowerCase().regexLastIndexOf(regEx); if (keyIndex > finalIndex) {finalIndex = keyIndex; keyPhrase = key; assignedDescriptors.push(key)}}}) // Find the last mention of a valid key from the entry.
  120.  
  121. if (finalIndex)
  122. {
  123.  
  124. console.log(finalIndex)
  125. const beginString = context.slice(0, finalIndex) // Establish the point up until the phrase/word we want to append to.
  126. const endString = context.slice(finalIndex + keyPhrase.length)
  127. //const entryString = entry["entry"].match(/[^.!?]+/) // Extract the first sentence from the entry - unsure about whether long descriptors should be permitted, and also how they would affect the generation flow.
  128. const entryString = entry["entry"].split('{')[1].split('}')[0];
  129. context = beginString + keyPhrase + ' (' + entryString + ')' + endString; // Assemble it with parenthesis encapsulation (less likely to interrupt narrative).
  130. }
  131. lines = context.split('\n')
  132. }
  133.  
  134. const addAuthorsNote = (entry, value = 0, ind = 0) => state.memory.authorsNote = `${entry["entry"]}`
  135. const revealWorldEntry = (entry, value = 0, ind = 0) => entry.isNotHidden = true
  136.  
  137. // frontMemory is fairly dependant on cooldowns which is why it's the only one that has that implemented at the moment.
  138. const addFrontMemory = (entry, value = 0, ind = 0) =>
  139. {
  140. if (!entry.fLastSeen) {entry.fLastSeen = info.actionCount};
  141. if (info.actionCount - entry.fLastSeen >= value) {entry.fLastSeen = info.actionCount;}
  142. if ((info.actionCount % entry.fLastSeen) == value || (info.actionCount - entry.fLastSeen == 0))
  143. {
  144. contextStacks["frontMemory"][0] = contextStacks["frontMemory"][0].replace(/\n\n>/gm, '').trim();
  145. contextStacks["frontMemory"][0] += `\n> ${entry["entry"]}`;
  146. entry.fLastSeen = info.actionCount;
  147. }
  148. }
  149. const addBackMemory = (entry, value = 0, ind = 0) => {contextStacks["backMemory"][0].push({text: "["+entry["entry"]+"]\n", value:ind});}
  150.  
  151. const addMiddleMemory = (entry, value = 0, ind = 0) => {
  152. contextStacks["middleMemory"][0].push({text: "["+entry["entry"]+"]\n", value: ind});
  153. }
  154.  
  155. /////////////// Input Modifier
  156.  
  157.  
  158. // Checkout the repo examples to get an idea of other ways you can use scripting
  159. // https://github.com/AIDungeon/Scripting/blob/master/examples
  160.  
  161. const modifier = (text) => {
  162. let modifiedText = text
  163. const lowered = text.toLowerCase()
  164. state.message = null;
  165. state.entryGen = [];
  166. state.generate = false;
  167. state.memory.frontMemory = "";
  168. state.break = false;
  169.  
  170.  
  171. var args = modifiedText.match(/\[([^\]]+)\]/);
  172. var extraTags = lowered.replace(args,"").replace("/wi")
  173. args = args ? args[1] : [];
  174.  
  175.  
  176. if(lowered.includes("/sort")){
  177. worldEntries = worldEntries.sort((a,b)=>{return a.entry.toLowerCase() > b.entry.toLowerCase() ? 1 : -1})
  178. //state.break = true;
  179. return{text:"",stop:true}
  180. }
  181.  
  182. if(lowered.includes("/wi") && args.length > 0){
  183.  
  184. args = args.split("|");
  185. state.entryGen = args;
  186. state.generate = true;
  187. state.newEntry = {keys:"",entry:""};
  188. modifiedText = "";
  189.  
  190. var type = args[args.length-1];
  191. var chosenEntries = getTypeEntries(type);
  192. var string = "";
  193. if(chosenEntries.length ==0){state.message = "Error: No entries of type found"}else{
  194. chosenEntries = shuffleArray(chosenEntries);
  195. string+=getHeader(type);
  196. for(var i =0 ; i < Math.min(chosenEntries.length,5);i++){
  197. string+="{(Tags: "+deformatKeys(chosenEntries[i]["keys"])+") "+chosenEntries[i]["entry"].replace(/[\{\}]/g,"")+"}\n"
  198. }
  199. string+="{(Tags:";
  200. if(args.length >=2){string+=" "+args[0]+")"; state.newEntry.keys = reformatKeys(args[0],type)}
  201. if(args.length >=3){string+=" "+args[1];state.newEntry.entry = args[1];}
  202. }
  203.  
  204. state.memory.frontMemory = string;
  205. }
  206.  
  207.  
  208. // You must return an object with the text property defined.
  209. return { text: modifiedText }
  210. }
  211.  
  212. // Don't modify this part
  213. modifier(text)
  214.  
  215.  
  216. ////////////// Context Modifier
  217.  
  218. let contextMemory = getMemory(text).split('\n').filter(function (el) {
  219. return el != "";
  220. });
  221. let context = getContext(text);
  222. let lines = context.split('\n'); // Split the line-breaks within the context into separated strings in an array.
  223.  
  224.  
  225. const entryFunctions = {
  226. 'a': addAuthorsNote, // [a] adds it as authorsNote, only one authorsNote at a time.
  227. 'f': addFrontMemory, // [f] adds it to the frontMemory stack, multiple can be added at a time, but only the latest one becomes an action.
  228. 'r': revealWorldEntry, // [r] reveals the entry once mentioned, used in conjuction with [e] to only reveal if all keywords are mentioned at once.
  229. 'c': addBackMemory, // [c] adds it to context, recommended to pre-fix keywords with $ to avoid duplicates from normal processing.
  230. 'e': () => {}, // [e] tells the custom keyword check to only run the above functions if every keyword of the entry matches.
  231. 'd': addDescription, // [d] adds the first sentence of the entry as a short, parenthesized descriptor to the last mention of the revelant keyword(s) e.g John (a business man)
  232. 'm': addMiddleMemory, // [i] adds a stack of entries two lines above the last input, serves as a mix between authorsNote and frontMemory.
  233. 'w': () => {}, // [w] assigns the weight attribute, the higher value the more recent/relevant it will be in context/frontMemory/intermediateMemory etc.
  234. 't': () => {}, // [t] assigns a "type" value to the entry, used for random world info entries.
  235. 'h': () => {}, // [h] assigns a "type header" value to the entry, to classify world info types.
  236. }
  237.  
  238. // To avoid complicating it with measurements of the additonal string, and at the cost of slightly less flexibility, we assign different functions to handle the positioning.
  239. // spliceMemory would be to position it 'at the top of context'/'end of memory' while spliceLines is for short/medium injections towards the lower part of context.
  240. let contextStacks = { // Handle the positioning of the various additional contexts in a formated list to have an easier overview of what's going on.
  241. 'frontMemory': ["", lines.length, spliceLines], // Splice it at the end of the array, push would naturally be easier, but /shrug
  242. 'middleMemory': [[], 3, spliceMiddle], // Splice it one line back.
  243. 'backMemory': [[], contextMemory.length, spliceMemory] // Splice it at the end of memory contextMemory.length
  244. }
  245. let assignedDescriptors = [] // Assemble a list of descriptors that have already been assigned (in an attempt) to avoid duplicates.
  246. // Pass the worldEntries list and check attributes, then process them.
  247. const processWorldEntries = (entries) =>
  248. {
  249. entries = [...entries] // Copy the entries to avoid in-place manipulation.
  250. const lastTurnString = getRawHistoryString(-900).toLowerCase().trim() // What we check the keywords against, this time around we basically check where in the context the last history element is then slice forward.
  251. entries = shuffleArray(entries);
  252. entries.sort((a, b) => a["keys"].match(/(?<=w=)\d+/) - b["keys"].match(/(?<=w=)\d+/)).forEach(wEntry => // Take a quick sprint through the worldEntries list and process its elements.
  253. {
  254.  
  255. var lastIndex = -1;
  256.  
  257. wEntry["keys"].replace(/\$/g, '').replace(/\|/g, ',').split(',').forEach(keyword => {
  258.  
  259. lastIndex = Math.max(lastIndex,lastTurnString.lastIndexOf(keyword.toLowerCase().trim()))
  260.  
  261. })
  262.  
  263.  
  264. console.log(`Entry: ${wEntry["keys"]}: LastIndex: ${lastIndex}`)// Only process attributes of entries detected on the previous turn. (Using the presumed native functionality of substring acceptance instead of RegEx wholeword match)
  265. // During the custom check we also (temporarily) remove the '$' prefix as to not need special processing of that later, a trim is also done.
  266. if (lastIndex >= 0)
  267.  
  268. {
  269. var used = false;
  270. try // We try to do something. If code goes kaboom then we just catch the error and proceed. This is to deal with non-attribute assigned entries e.g those with empty bracket-encapsulations []
  271. {
  272.  
  273. // Get the attribute value pairs. [attrib, value]
  274. const entryAttributes = hasAttributes(wEntry["keys"].extractString('[', ']'))
  275. // Do a strict/every match if it's flagged as such, entry will only be processed if all keywords match as opposed to any.
  276. if (entryAttributes.some(attrib => attrib.includes('e')))
  277. { if (everyCheck(wEntry, lastTurnString)) { entryAttributes.forEach(attrib => entryFunctions[attrib[0]](wEntry, attrib[1], lastIndex)); used = true; } }
  278. // If it's not flagged with 'e' then process as normal (any check)
  279. else {entryAttributes.forEach(attrib => entryFunctions[attrib[0]](wEntry, attrib[1], lastIndex)); used = true;}
  280.  
  281. }
  282. catch (error) {console.log(error)} // Catch the error as it'd most likely not be destructive or otherwise detrimental.
  283. }
  284.  
  285. })
  286. }
  287.  
  288.  
  289. const modifier = (text) =>
  290. {
  291. state.memory.authorsNote = "";
  292. if(!state.generate){
  293. var trailingLine = false;
  294. if(text.slice(-1)=="\n"){trailingLine = true;}
  295.  
  296. // Position the various attribute tags, push them into temporary lists etc.
  297. if (worldEntries) {processWorldEntries(worldEntries);}
  298. // Position the various additional context content into their correct positions.
  299. Object.keys(contextStacks).forEach(key => {contextStacks[key][2](contextStacks[key][0], contextStacks[key][1])})
  300.  
  301. contextMemory = contextMemory.join('\n')
  302.  
  303. const combinedLines = lines.join("\n").slice(-(info.maxChars - contextMemory.length)) // Account for additional context added to memory
  304.  
  305. const finalText = [contextMemory, (combinedLines.slice(-1) == "\n" && trailingLine == false ? combinedLines.slice(0,-1) : combinedLines)].join("")
  306.  
  307.  
  308. // return {text: finalText.replace(/^[^A-Z].+[,;.:]/, '')}
  309. return{text:finalText};
  310. }else{
  311.  
  312. if(state.memory.frontMemory.length > 500){
  313. text = text.slice(0,-500)+state.memory.frontMemory.slice(0,-500)+text.slice(-500);
  314. }
  315.  
  316. return {text:text};
  317. }
  318. }
  319. modifier(text)
  320.  
  321. ///////////////// Output Modifier
  322.  
  323. // To prevent the AI from outputting parenthesized information, we simply remove it.
  324. const modifier = (text) =>
  325. {
  326. state.memory.frontMemory = "";
  327. let modifiedText = text;
  328. if(state.break){state.break = false;return {text:""}}
  329. if(!state.generate){
  330. modifiedText = modifiedText.replace(/ ?\(.*\)/gm, '');
  331. state.message = "";
  332. return {text: modifiedText}
  333. }
  334. else{
  335. console.log("Current output: "+modifiedText);
  336. modifiedText = modifiedText.split("}")[0];
  337.  
  338. if(state.newEntry.keys==""){
  339. modifiedText = modifiedText.split(")");
  340. state.newEntry.keys = reformatKeys(modifiedText[0],state.entryGen[state.entryGen.length-1]);
  341. modifiedText.shift()
  342. state.newEntry.entry = modifiedText.join(")");}
  343. else{
  344. state.newEntry.entry += modifiedText;
  345. }
  346. state.message = "New entry created: {(Tags: "+state.newEntry.keys+") "+state.newEntry.entry;
  347. state.newEntry.isNotHidden = true;
  348. worldEntries.push(state.newEntry);
  349. state.newEntry = {};
  350. state.generate = false;
  351. return{text:""}
  352. }
  353.  
  354.  
  355. }
  356. modifier(text)
Add Comment
Please, Sign In to add comment