From 4fd9a4c029cfe870b9dc08d02755a26c7996a401 Mon Sep 17 00:00:00 2001 From: Concedo <39025047+LostRuins@users.noreply.github.com> Date: Sun, 23 Aug 2026 15:37:31 +0800 Subject: [PATCH] updated lite --- embd_res/klite.embd | 892 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 857 insertions(+), 35 deletions(-) diff --git a/embd_res/klite.embd b/embd_res/klite.embd index a5d03f444..1a39a5afe 100644 --- a/embd_res/klite.embd +++ b/embd_res/klite.embd @@ -4241,15 +4241,15 @@ Current version indicated by LITEVER below. const ALLTALK_ID = 4; const OAI_TTS_ID = 5; - const HD_RES_PX = 768; - const VHD_RES_PX = 960; - const NO_HD_RES_PX = 512; - const PREVIEW_RES_PX = 200; - const AVATAR_PX = 384; - const SAVE_SLOTS = 12; - const SCENARIO_SLOTS = 6; - const num_regex_rows = 4; - const default_websearch_template = "### New Task:\nFrom above text, rephrase the search engine query \"{{QUERY}}\" as a single short phrase (for search engines) using proper nouns, references and names to avoid ambiguity.\n\n### Rephrased Search Query Created:\n"; + var HD_RES_PX = 768; + var VHD_RES_PX = 960; + var NO_HD_RES_PX = 512; + var PREVIEW_RES_PX = 200; + var AVATAR_PX = 384; + var SAVE_SLOTS = 12; + var SCENARIO_SLOTS = 6; + var num_regex_rows = 4; + var default_websearch_template = "### New Task:\nFrom above text, rephrase the search engine query \"{{QUERY}}\" as a single short phrase (for search engines) using proper nouns, references and names to avoid ambiguity.\n\n### Rephrased Search Query Created:\n"; //all configurable globals var unique_uid = "LITE_UID_"+(Math.floor(100000 + Math.random() * 900000)).toString(); @@ -4468,6 +4468,7 @@ Current version indicated by LITEVER below. tts_mode: 0, //0 is disabled xtts_voice: "female_calm", kcpp_tts_voice: "kobo", + oai_tts_model: "tts-1", oai_tts_voice: "alloy", wb_tts_choice: 0, kcpp_tts_json: "", @@ -4535,6 +4536,8 @@ Current version indicated by LITEVER below. show_nametags: true, enable_tool_use: false, tools_auto_exec: false, + custom_tools: [], //array of custom tools (with source code) + custom_tools_AI_enabled: false, corsproxy_mcp: false, kcpp_mcp_bridge: true, cached_mcp_tools: {}, //key is url, value is tools array @@ -4640,7 +4643,9 @@ Current version indicated by LITEVER below. saved_comfy_bearer_token: true, saved_xtts_url: true, generate_images_mode: true, - saved_mcp_urls: true + saved_mcp_urls: true, + custom_tools: true, + custom_tools_AI_enabled: true }; const defaultsettings = JSON.parse(JSON.stringify(localsettings)); @@ -8299,6 +8304,677 @@ Current version indicated by LITEVER below. }); } + var trusted_custom_tools = {}; //Functions are only evaluated when actually called (by user or AI) + var customtools_snapshot = null; //Temp UI editor + function customtools_empty_parameters() + { + return {type:"object",properties:{},additionalProperties:false}; + } + function customtools_clone(value) + { + return JSON.parse(JSON.stringify(value)); + } + function customtool_is_valid_name(name) + { + return (typeof name==="string" && /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/.test(name)); + } + function customtool_is_valid_param_name(name) + { + return (typeof name==="string" && /^[$A-Z_a-z][$0-9A-Z_a-z]*$/.test(name)); + } + function customtool_is_async(tool) + { + return (tool && tool.parameters && tool.parameters.additionalProperties && tool.parameters.additionalProperties.asynchronous); + } + function customtool_get_parameters(tool) + { + if(!tool || !tool.parameters || typeof tool.parameters!=="object" || Array.isArray(tool.parameters) || tool.parameters.type!=="object" || !tool.parameters.properties || typeof tool.parameters.properties!=="object" || Array.isArray(tool.parameters.properties)) + { + return null; + } + return tool.parameters.properties; + } + function customtool_schema_error(tool) + { + let params = customtool_get_parameters(tool); + if(params===null) + { + return "Parameters must be a JSON schema object with type:\"object\" and a properties object."; + } + let names = Object.keys(params); + for(let i=0;i typeof nam==="string" && params.hasOwnProperty(nam)))) + { + return "Required parameters must be an array of defined parameter names."; + } + return ""; + } + function customtool_normalize(tool) + { + let typetemplate = {name:"string",description:"string",parameters:"object",functionBody:"string",userCallable:"boolean"}; + if(!is_obj_with_types(tool,typetemplate,false)) + { + throw new Error("Object is not a custom tool"); + } + let clean = filter_obj_by_keys(tool,Object.keys(typetemplate)); + if(!customtool_is_valid_name(clean.name)) + { + throw new Error("Invalid tool name: "+clean.name); + } + let schemaerr = customtool_schema_error(clean); + if(schemaerr) + { + throw new Error(schemaerr); + } + return clean; + } + function customtools_sanitize_list(tools) + { + if(!Array.isArray(tools)) + { + return []; + } + let clean_tools = []; + let seen_names = {}; + for(let i=0;iFunction code.

