Neocortex
IntegrationsWeb SDK

Components

Drop-in Web Components for chat, group chat, quizzes, microphone input and usage status

Web Components with shadow DOM, for plain HTML or any framework.

Registering the elements

ImportEffect
import "@neocortex-link/web/ui/auto"Registers every <neocortex-*> element on import.
import { defineNeocortexElements } from "@neocortex-link/web/ui"Exports the element classes without registering them. Call defineNeocortexElements() when you are ready.

In plain HTML, load the auto entry from a CDN:

<script type="module" src="https://cdn.jsdelivr.net/npm/@neocortex-link/web@0.2/dist/ui/auto.js"></script>

defineNeocortexElements() is idempotent and a no-op outside the browser, so it is SSR-safe.

Elements

ElementWhat it is
<neocortex-chat>A complete one-to-one chat: history, typing, mic input, spoken replies.
<neocortex-group-chat>Several characters in one conversation, each in their own colour.
<neocortex-quiz>A quiz host with questions, options, hints, progress and a leaderboard (Team plan).
<neocortex-chat-panel>The scrolling message list on its own.
<neocortex-message>One chat bubble.
<neocortex-text-input>A text box with a send button. Fires nx-send.
<neocortex-mic-button>A microphone button with a level ring and silence countdown. Fires nx-recorded.
<neocortex-mic-select>A microphone picker.
<neocortex-thinking>Typing dots.
<neocortex-usage-badge>Shows whether characters are available or out of credits.

voice is on when present and off when absent or set to "false". history and mic are on unless set to "false". The other boolean attributes (push-to-talk, no-header, disabled, active) are on whenever present, whatever their value.

neocortex-chat

<neocortex-chat endpoint="/api/neocortex" character-id="your-character-id" voice></neocortex-chat>
AttributeDescription
character-idThe character to talk to. Required. Changing it starts a new agent.
endpointYour proxy route. When left out, the element uses the client set with setDefaultClient, or /api/neocortex.
voiceSpeak replies aloud. The header has a toggle for it.
push-to-talkThe mic records only while held, instead of stopping after a pause. push-to-talk="false" turns it off.
headingHeader title. When empty, the character's name is shown once it speaks.
placeholderPlaceholder text of the input.
languageLanguage for speech and transcription, for example en or tr.
history="false"Don't load the previous conversation on start.
mic="false"Hide the microphone button.
no-headerHide the header.
themelight or dark. Follows prefers-color-scheme when left out.

Properties and methods

  • agent is the SmartAgent behind the element. Register actions or listen to its events on it.
  • client gets or sets the NeocortexClient the element uses. Setting it overrides endpoint and starts a new agent.
  • reset() stops the current reply, forgets the conversation and clears the panel. The header's reset button does the same.
const chat = document.querySelector("neocortex-chat");
chat.agent.registerAction("OPEN_DOOR", (action) => door.open(action.targetId));

Changing character-id, endpoint or client creates a new agent; register handlers again. The conversation persists in localStorage.

neocortex-group-chat

<neocortex-group-chat endpoint="/api/neocortex" character-ids="aria,bram" voice></neocortex-group-chat>

Takes the same attributes as <neocortex-chat>, with these instead of character-id:

AttributeDescription
character-idsComma-separated character ids. Changing it starts a new agent.
max-turnsHow many characters may speak in one turn.

Each character's name and bubble accent get their own colour. The header has a Let them talk button that lets the characters carry on without the player. agent is a GroupDirector. See Group Chat for a walkthrough.

neocortex-quiz

<neocortex-quiz endpoint="/api/neocortex" character-id="host-id" question-set-id="set-id" voice></neocortex-quiz>
AttributeDescription
character-idThe quiz host. Required.
question-set-idThe question set to play. Required.
endpointYour proxy route.
voiceSpeak the host's lines aloud.
headingHeader title. Defaults to Quiz.
question-countHow many questions to ask.
themelight or dark.

