Perchance Code
Perchance Code
If you open the advanced options in the character creation area then you'll see the "custom code" input. This allows you to add some JavaScript code that extend the
functionality of your character.
Examples
After reading this doc to get a sense of the basics, visit this page for more complex, "real-world" examples: Custom Code Examples
The oc Object
Within your custom code, you can access and update oc.thread.messages . It's an array that looks like this:
[
{
author: "user",
content: "Hello",
},
{
author: "ai",
content: "Hi.",
},
{
author: "system",
hiddenFrom: ["user"], // can contain "user" and/or "ai"
expectsReply: false, // this means the AI won't automatically reply to this message
content: "Here's an example system message that's hidden from the user and which the AI won't automatically reply to.",
},
]
The most recent message is at the bottom/end of the array. The author field can be user , ai , or system . Use "system" for guiding the AI's behavior, and including
context/info where it wouldn't make sense to have that context/info come from the user or the AI.
oc.thread.on("MessageAdded", function({message}) {
message.content = message.content.replaceAll(":)", " ᵔ ᵕ ᵔ ა");
});
You can edit existing messages like in this example, and you can also delete them by just removing them from the oc.thread.messages array (with pop , shift , splice , or
however else), and you can of course add new ones - e.g. with push / unshift .
Messages have a bunch of other properties which are mentioned futher down on this page. For example, here's how to randomize the text color of each message that is
added to the chat thread using the wrapperStyle property:
oc.thread.on("MessageAdded", function({message}) {
let red = Math.round(Math.random()*255);
let green = Math.round(Math.random()*255);
let blue = Math.round(Math.random()*255);
message.wrapperStyle = `color:rgb(${red}, ${green}, ${blue});`;
});
Note that your MessageAdded handler can be async , and it'll be await ed so that you can be sure your code has finished running before the AI responds.
You can also access and edit character data via oc.character.propertyName . Here's a full list of all the property names that you can access and edit on the oc object:
character
name - text/string
avatar
url - url to an image
roleInstruction - text/string describing the character and their role in the chat
reminderMessage - text/string reminding the character of things it tends to forget
initialMessages - an array of message objects (see thread.messages below for valid message properties)
imagePromptSuffix - text added after the prompt for all images generated by the AI in chats with this character
imagePromptTriggers - each line is of the form trigger phrase: description of the thing - see character editor for examples
shortcutButtons - an array of objects like {autoSend:false, insertionType:"replace", message:"/ai be silly", name: "silly response", clearAfterSend:true} .
When a new chat thread is created, a snapshot of these shortcutButtons is copied over to the thread , so if you want to change the current buttons in the thread,
you should edit oc.thread.shortcutButtons instead. Only change oc.character.shortcutButtons if you want to change the buttons that will be available for all future
chat threads created with this character.
insertionType can be replace , or prepend (put before existing text), or append (put after existing text)
clearAfterSend and autoSend can both be either true or false
thread
name - text/string
name - if this is not undefined , then it overrides the default ai/user/system name as which is oc.thread.character.name or if that's undefined , then
oc.character.name is used as the final fallback/default. If name is not defined for an author=="user" message, then oc.thread.userCharacter.name is the first
fallback, and then oc.character.userCharacter.name , and then oc.userCharacter.name (which is read-only). And for the system character the first fallback is
oc.thread.systemCharacter.name and then oc.character.systemCharacter.name .
avatar - this will override the user's/ai's default avatar for this particular message. See the above name property for info on fallbacks.
url - url to an image
size - multiple of default size (default value is 1 )
note that you can include HTML within the content of message (but you should use oc.messageRenderingPipeline for visuals where possible - see below)
instruction - the instruction that was written in /ai <instruction> or /user <instruction> - used when the regenerate button is clicked
scene - the most recent message that has a scene is the scene that is "active"
background
url - image or video url
name - text/string
avatar
url
size
shape
reminderMessage
roleInstruction
avatar
url
size
shape
systemCharacter - thread-specific system character overrides
name
avatar
url
size
shape
customData - thread-specific custom data storage
messageWrapperStyle - CSS applied to all messages in the thread, except those with message.wrapperStyle defined
shortcutButtons - see notes on oc.character.shortcutButtons , above.
messageRenderingPipeline - an array of processing functions that get applied to messages before they are seen by the user and/or the ai (see "Message Rendering"
section below)
Note that many character properties aren't available in the character editor UI, so if you e.g. wanted to add a stop sequence for your character so it stops whenever it writes
":)", then you could do it by adding this text to the custom code text box in the character editor:
oc.character.stopSequences = [":)"];
Here's some custom code which allows the AI to see the contents of webpages/PDFs if you put URLs in your messages:
Custom code is executed securely (i.e. in a sandboxed iframe), so if you're using a character that was created by someone else (and that has some custom code), then their
code won't be able to access your user settings or your messages with other characters, for example. The custom code only has access to the character data and the
messages for your current conversation.
Here's some custom code that adds a /charname command that changes the name of the character. It intercepts the user messages, and if it begins with /charname , then it
changes oc.character.name to whatever comes after /charname , and then deletes the message.
Events
Each of these events has a message object, and MessageDeleted has originalIndex for the index of the deleted message:
oc.thread.on("MessageAdded", function({message}) { ... }) - a message was added to the end of the thread (note: this event triggers after the message has finished
generating completely)
oc.thread.on("MessageEdited", function({message}) { ... }) - message was edited or regenerated
oc.thread.on("MessageInserted", function({message}) { ... }) - message was inserted (see message editing popup)
oc.thread.on("MessageDeleted", function({message, originalIndex}) { ... }) - user deleted a message (trash button)
oc.thread.on("MessageStreaming", function(data) { ... }) - see 'Streaming Messages' section below
The message object is an actual reference to the object, so you can edit it directly like this:
oc.thread.on("MessageAdded", function({message}) {
message.content += "blah";
})
Here's an example of how you can get the index of edited messages:
oc.thread.on("MessageEdited", function({message}) {
let editedMessageIndex = oc.thread.messages.findIndex(m => m === message);
// ...
});
Message Rendering
Sometimes you may want to display different text to the user than what the AI sees. For that, you can use oc.messageRenderingPipeline . It's an array that you .push() a
function into, and that function is used to process messages. Your function should use the reader parameter to determine who is "reading" the message (either user or ai ),
and then "render" the message content accordingly. Here's an example to get you started:
oc.messageRenderingPipeline.push(function({message, reader}) {
if(reader === "user") message.content += ""; // user will see all messages with a flower emoji appended
if(reader === "ai") message.content = message.content.replaceAll("wow", "WOW"); // ai will see a version of the message with all instances of "wow" capitalized
});
Visual Display and User Inputs
Your custom code runs inside an iframe. You can visually display the iframe using oc.window.show() (and hide with oc.window.hide() ). The user can drag the embed around
on the page and resize it. All your custom code is running within the iframe embed whether it's currently displayed or not. You can display content in the embed by just
executing custom code like document.body.innerHTML = "hello world" .
You can use the embed to e.g. display a dynamic video/gif avatar for your character that changes depending on the emotion that is evident in the characters messages
(example). Or to e.g. display the result of the p5.js code that the character is helping you write. And so on.
Here's how to use oc.getInstructCompletion (see the ai-text-plugin page for details on the parameters):
That gives you result.text , which is the whole text, including the startWith text that you specified, and result.generatedText , which is only the text that came after the
startWith text - i.e. only the text that the AI actually generated.
Here's how to use oc.textToImage (see the text-to-image-plugin page for details on some other parameters you can use):
And now you can use result.dataUrl , which will look something like data:image/jpeg;base64,s8G58o8ujR4..... . A data URL is like a normal URL, except the data is stored in
the URL itself instead of being stored on a server somewhere. But you can just treat it as if it were something like https://example.com/foo.jpeg .
You should use oc.getInstructCompletion for most tasks, but sometimes a chat-style API may be useful. Here's how to use oc.getChatCompletion :
Here's an example of some custom code that edits all messages to include more emojis:
Streaming Messages
See the text-to-speech plugin code for a "real-world" example of this.
Interactive Messages
You can use button onclick handlers in message so that e.g. the user can click a button to take an action instead of typing:
I recommend that you use oc.messageRenderingPipeline to turn a custom format into HTML, rather than actually having HTML in your messages (the HTML would use more
tokens, and might confuse the AI). So your format might look like this:
What would you like to do?
1. [[Fight]]
2. [[Run]]
You could prompt/instruct/remind your character to reply in that format with an instruction message that's something similar to this:
You are a game master. You creatively and engagingly simulate a world for the user. The user takes actions, and you describe the consequences.
Your messages should end with a list of possible actions, and each action should be wrapped in double-square brackets like this:
Actions:
1. [[Say sorry]]
2. [[Turn and run]]
oc.messageRenderingPipeline.push(function({message, reader}) {
if(reader === "user") {
message.content = message.content.replace(/\[\[(.+?)\]\]/g, (match, text) => {
let encodedText = encodeURIComponent(text); // this is a 'hacky' but simple way to prevent special characters like quotes from breaking the onclick attribute
return `<button onclick="oc.thread.messages.push({author:'user', content:decodeURIComponent('${encodedText}')});">${text}</button>`;
});
}
});
If you want to change something about the way this works (e.g. change the double-square-bracket format to something else), but don't know JavaScript, the "Custom Code
Helper" starter character might be able to help you make some adjustments.
Note that you can't use the this keyword within the button onclick handler - it actually just sends the code in the onclick to your custom code iframe and executes it there,
so there's no actual element that's firing the onclick from the iframe's perspective, and thus no this or event , etc.
Gotchas
function hello() {
console.log("hi");
}
document.body.innerHTML = `<div onclick="hello()">click me</div>`;
oc.window.show();
This is because all custom code is executed inside a <script type=module> so you need to make functions global if you want to access them from outside the module (e.g. in
click handlers). So if you want to the above code to work, you should define the hello function like this instead:
window.hello = function() {
console.log("hi");
}
FAQ
Is it possible to run a custom function before the AI tries to respond? I.e., after the user message lands, but before the AI responds? And then kick off the AI response
process after the async call returns?
Answer: Yep, the MessageAdded event runs every time a message is added - user or ai. So you can check if(oc.thread.messages.at(-1).author === "user") { ... }
(i.e. if latest message is from user) and the ... code will run right after the user responds, and before the ai responds.