rojects/workspace/history/2026/07-July/09-Thursday/index.html

204 lines
9.7 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Session Summary — 9 July 2026</title>
<link rel="stylesheet" href="../../../../_assets_/styles.css">
<link rel="stylesheet" href="../../../../_assets_/nav.css">
</head>
<body>
<div class="page">
<header>
<p class="date">Thursday, 9 July 2026</p>
<h1>Roject — Session Summary</h1>
<p class="subtitle">
RojoChatPanel — a streaming AI chat panel for the editor, with LangChain backend,
markdown rendering, and tab container integration.
</p>
</header>
<section>
<h2>What we built</h2>
<div class="card">
<h3>RojoChatPanel — Backend</h3>
<p>
A new <code>/api/rojos/chat</code> route streams AI responses from an
OpenAI-compatible model (e.g. a local LLAMA). The backend is split into
three files under <code>server/rojos/</code>:
</p>
<ul style="margin-top:0.75rem">
<li><strong>RojosConfig.ts</strong> — hardcoded config wrapper (<code>baseURL</code>,
<code>model</code>, <code>apiKey</code>). Designed to grow as more Rojo settings
are added.</li>
<li><strong>RojosAgent.ts</strong> — in-memory session store (<code>Map</code> of
conversation ID → message history). Uses <code>ChatOpenAI.stream()</code> from
<code>@langchain/openai</code> with messages converted to
<code>HumanMessage</code> / <code>AIMessage</code> via <code>@langchain/core</code>.</li>
<li><strong>server/routes/rojos.ts</strong> — Express router, <code>POST /chat</code>,
requires auth. Iterates the agent stream with <code>for await</code>,
writes newline-delimited JSON chunks (<code>{ type:"CHAT", text }</code>,
<code>{ type:"THINKING" }</code>, <code>{ type:"DONE" }</code>) directly
via <code>res.write()</code>, then calls <code>res.end()</code>.
After streaming, calls <code>updateAgentConversation</code> so the assistant
reply is appended to the session history.</li>
</ul>
<p style="margin-top:0.75rem">
The route is mounted at <code>/api/rojos</code> in <code>server/index.ts</code>,
keeping the namespace open for future Rojo settings endpoints.
</p>
<div class="tags">
<span class="tag">server/rojos/RojosConfig.ts</span>
<span class="tag">server/rojos/RojosAgent.ts</span>
<span class="tag">server/routes/rojos.ts</span>
<span class="tag">@langchain/openai</span>
<span class="tag">@langchain/core</span>
<span class="tag">SSE / res.write</span>
</div>
</div>
<div class="card">
<h3>RojoChatPanel — Frontend Component</h3>
<p>
A new <code>rojo-chat-panel</code> custom element following the same pattern
as <code>html-editor-panel</code>. The element has three zones:
</p>
<ul style="margin-top:0.75rem">
<li><strong>Toolbar</strong> — robot emoji icon, conversation name label, two
placeholder buttons (actions ★, settings ☰).</li>
<li><strong>Chat history</strong> — scrolling <code>div</code>; user messages
are right-aligned bubble, assistant messages are left-aligned and rendered
with <code>markdown-it</code> (live as chunks arrive).</li>
<li><strong>Input area</strong><code>contenteditable</code> div (3-line height),
a placeholder <em>+</em> button, and a <em></em> send button.
Enter sends; Shift+Enter inserts a newline.</li>
</ul>
<p style="margin-top:0.75rem">
Each panel instance generates a fresh <code>crypto.randomUUID()</code>
conversation ID on connect. Raw text is extracted from the
<code>contenteditable</code> with an inlined recursive walker (same logic
as <code>DOMEditor.extractRawText</code>).
<code>markdown-it</code> is loaded as a UMD global from
<code>/vendor/markdown-it.min.js</code> (built file copied from
<code>node_modules</code>, no bundler required).
</p>
<div class="tags">
<span class="tag">src/components/rojo-chat-panel/rojo-chat-panel.ts</span>
<span class="tag">public/components/rojo-chat-panel/rojo-chat-panel.css</span>
<span class="tag">public/vendor/markdown-it.min.js</span>
<span class="tag">contenteditable</span>
<span class="tag">ReadableStream</span>
</div>
</div>
<div class="card">
<h3>Tab Container Integration</h3>
<p>
<em>Rojo Chat</em> was added to the <code>panelTypes</code> array in
<code>TabContainer.openMenu()</code>, so it appears under
<strong>Add → Rojo Chat</strong> in every tab container's <code></code> menu.
<strong>Duplicate</strong> already worked for free — it calls the stored
factory, creating a second panel with its own UUID and empty history.
</p>
<p style="margin-top:0.75rem">
<code>RojoChatPanel</code> also implements <code>addContextMenuEntries</code>
(the <code>EditorPanel</code> interface), displaying a read-only label with
the first 8 characters of the conversation ID.
</p>
<div class="tags">
<span class="tag">src/components/tab-container/tab-container.ts</span>
<span class="tag">addContextMenuEntries</span>
<span class="tag">panelTypes</span>
</div>
</div>
</section>
<section>
<h2>Key Decisions</h2>
<div class="decision">
<strong>Split LangChain packages, not the monolithic <code>langchain</code></strong>
<p>
The example code used the monolithic <code>langchain</code> package with
<code>createAgent</code>. We switched to <code>@langchain/core</code> +
<code>@langchain/openai</code> and call <code>model.stream()</code> directly.
With no tools in scope yet, this is equivalent and avoids a large dependency.
Migrating to <code>createReactAgent</code> from <code>@langchain/langgraph</code>
when tools are added will be a small, localised change in <code>RojosAgent.ts</code>.
</p>
</div>
<div class="decision">
<strong><code>RojosConfig.ts</code> as a hardcoded TypeScript wrapper</strong>
<p>
A JSON config file in <code>data/</code> 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.
</p>
</div>
<div class="decision">
<strong><code>markdown-it</code> UMD copied to <code>public/vendor/</code></strong>
<p>
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
<code>public/vendor/</code> keeps it self-hosted, available offline, and
version-locked with the rest of <code>node_modules</code>.
</p>
</div>
<div class="decision">
<strong>In-memory conversation sessions, UUID per panel instance</strong>
<p>
Persisting conversations to <code>data/</code> 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
<code>connectedCallback</code>; named/saved conversations are deferred.
</p>
</div>
<div class="decision">
<strong><code>for await</code> + <code>res.write()</code> instead of <code>Readable.pipe()</code></strong>
<p>
The initial implementation used <code>Readable.from(async function*(){})</code>
piped to the response. This was simplified to a direct <code>for await</code>
loop calling <code>res.write()</code> then <code>res.end()</code>, which is
easier to read and avoids the intermediate <code>Readable</code> wrapper.
</p>
</div>
</section>
<section>
<h2>Structural Changes</h2>
<div class="card">
<p>
<code>package.json</code> — added <code>@langchain/core</code>, <code>@langchain/openai</code>, <code>markdown-it</code><br>
<code>server/rojos/RojosConfig.ts</code> — new: model/provider config wrapper<br>
<code>server/rojos/RojosAgent.ts</code> — new: in-memory session store, LangChain stream<br>
<code>server/routes/rojos.ts</code> — new: POST /chat streaming endpoint<br>
<code>server/index.ts</code> — added <code>import rojosRouter</code>, mounted at <code>/api/rojos</code><br>
<code>src/components/rojo-chat-panel/rojo-chat-panel.ts</code> — new: custom element<br>
<code>public/components/rojo-chat-panel/rojo-chat-panel.css</code> — new: panel styles<br>
<code>public/vendor/markdown-it.min.js</code> — new: UMD build (copied from node_modules)<br>
<code>public/editor.html</code> — added rojo-chat-panel CSS, vendor script, module script<br>
<code>src/components/tab-container/tab-container.ts</code> — added Rojo Chat to panelTypes
</p>
</div>
</section>
<footer>
Roject &mdash; session log &mdash; 9 July 2026
</footer>
</div>
<script>var NAV_ROOT = '../../../../';</script>
<script src="../../../../_assets_/nav-data.js"></script>
<script src="../../../../_assets_/nav.js"></script>
</body>
</html>