Caution: These tools will have full access to your story and API keys, so only enable third-party tools that you trust!
Let AI Use Custom Tools? (Requires Toolcalling Enabled)

You can call selected tools with slash command syntax in the prompt submission box, e.g.
/toolname parameter1 parameter2 ... parameterN.

Want to get started? Click here to load some simple example tools.

`; + + if(customtools_snapshot.length>0) + { + ct += `` + +`` + +`` + +`` + +`` + +``; + + for (let i=0; i`; + ct += ``; + ct += ``; + ct += ``; + } + ct += `
Tool NameEdit/CallDel.
${escape_html(tool.name)}
${escape_html(desc)}
`; + } + else + { + ct += "

No custom tools defined.


"; + } + ct += `
`; + + document.getElementById("customtoolsitems").innerHTML = ct; + } + function customtools_done(save) + { + if(save) + { + customtools_capture_user_callable(); + let customtools_edited = false; + customtools_snapshot.forEach(tool => { + if(tool.tmp_edited){ + customtools_edited = true; + delete tool["tmp_edited"]; + } + }); + if(customtools_edited){ + trusted_custom_tools = {}; + } + localsettings.custom_tools = customtools_snapshot; + localsettings.custom_tools_AI_enabled = (document.getElementById("enable_custom_tools_AI").checked?true:false); + } + customtools_snapshot = null; + document.getElementById("customtoolscontainer").classList.add("hidden"); + hide_popups(); + } + function simplecustomtoolsexample() + { + customtools_snapshot = [ + {name:"mult",description:"Multiply two numbers",parameters:{type:"object",properties:{a:{type:"number"},b:{type:"number",default:"42"}},required:["a"],additionalProperties:false},functionBody:"return a*b;",userCallable:true}, + {name:"set_nostalgia_theme",description:"Sets the user's interface to the Nostalgia theme.",parameters:{type:"object",properties:{},additionalProperties:false},functionBody:"document.getElementById('colortheme').value = localsettings.colortheme = 1;\ntoggle_theme_colors();",userCallable:true}, + {name:"critic_agent",description:"Independently reviews the input text.",parameters:{type:"object",properties:{inputtxt:{type:"string"}},required:["inputtxt"],additionalProperties:{asynchronous:true}},functionBody:"return fetch(\n\tapply_proxy_url(custom_kobold_endpoint + kobold_custom_gen_endpoint),\n\t{\n\t\tmethod: 'POST',\n\t\theaders: get_kobold_header(),\n\t\tbody: JSON.stringify({\n\t\t\t'prompt': get_instruct_starttag(false) + 'Scrutinize the following text, and identify one area for improvement:\\n\"\"\"\\n' + inputtxt + '\\n\"\"\"' + get_instruct_endtag(false) + 'Here is a brief critique:\\n',\n\t\t\t'max_length': 512,\n\t\t\t'temperature': localsettings.temperature,\n\t\t\t'quiet': false\n\t\t})\n\t}\n).then(x => x.json())\n.then((resp) => resp.results[0].text);",userCallable:false} + ]; + show_customtools(); + } + function export_customtools_to_file() + { + customtools_capture_user_callable(); + let exported = customtools_snapshot.map(tool => filter_obj_by_keys(tool,["name","description","parameters","functionBody","userCallable"])); + saveFileGeneric("custom_tools",JSON.stringify(exported),"application/json"); + } + function load_customtools_from_file() + { + promptUserForLocalFile((fileDetails) => { + try + { + let { file, fileName, ext, content, plaintext } = fileDetails; + let filecontent = JSON.parse(plaintext); + if(filecontent && (typeof filecontent==="object") && filecontent.hasOwnProperty("savedsettings")){ + //assume it's a kai file + filecontent = filecontent.savedsettings.custom_tools; + } + if(!Array.isArray(filecontent)){ + throw new Error("Custom tools file must contain an array"); + } + //else assume it's an exported array + let has_unknown_attributes = false; + let clean_tools = []; + let seen_names = {}; + for (let i = 0; i < filecontent.length; ++i) { + let clean = customtool_normalize(filecontent[i]); + + has_unknown_attributes = + has_unknown_attributes || (Object.keys(filecontent[i]).length > 5); + + let lowername = clean.name.toLowerCase(); + + if (seen_names[lowername]) { + throw new Error("Duplicate tool name: " + clean.name); + } + + seen_names[lowername] = true; + clean_tools.push(clean); + } + if(has_unknown_attributes){ //for security, we destroy them (but alert the user) + msgbox("Warning: Found unknown attributes in your custom tools JSON (not imported).","Custom Tools Import Warning"); + } + customtools_snapshot = clean_tools; + show_customtools(); + } + catch (e) { + msgbox("Custom Tools File Import Failed: "+e); + return; + } + }); + } + function is_obj_with_types(inp, typObj, strict=true) //check if inp is an object with typed fields specified by typObj + { + return inp && (typeof inp==="object") && !Array.isArray(inp) && + Object.keys(typObj).map(k => {return inp.hasOwnProperty(k) && (typeof inp[k]===typObj[k])}).every(Boolean) && + (!strict || Object.keys(inp).length===Object.keys(typObj).length); //if strict, then no other fields are allowed + } + function filter_obj_by_keys(obj,keys) + { + let filteredObj = {}; + for (let k of keys){ + filteredObj[k] = obj[k]; + } + return filteredObj; + } + function edit_customtool(idx=null) //we stash the working tmptool at the end of the customtools_snapshot array + { + if(document.getElementById("customtooleditcontainer").classList.contains("hidden")) + { + customtools_capture_user_callable(); + let tool = (customtools_snapshot.length===idx) ? + {name:"", description:"", parameters:customtools_empty_parameters(), functionBody:"",userCallable:false} : + customtools_clone(filter_obj_by_keys(customtools_snapshot[idx],["name","description","parameters","functionBody","userCallable"])); + tool.tmp_origidx = idx; + customtools_snapshot.push(tool); + + document.getElementById("customtooleditcontainer").classList.remove("hidden"); + document.getElementById("customtooleditor_name").value = tool.name; + document.getElementById("customtooleditor_description").value = tool.description; + let paramsjsonstr = JSON.stringify(tool.parameters); + document.getElementById("customtooleditor_parametersjson").value = paramsjsonstr; + document.getElementById("customtooleditor_functionbody").value = tool.functionBody; + document.getElementById("customtooleditor_asynchronous").checked = customtool_is_async(tool); + try { //check if parameters JSON can be completely reconstructed from the template + document.getElementById("customtooleditor_editparametersjsondirectly").checked = (JSON.stringify(customtooleditor_templated_json(false))!==paramsjsonstr); + } catch (e) { + document.getElementById("customtooleditor_editparametersjsondirectly").checked = true; + } + customtooleditor_parameters_toggle(); + } + + let tool = customtools_snapshot[customtools_snapshot.length-1]; + let toolargs = Object.keys(tool.parameters.properties); + let ctep = "

This tool has no parameters.

"; + if(toolargs.length) + { + let selectable_types = ["string","number","boolean","integer","null","array","object"]; //"function" + ctep = `` + +`` + +`` + +`` + +`` + +`` + +``; + for( let i=0; i` + +`` + +`` + +``; + } + ctep += `
Name TypeDefault ValueDelete
Set Default?
`; + } + ctep += `
Add Parameter
`; + document.getElementById("customtooleditor_parameterstemplatecontainer").innerHTML = ctep; + } + function customtooleditor_templated_json(usetemplate=true) //returns a parameters JSON object, or an error string + { + let newparams = {type:"object",properties:{}}; + let newrequired = []; + let toolprops = customtools_snapshot[customtools_snapshot.length-1].parameters.properties; + for(let i=0; i tool.name.toLowerCase()===newname.toLowerCase()); + let idx = customtools_snapshot[customtools_snapshot.length-1].tmp_origidx; + if(![-1,customtools_snapshot.length-1,idx].includes(namematch)) + { + msgbox("Duplicate tool names are not allowed!"); + return; + } + let newparameters = document.getElementById("customtooleditor_editparametersjsondirectly").checked; + if(newparameters) + { + try + { + newparameters = JSON.parse(document.getElementById("customtooleditor_parametersjson").value); + if(!newparameters || (typeof newparameters !== "object")) + { + msgbox("Must specify a parameters JSON object!"); + return; + } + let schemaerr = customtool_schema_error({parameters:newparameters}); + if(schemaerr) + { + msgbox(schemaerr); + return; + } + } catch(e) { + msgbox("Error parsing parameters JSON: "+e.message); + return; + } + } + else + { + newparameters = customtooleditor_templated_json(); + if(typeof newparameters==="string") //Error + { + msgbox(newparameters); + return; + } + } + + let tool = customtools_snapshot[idx]; + if(tool.name!==newname) + { + tool.name = newname; + tool["tmp_edited"] = true; + } + if(tool.description!==document.getElementById("customtooleditor_description").value) + { + tool.description = document.getElementById("customtooleditor_description").value; + } + if(JSON.stringify(tool.parameters)!==JSON.stringify(newparameters)) + { + tool.parameters = newparameters; + tool["tmp_edited"] = true; + } + if(tool.functionBody!==document.getElementById("customtooleditor_functionbody").value) + { + tool.functionBody = document.getElementById("customtooleditor_functionbody").value; + tool["tmp_edited"] = true; + } + if(idx===customtools_snapshot.length-1) //newly added tool + { + delete tool["tmp_origidx"]; + tool["tmp_edited"] = true; + customtools_snapshot.push(null); //push a dummy value to get popped + } + } + customtools_snapshot.pop(); //clear the working tmptool + document.getElementById("customtooleditcontainer").classList.add("hidden"); + show_customtools(); + } + + function parse_slash_command(tool, senttext) //simple domain-specific language, returns args object or string error + { + let toolprops = customtool_get_parameters(tool); + if(toolprops===null){ + return "Error parsing slash command (/"+tool.name+"): invalid tool parameters"; + } + let toolparams = Object.keys(toolprops); + let sentargs = {}; + if(tool.name.length+1>=senttext.length){ + return sentargs; + } + let paramidx = -1; + let currarg = ""; + let currstack = []; //tracks nesting depth, empty=toplevel + let inquotes = false; + for(let i=tool.name.length+1; i !sentargs.hasOwnProperty(nam)); + //if this still == -1 (i.e. all arguments already bound) + //then we assume currarg is a trailing comment, and discard it + } + if(paramidx!==-1){ + sentargs[toolparams[paramidx]] = currarg; + paramidx = -1; + } + currarg = ""; + } //else ch is inter-argument whitespace, discard it + continue; + } + if(ch==='[' || ch==='{' || ch==='(' || ch==='"' || ch==="'") //handle nested delimiters (do NOT continue next char) + { + currstack.push(ch); + inquotes = (ch==='"' || ch==="'"); + } + else if(ch===']' || ch==='}' || ch===')') + { + if(!(ch===']' && stacktop==='[') && !(ch==='}' && stacktop==='{') && !(ch===')' && stacktop==='(')){ + return "Error parsing slash command (/"+tool.name+"): mismatched delimiters ('"+stacktop+"' followed by '"+ch+"')"; + } + currstack.pop(); + } + currarg += ch; //ordinary character + } + //end of senttext input + if(currstack.length){ + return "Error parsing slash command (/"+tool.name+"): unmatched delimiter "+currstack.pop(); + } + if(paramidx!==-1 || currarg.length) //last argument + { + if(paramidx===-1){ + paramidx = toolparams.findIndex(nam => !sentargs.hasOwnProperty(nam)); + } + if(paramidx!==-1){ + sentargs[toolparams[paramidx]] = currarg; + } + } + return sentargs; + } + function customtool_parse_literal(value) + { + if(typeof value!=="string") + { + return value; + } + let trimmed = value.trim(); + if(trimmed==="") + { + return ""; + } + if((trimmed[0]==="'" && trimmed[trimmed.length-1]==="'") || (trimmed[0]==='"' && trimmed[trimmed.length-1]==='"')) + { + if(trimmed[0]==="'") + { + return trimmed.slice(1,-1).replace(/\\'/g,"'").replace(/\\\\/g,"\\"); + } + try { + return JSON.parse(trimmed); + } catch(e) { + return trimmed.slice(1,-1); + } + } + if(/^(true|false|null)$/i.test(trimmed) || /^-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(trimmed) || /^[\[{]/.test(trimmed)) + { + try { + return JSON.parse(trimmed); + } catch(e) { + return value; + } + } + return value; + } + function customtool_coerce_arg(value, propobj) + { + let type = propobj.type?propobj.type:""; + let argval = customtool_parse_literal(value); + switch(type) + { + case "string": + return (argval===undefined?undefined:String(argval)); + case "number": + argval = Number(argval); + if(Number.isNaN(argval)){ throw new Error("Parameter must be a number"); } + return argval; + case "boolean": + if(typeof argval==="string") + { + if(argval.toLowerCase()==="true"){ return true; } + if(argval.toLowerCase()==="false"){ return false; } + } + return Boolean(argval); + case "integer": + argval = Number(argval); + if(Number.isNaN(argval)){ throw new Error("Parameter must be an integer"); } + return Math.trunc(argval); + case "null": + return (argval==null)?null:undefined; + case "array": + if(!Array.isArray(argval)){ throw new Error("Parameter must be an array"); } + return argval; + case "object": + if(argval===null || typeof argval!=="object" || Array.isArray(argval)){ throw new Error("Parameter must be an object"); } + return argval; + default: + return argval; + } + } + function TrustedCustomToolCallPromise(tool, argsobj){ + return Promise.resolve() + .then(()=>{ + argsobj = argsobj || {}; + let schemaerr = customtool_schema_error(tool); + if(schemaerr){ throw new Error(schemaerr); } + let toolparamobj = customtool_get_parameters(tool); + let toolparams = Object.keys(toolparamobj); + if(tool.parameters.required) + { + for(let i=0;i { + let propobj = toolparamobj[nam]; + let argval = Object.prototype.hasOwnProperty.call(argsobj, nam) ? argsobj[nam] + : Object.prototype.hasOwnProperty.call(propobj, "default") ? propobj.default + : undefined; + return customtool_coerce_arg(argval, propobj); + }); + let name = tool.name; + if(!trusted_custom_tools.hasOwnProperty(name)) //evaluates the tool's functionBody + { + trusted_custom_tools[name] = new Function(...toolparams, tool.functionBody); + } + let result = trusted_custom_tools[name](...callargs); + if(result) + { + console.log("Custom Tool Call OK"); + console.log(result); + } + return (tool.parameters.additionalProperties && tool.parameters.additionalProperties.asynchronous) ? + result : Promise.resolve(result); + }) + .catch(err => { + console.log("Custom Tool Call Error"); + console.log(err || String(err)); + throw err; + }) + } + //MCP HTTP client code const MCP_PROTOCOL_VER = "2025-11-25"; var mcp_req_id = 0; @@ -8626,16 +9302,53 @@ Current version indicated by LITEVER below. pending_response_id = "toolcall-v1-dummy-id-"+(Math.floor(1000 + Math.random() * 9000)).toString(); //dummy id, autogenerated let mcpurl = GetMCPUrlOfTool(callresp.name); - const customHeaders = localsettings.cached_mcp_tools[mcpurl].apikey ? - { 'Authorization': `Bearer ${localsettings.cached_mcp_tools[mcpurl].apikey}` } : {}; - if(localsettings.corsproxy_mcp) + let MCPToolCallPromise = null; + if(mcpurl==="custom_tools" && localsettings.custom_tools_AI_enabled) //hook in the custom tool { - mcpurl = apply_proxy_url(mcpurl,true); + MCPToolCallPromise = Promise.resolve() + .then(() => { + // Validate inputs + if(!localsettings.cached_mcp_tools || Object.keys(localsettings.cached_mcp_tools).length === 0 || !callresp.name) { + throw new Error("tool call invalid parameters"); + } + let customtools = customtools_sanitize_list(localsettings.custom_tools); + let idx = customtools.map(tool => tool.name).indexOf(callresp.name); + if(idx===-1){ + throw new Error("Custom tool not found"); + } + // Simulate the tool call in Javascript + return TrustedCustomToolCallPromise(customtools[idx],callargs); + }).then((result) => { + let restype = typeof(result); + if(restype==="string" && result.startsWith("data:image")) + { + restype = "image_url"; + result = {url:result}; + } + if(restype==="undefined") //if the function has no return value, we add one + { + restype = "string"; + result = "Operation completed successfully"; + } + let content = {type: restype}; + content[restype] = result; + return {"content":[content]}; + }); } - MCPInit(mcpurl, customHeaders) - .then(mcp_client => { - return MCPToolCallInternal(mcp_client,callresp.name,callargs); - }).then(function (response) { + else + { + const customHeaders = localsettings.cached_mcp_tools[mcpurl].apikey ? + { 'Authorization': `Bearer ${localsettings.cached_mcp_tools[mcpurl].apikey}` } : {}; + if(localsettings.corsproxy_mcp) + { + mcpurl = apply_proxy_url(mcpurl,true); + } + MCPToolCallPromise = MCPInit(mcpurl, customHeaders) + .then(mcp_client => { + return MCPToolCallInternal(mcp_client,callresp.name,callargs); + }); + } + MCPToolCallPromise.then(function (response) { if(response.content && response.content.length>0) { let outitems = []; @@ -8711,6 +9424,9 @@ Current version indicated by LITEVER below. let url = apply_proxy_url(custom_kobold_endpoint + koboldcpp_mcp_endpoint); servers[url] = { apikey: (custom_kobold_key!=""?custom_kobold_key:null), tools:[] }; } + if(localsettings.custom_tools_AI_enabled){ + servers["custom_tools"] = { apikey: null, tools: [] }; + } return servers; } function Fetch_MCP_Tools() //connects to multiple mcp servers, updates the tool list @@ -8723,6 +9439,11 @@ Current version indicated by LITEVER below. const fetchPromises = []; urls.forEach(url => { + if(url==="custom_tools" && localsettings.custom_tools_AI_enabled) + { + let customtools = customtools_sanitize_list(localsettings.custom_tools); + fetchPromises.push({url:url, apikey:null, tools:customtools.map(tool => ({type:"function",function:filter_obj_by_keys(tool,["name","description","parameters"])}))}); + } else { const customHeaders = urlsobj[url].apikey ? { 'Authorization': `Bearer ${urlsobj[url].apikey}` } : {}; @@ -8745,15 +9466,26 @@ Current version indicated by LITEVER below. }); fetchPromises.push(fetchPromise); + } }); // wait till all fetched or failed Promise.all(fetchPromises) .then(results => { pending_cached_mcp_tools = {}; + let seen_tool_names = {}; for(let i=0;i { + let toolname = tool && tool.function ? tool.function.name : ""; + if(!toolname || seen_tool_names[toolname.toLowerCase()]) + { + return false; + } + seen_tool_names[toolname.toLowerCase()] = true; + return true; + }); pending_cached_mcp_tools[itm.url] = {apikey: itm.apikey, tools:itm.tools }; } Render_MCP_Tools(pending_cached_mcp_tools,pending_disabled_mcp_tools); @@ -8774,6 +9506,7 @@ Current version indicated by LITEVER below. const toolscontainer = document.getElementById("tools_list_container"); let toolshtml = ``; let hasAnyTools = false; + let toolidx = 0; for(let url in mcp_tools) { @@ -8781,17 +9514,20 @@ Current version indicated by LITEVER below. itm.tools.forEach(tool => { const toolFunction = tool.function || tool; hasAnyTools = true; + let checkboxid = "mcptool_"+toolidx; + let tooldesc = toolFunction.description ? toolFunction.description.substr(0, 180) : "No description"; toolshtml += ` `; + toolidx++; }); } toolshtml += `
ToolEnabled
-
${toolFunction.name}
-
${toolFunction.description ? toolFunction.description.substr(0, 180) : 'No description'}
+
${escape_html(toolFunction.name)}
+
${escape_html(tooldesc)}
- +
`; @@ -10281,6 +11017,8 @@ Current version indicated by LITEVER below. new_save_storyobj.savedsettings.saved_comfy_bearer_token = ""; new_save_storyobj.savedsettings.saved_xtts_url = ""; new_save_storyobj.savedsettings.saved_mcp_urls = ""; + new_save_storyobj.savedsettings.custom_tools = []; + new_save_storyobj.savedsettings.custom_tools_AI_enabled = false; new_save_storyobj.savedsettings.modelhashes = []; @@ -16276,6 +17014,7 @@ Current version indicated by LITEVER below. } document.getElementById("ttsselect").value = localsettings.tts_mode; document.getElementById("kcpp_tts_voice").value = localsettings.kcpp_tts_voice; + document.getElementById("oai_tts_model").value = localsettings.oai_tts_model; document.getElementById("oai_tts_voice").value = localsettings.oai_tts_voice; if(wbvoices) { @@ -17132,7 +17871,10 @@ Current version indicated by LITEVER below. localsettings.tts_mode = document.getElementById("ttsselect").value; localsettings.xtts_voice = document.getElementById("xtts_voices").value; localsettings.kcpp_tts_voice = document.getElementById("kcpp_tts_voice").value; - localsettings.oai_tts_voice = document.getElementById("oai_tts_voice").value?document.getElementById("oai_tts_voice").value:defaultsettings.oai_tts_voice; + let oai_tts_model = document.getElementById("oai_tts_model").value; + let oai_tts_voice = document.getElementById("oai_tts_voice").value; + localsettings.oai_tts_model = oai_tts_model?oai_tts_model:defaultsettings.oai_tts_model; + localsettings.oai_tts_voice = oai_tts_voice?oai_tts_voice:defaultsettings.oai_tts_voice; localsettings.wb_tts_choice = document.getElementById("wb_tts_choice").value?document.getElementById("wb_tts_choice").value:0; localsettings.kcpp_tts_json = kcpp_tts_json; localsettings.beep_notify_mode = document.getElementById("beep_notify_mode").value; @@ -17173,16 +17915,18 @@ Current version indicated by LITEVER below. localsettings.saved_mcp_urls = document.getElementById("mcpurls").value.trim(); localsettings.cached_mcp_tools = JSON.parse(JSON.stringify(pending_cached_mcp_tools)); pending_disabled_mcp_tools = []; + let mcptoolidx = 0; for(key in localsettings.cached_mcp_tools) { let currarr = localsettings.cached_mcp_tools[key].tools; for(let i=0;i
${(curr)}\n

`; + let expandedhtml = `
${(curr)}\n
\n`; if(!curr || curr.trim()=="" || curr.trim()=="/nothink" || curr.trim()==`${localsettings.start_thinking_tag}${localsettings.stop_thinking_tag}`) { - expandedhtml = `
`; + expandedhtml = `\n`; } ++matchiter; return expandedhtml; @@ -19461,6 +20205,15 @@ Current version indicated by LITEVER below. ttsMasterGain.connect(ttsAudioContext.destination); function tts_speak(text, do_download=false, do_embed_tts=false, is_test=false) { + if(!text || text=="" || text.trim()=="") + { + return; + } + text = strip_tts_media_placeholders(text); + if(!text || text=="" || text.trim()=="") + { + return; + } let ssval = localsettings.tts_mode; let streamallowed = (ssval==XTTS_ID || ssval==ALLTALK_ID || ssval==OAI_TTS_ID || ssval==KCPP_TTS_ID); if(streamallowed && localsettings.tts_stream && !is_test) @@ -19477,6 +20230,16 @@ Current version indicated by LITEVER below. tts_speak_direct(text,do_download,do_embed_tts,is_test,false); } } + + function strip_tts_media_placeholders(text) + { + text = text.replace(/[\s\S]*?<\/t2i>/g, ""); + text = text.replace(/\[<\|p\|.+?\|p\|>\]/g, ""); + text = text.replace(/\[<\|h\|.+?\|h\|>\]/g, ""); + text = text.replace(/{{\[DAT_.{1,8}_REF\]}}/g, ""); + return text; + } + function poll_chunked_tts() { if(tts_speak_input_queue.length>0 && !tts_is_processing && tts_speak_output_queue.length < 3) @@ -19511,6 +20274,11 @@ Current version indicated by LITEVER below. function tts_speak_direct(text, do_download=false, do_embed_tts=false, is_test=false, append_to_outqueue=false) { + if(!text || text=="" || text.trim()=="") + { + return; + } + text = strip_tts_media_placeholders(text); if(!text || text=="" || text.trim()=="") { return; @@ -20084,6 +20852,24 @@ Current version indicated by LITEVER below. corpo_edit_chunk_save(); } let senttext = document.getElementById("input_text").value; + let toolname = senttext.startsWith('/')?senttext.slice(1).match(/^\S*/)[0]:''; + let customtools = customtools_sanitize_list(localsettings.custom_tools); + let idx = customtools.map(tool => tool.name).indexOf(toolname); + if(idx!==-1 && customtools[idx].userCallable){ + //try to parse senttext as a slash command + let sentargs = parse_slash_command(customtools[idx],senttext); + if(typeof sentargs==="string"){ + msgbox(sentargs); + return; + } + document.getElementById("input_text").value = "Slash command (/"+toolname+") activated, please wait..."; + TrustedCustomToolCallPromise(customtools[idx],sentargs).then(function (res) { + document.getElementById("input_text").value = res?res:("Slash command (/"+toolname+") executed successfully."); + }).catch(function (err) { + document.getElementById("input_text").value = "Slash command (/"+toolname+") error: "+err.message; + }); + return; + } document.getElementById("input_text").value = ""; PerformWebsearch(senttext,(res)=>{ submit_generation(senttext); @@ -23332,15 +24118,6 @@ Current version indicated by LITEVER below. let pat = new RegExp(get_thinking_regex(), "gmi"); gentxtspeak = gentxtspeak.replace(pat, ''); } - //remove t2i - if (localsettings.img_autogen_type == 2) - { - const pat = /(.*?)<\/t2i>/g; - gentxtspeak = gentxtspeak.replace(pat, ""); - const pat2 = /{{\[DAT_.{1,8}_REF\]}}/g; - gentxtspeak = gentxtspeak.replace(pat2, ""); - } - if(localsettings.narrate_targets != 0) { tts_speak(gentxtspeak,false,localsettings.embed_narrations,false); @@ -31721,6 +32498,10 @@ Current version indicated by LITEVER below. class="helptext">If disabled, you need to manually execute every tool call the AI suggests. +
+
Define Custom Tools ?Allows you to build your own tools as Javascript functions (caution).
+ +
Use CORS Proxy for MCP ?Many public MCP servers restrict CORS, use this to proxy your requests. Uses external tools, please only use if necessary.
@@ -33076,6 +33857,47 @@ Current version indicated by LITEVER below.
+ + + +