Custom 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.
Some examples of what you can do with this:
- Allow a character to transform/edit itself (like the "Unknown" starter character)
- Give your character access to the internet (e.g. so you can ask it to summarise webpages)
- Improve your character's memory by setting up your own embedding/retrieval system (see "Storing Data" section below)
- Give your character a custom voice using an API like ElevenLabs
- Allow your character to run custom JS or Python code
- Give your character the ability to create pictures using Stable Diffusion
- Auto-delete/retry messages from your character that contain certain keywords
- Change the background image of the chat, or the chat bubble style, or the avatar images, or the music, depending on what's happening in your story
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:
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.
Below is an example that replaces :)
with ૮ ˶ᵔ ᵕ ᵔ˶ ა
in every message that is added to the thread. Just paste it into the custom code box to try it out.
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:
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/stringavatar
url
- url to an imagesize
- multiple of default size (default value is1
)shape
- "circle" or "square" or "portrait"
roleInstruction
- text/string describing the character and their role in the chatreminderMessage
- text/string reminding the character of things it tends to forgetinitialMessages
- an array of message objects (seethread.messages
below for valid message properties)customCode
- yep, a character can edit its own custom codeimagePromptPrefix
- text added before the prompt for all images generated by the AI in chats with this characterimagePromptSuffix
- text added after the prompt for all images generated by the AI in chats with this characterimagePromptTriggers
- each line is of the formtrigger phrase: description of the thing
- see character editor for examplesshortcutButtons
- 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 theseshortcutButtons
is copied over to thethread
, so if you want to change the current buttons in the thread, you should editoc.thread.shortcutButtons
instead. Only changeoc.character.shortcutButtons
if you want to change the buttons that will be available for all future chat threads created with this character.insertionType
can bereplace
, orprepend
(put before existing text), orappend
(put after existing text)clearAfterSend
andautoSend
can both be eithertrue
orfalse
name
is just the label used for the buttonmessage
is the content that you want to send or insert into the reply box
streamingResponse
-true
orfalse
(default istrue
)customData
- an object/dict where you can store arbitrary dataPUBLIC
- a special sub-property ofcustomData
that will be shared within character sharing URLs
thread
name
- text/stringmessages
- an array of messages, where each message has:content
- required - the message text - it can include HTML, and is rendered as markdown by default (seeoc.messageRenderingPipeline
)author
- required - "user" or "ai"name
- if this is notundefined
, then it overrides the default ai/user/system name as which isoc.thread.character.name
or if that'sundefined
, thenoc.character.name
is used as the final fallback/default. Ifname
is not defined for anauthor=="user"
message, thenoc.thread.userCharacter.name
is the first fallback, and thenoc.character.userCharacter.name
, and thenoc.userCharacter.name
(which is read-only). And for the system character the first fallback isoc.thread.systemCharacter.name
and thenoc.character.systemCharacter.name
.hiddenFrom
- array that can contain "user" or "ai" or both or neitherexpectsReply
-true
(bot will reply to this message) orfalse
(bot will not reply), orundefined
(use default behavior - i.e. reply to user messages, but not own messages)customData
- message-specific custom data storageavatar
- this will override the user's/ai's default avatar for this particular message. See the abovename
property for info on fallbacks.url
- url to an imagesize
- multiple of default size (default value is1
)shape
- "circle" or "square" or "portrait"
wrapperStyle
- css for the "message bubble" - e.g. "background:white; border-radius:10px; color:grey;"- note that you can include HTML within the
content
of message (but you should useoc.messageRenderingPipeline
for visuals where possible - see below)
- note that you can include HTML within the
instruction
- the instruction that was written in/ai <instruction>
or/user <instruction>
- used when the regenerate button is clickedscene
- the most recent message that has a scene is the scene that is "active"background
url
- image or video urlfilter
- css filter - e.g.hue-rotate(90deg); blur(5px)
music
url
- audio url (also supports video urls)volume
- between 0 and 1
character
- thread-specific character overridesname
- text/stringavatar
url
size
shape
reminderMessage
roleInstruction
userCharacter
- thread-specific user character overridesname
avatar
url
size
shape
systemCharacter
- thread-specific system character overridesname
avatar
url
size
shape
customData
- thread-specific custom data storagemessageWrapperStyle
- CSS applied to all messages in the thread, except those withmessage.wrapperStyle
definedshortcutButtons
- see notes onoc.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:
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 regeneratedoc.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:
Here's an example of how you can get the index of edited messages:
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:
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.
Using the AI in Your Custom Code
You may want to use GPT/LLM APIs in your message processing code. For example, you may want to classify the sentiment of a message in order to display the correct avatar (see "Visual Display ..." section), or you may want to implement your own custom chat-summarization system, for example. In this case, you can use oc.getInstructCompletion
or oc.textToImage
.
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
:
The messages
parameter is the only required one.
Here's an example of some custom code that edits all messages to include more emojis:
Storing Custom Data
If you'd like to save some data that is generated by your custom code, then you can do that by using oc.thread.customData
- e.g. oc.thread.customData.foo = 10
. You can also store custom data on individual messages like this: message.customData.foo = 10
. If you want to store data in the character itself, then use oc.character.customData.foo = 10
, but note that this data will not be shared within character share links. If you do want to save the data to the character in a way that's preserved in character share links, then you should store data under oc.character.customData.PUBLIC
- e.g. oc.character.customData.PUBLIC = {foo:10}
.
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:
You could prompt/instruct/remind your character to reply in that format with an instruction message that's something similar to this:
And then you'd add this to your custom code:
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> is not defined" in click/event handlers
The following code won't work:
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:
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 checkif(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.
- Answer: Yep, the