The player answers by clicking an option, typing or speaking, and can ask for a hint or pass. The element reflects its phase in a state attribute: idle, playing or finished. agent is a QuizAgent.

See Quiz for a walkthrough.

neocortex-chat-panel

A scrolling message list with a thinking indicator at the end. Use it to build your own chat layout.

AttributeDescription
empty-textText shown while there are no messages.
MemberDescription
addMessage(message, { prepend })Adds a bubble and returns it. message is { sender, text, name?, emotion?, hue? }, where sender is player, character or notice, and hue is a colour angle for a speaker in a group.
thinkingSet to true or false to show or hide the thinking dots.
clear()Removes every message.
scrollToEnd()Scrolls to the newest message.

neocortex-message

One bubble. Text is always set as text, never parsed as HTML.

AttributeDescription
senderplayer, character or notice.
textThe message. Falls back to the element's text content.
nameSpeaker name, shown above character bubbles.
emotionShown as the bubble's tooltip when it is not neutral.
hueA colour angle (0 to 360) for the name and an accent line.

neocortex-text-input

AttributeDescription
placeholderDefaults to Say something….
maxlengthDefaults to 500, the proxy's default message limit.
disabledDisables the box and the send button.

Fires nx-send with { text } when the player submits. Call preventDefault() on it to keep the text in the box. It also has a value property and a focus() method.

neocortex-mic-button

Click to listen, and recording stops after a pause. With push-to-talk, hold it with the pointer, Space or Enter.

AttributeDescription
push-to-talkRecord only while held.
disabledDisables the button.
thresholdLoudness from 0 to 1 that counts as speech. Defaults to 0.1.
max-waitSeconds of silence that end a recording. Defaults to 1.2.

The element reflects its state in a state attribute: idle, listening or recording. It has a receiver property (the AudioReceiver behind it) and toggle() and stop() methods. The button is disabled in browsers that cannot record audio.

neocortex-mic-select

Lists microphones and switches the one a mic button records from. Device names appear once microphone permission has been granted, and the choice is remembered for the next visit.

<neocortex-mic-button id="mic"></neocortex-mic-button>
<neocortex-mic-select for="mic"></neocortex-mic-select>

Point it at a mic button with for="<id>", or set its receiver property to an AudioReceiver. refresh() re-reads the device list.

neocortex-thinking

Three bouncing dots, hidden unless active is set.

AttributeDescription
activeShow the dots. Also available as the active property.
labelScreen reader text. Defaults to Thinking….

neocortex-usage-badge

A status pill that shows whether characters are available, running low, or resting because credits or a per-player or per-character cap ran out.

AttributeDescription
endpointYour proxy route.
character-idInclude this character's cap in the check.
refreshSeconds between checks. Defaults to 60.

The element reflects the result in a status attribute: ok, low, empty, or unknown when the check failed. See UsageGate to gate your own UI.

Events

Events are CustomEvents that bubble out of shadow DOM, so you can listen on the element or any ancestor. The payload is in event.detail.

EventFired bydetail
nx-linechat, group chat{ characterId, name, line, index }, when a line of a reply appears. line is { text, emotion, spokenText?, speechToken? }.
nx-linequiz{ line }, when a host line appears. Quiz lines also carry a role.
nx-actionchat, group chat{ characterId, action, message }, where action is { name, targetId }.
nx-emotionchat, group chat{ characterId, emotion }, when a character's emotion changes.
nx-responsechat, group chatThe whole reply { sessionId, messages }, as soon as it arrives.
nx-reply-finishedchat, group chatThe same reply, once every line has been shown or spoken.
nx-turnquizEvery quiz turn as the server sent it.
nx-finishedquizThe closing turn.
nx-errorchat, group chat, quiz{ error }, a NeocortexError.
nx-errormic button{ error, reason? }. reason is "permission" when microphone access was blocked.
nx-sendtext input{ text }
nx-recordedmic button{ audio }, a 16 kHz mono WAV Blob.
nx-usageusage badgeThe usage response { status, creditsRemaining, player?, character? }.

