Thursday, 9 July 2026
RojoChatPanel — a streaming AI chat panel for the editor, with LangChain backend, markdown rendering, and tab container integration.
A new /api/rojos/chat route streams AI responses from an
OpenAI-compatible model (e.g. a local LLAMA). The backend is split into
three files under server/rojos/:
baseURL,
model, apiKey). Designed to grow as more Rojo settings
are added.Map of
conversation ID → message history). Uses ChatOpenAI.stream() from
@langchain/openai with messages converted to
HumanMessage / AIMessage via @langchain/core.POST /chat,
requires auth. Iterates the agent stream with for await,
writes newline-delimited JSON chunks ({ type:"CHAT", text },
{ type:"THINKING" }, { type:"DONE" }) directly
via res.write(), then calls res.end().
After streaming, calls updateAgentConversation so the assistant
reply is appended to the session history.
The route is mounted at /api/rojos in server/index.ts,
keeping the namespace open for future Rojo settings endpoints.
A new rojo-chat-panel custom element following the same pattern
as html-editor-panel. The element has three zones:
div; user messages
are right-aligned bubble, assistant messages are left-aligned and rendered
with markdown-it (live as chunks arrive).contenteditable div (3-line height),
a placeholder + button, and a ▲ send button.
Enter sends; Shift+Enter inserts a newline.
Each panel instance generates a fresh crypto.randomUUID()
conversation ID on connect. Raw text is extracted from the
contenteditable with an inlined recursive walker (same logic
as DOMEditor.extractRawText).
markdown-it is loaded as a UMD global from
/vendor/markdown-it.min.js (built file copied from
node_modules, no bundler required).
Rojo Chat was added to the panelTypes array in
TabContainer.openMenu(), so it appears under
Add → Rojo Chat in every tab container's ⋮ menu.
Duplicate already worked for free — it calls the stored
factory, creating a second panel with its own UUID and empty history.
RojoChatPanel also implements addContextMenuEntries
(the EditorPanel interface), displaying a read-only label with
the first 8 characters of the conversation ID.
langchain
The example code used the monolithic langchain package with
createAgent. We switched to @langchain/core +
@langchain/openai and call model.stream() directly.
With no tools in scope yet, this is equivalent and avoids a large dependency.
Migrating to createReactAgent from @langchain/langgraph
when tools are added will be a small, localised change in RojosAgent.ts.
RojosConfig.ts as a hardcoded TypeScript wrapper
A JSON config file in data/ would allow runtime editing but
adds complexity (read-on-start, hot-reload). Since the model endpoint is not
expected to change frequently, a TS constant is simpler and caught by the
type checker. The file is the single place to change when adding providers.
markdown-it UMD copied to public/vendor/
The project has no bundler and all frontend modules are bare ES modules.
A CDN script would require a network call. Copying the UMD build once into
public/vendor/ keeps it self-hosted, available offline, and
version-locked with the rest of node_modules.
Persisting conversations to data/ adds file I/O and a
naming/indexing scheme. For a first iteration, in-memory is sufficient and
keeps the surface area small. Each panel gets its own UUID on
connectedCallback; named/saved conversations are deferred.
for await + res.write() instead of Readable.pipe()
The initial implementation used Readable.from(async function*(){})
piped to the response. This was simplified to a direct for await
loop calling res.write() then res.end(), which is
easier to read and avoids the intermediate Readable wrapper.
package.json — added @langchain/core, @langchain/openai, markdown-it
server/rojos/RojosConfig.ts — new: model/provider config wrapper
server/rojos/RojosAgent.ts — new: in-memory session store, LangChain stream
server/routes/rojos.ts — new: POST /chat streaming endpoint
server/index.ts — added import rojosRouter, mounted at /api/rojos
src/components/rojo-chat-panel/rojo-chat-panel.ts — new: custom element
public/components/rojo-chat-panel/rojo-chat-panel.css — new: panel styles
public/vendor/markdown-it.min.js — new: UMD build (copied from node_modules)
public/editor.html — added rojo-chat-panel CSS, vendor script, module script
src/components/tab-container/tab-container.ts — added Rojo Chat to panelTypes