Emotions are camelCase, for example happy or curious. By default nx-action fires as soon as the reply arrives, before it is played. Change that with the agent's actionTrigger.

The chat and quiz elements show a short notice in the conversation when something goes wrong. Call preventDefault() on nx-error to show your own instead. This covers microphone errors too.

chat.addEventListener("nx-error", (event) => {
  event.preventDefault();
  toast(event.detail.error.message);
});

The built-in text input and mic button sit inside the chat and quiz elements, so their nx-send, nx-recorded and nx-error events bubble out of those elements too.

Theming

Every colour is a CSS custom property. Set them on the element or any ancestor:

neocortex-chat {
  --nx-accent: #7c3aed;
  --nx-character-bubble: #1e1b4b;
  --nx-radius: 20px;
  --nx-font: "Inter", sans-serif;
  height: 600px;
}
TokenLight defaultDark default
--nx-accent#f59e0b#f59e0b
--nx-accent-text#1f1f1f#1f1f1f
--nx-bg#ffffff#121212
--nx-surface#f5f5f4#1c1c1c
--nx-text#1c1917#f5f5f4
--nx-muted#78716c#a8a29e
--nx-border#e7e5e4#2e2e2e
--nx-player-bubble--nx-accent--nx-accent
--nx-player-text--nx-accent-text--nx-accent-text
--nx-character-bubble#1f1f1f#2a2a2a
--nx-character-text#fafaf9#fafaf9
--nx-danger#dc2626#dc2626
--nx-radius14px14px
--nx-fontsystem-ui, -apple-system, "Segoe UI", Roboto, sans-serifsame

<neocortex-chat> is 32rem tall and <neocortex-quiz> is 36rem by default. Set height on the element to change it.

Light, dark and right-to-left

Elements follow the visitor's prefers-color-scheme. Set theme="light" or theme="dark" on an element to choose one; the chat, group chat, quiz and panel elements pass it on to everything inside them. To set exact colours, use the --nx-* tokens, which also reach every nested element.

The layout uses logical CSS properties, so the elements respect dir="rtl" on the element or the page. They also honour prefers-reduced-motion.

Parts

Internals are exposed with ::part():

neocortex-chat::part(header) { display: none; }
neocortex-chat::part(footer) { padding: 1rem; }
neocortex-chat::part(bubble) { font-size: 1.05rem; }
neocortex-chat::part(player) { --nx-player-bubble: #2563eb; }
ElementParts
<neocortex-chat>header, heading, voice-toggle, reset, panel, footer, input, mic
<neocortex-group-chat>The chat's parts, plus continue
<neocortex-quiz>header, heading, count, progress, panel, leaderboard, question, options, confirm, start, footer, input, mic, hint, pass
<neocortex-chat-panel>list, empty, thinking, and each message as message plus its sender (player, character or notice)
<neocortex-message>name, bubble
<neocortex-text-input>form, input, send
<neocortex-mic-button>button
<neocortex-mic-select>select
<neocortex-thinking>dots
<neocortex-usage-badge>pill, dot, label

The chat, group chat and quiz elements forward the parts of the elements inside them, so you can style those from the page too:

Forwarded partComes from
list, empty, thinking, dotsThe message panel
message, player, character, noticeEach message, by sender
name, bubbleInside each message
form, send, and input-field (the text box itself)The text input
mic-buttonThe microphone button

Auth headers

Requests carry your site's cookies by default (same origin only). If your proxy expects a token instead, give every element one client that sends it. It applies to every element without an endpoint attribute, including ones already on the page:

import { NeocortexClient, setDefaultClient } from "@neocortex-link/web/ui";

setDefaultClient(
  new NeocortexClient({
    baseUrl: "/api/neocortex",
    headers: () => ({ authorization: `Bearer ${getToken()}` }),
  }),
);

headers can be an object or a function that is called for every request. To give a single element its own client, set its client property instead.

Actions

document.querySelector("neocortex-chat").agent.registerAction("OPEN_DOOR", (action) => doors.get(action.targetId)?.open());

See Interactables.

On this page