feat: route incoming messages through interaction kernel
This commit is contained in:
+30
-3
@@ -2,12 +2,13 @@ import DOMPurify from "dompurify";
|
||||
import { marked } from "marked";
|
||||
import "./style.css";
|
||||
import {
|
||||
decodeConversationItem,
|
||||
type Actor,
|
||||
type ConversationItem,
|
||||
type Envelope,
|
||||
type JsonObject,
|
||||
} from "./protocol";
|
||||
import { InteractionKernel } from "./runtime/interaction-kernel";
|
||||
import { RendererRegistry } from "./runtime/renderer-registry";
|
||||
|
||||
type Session = {
|
||||
api: string;
|
||||
@@ -32,6 +33,7 @@ const DEFAULT_API = "http://127.0.0.1:8090";
|
||||
let session: Session = { api: localStorage.getItem("lineup.api") || DEFAULT_API, uid: "", agentUID: "agent_hermes_main", channelID: "agent_default_channel", channelType: 2, lastSeq: 0, active: false };
|
||||
let polling = false;
|
||||
let thinking: HTMLElement | undefined;
|
||||
const interactionKernel = new InteractionKernel();
|
||||
|
||||
const app = document.querySelector<HTMLDivElement>("#app");
|
||||
if (!app) throw new Error("LineUp host root is missing");
|
||||
@@ -132,6 +134,7 @@ function hideThinking(): void { thinking?.remove(); thinking = undefined; }
|
||||
|
||||
function render(item: ConversationItem): void {
|
||||
switch (item.kind) {
|
||||
case "user-message": appendBubble("human", item.text); break;
|
||||
case "markdown": hideThinking(); appendBubble("agent", item.markdown, true); break;
|
||||
case "agent-status": item.status === "thinking" ? showThinking(item.detail || "正在思考…") : (hideThinking(), setPresence(item.detail || item.status || "在线")); break;
|
||||
case "progress": { const percent = Math.max(0, Math.min(100, item.percent)); addSystem(`${item.title || "Agent 任务"}:${percent}%${item.status ? ` · ${item.status}` : ""}`); break; }
|
||||
@@ -142,6 +145,22 @@ function render(item: ConversationItem): void {
|
||||
}
|
||||
}
|
||||
|
||||
// M0-03 establishes the dispatch boundary. M0-06 will move these individual
|
||||
// DOM renderers out of App Shell; until then the registry keeps the same UI
|
||||
// behavior while ensuring all incoming content traverses the Kernel first.
|
||||
const rendererRegistry = new RendererRegistry<void>();
|
||||
rendererRegistry.register("markdown", render);
|
||||
rendererRegistry.register("agent-status", render);
|
||||
rendererRegistry.register("progress", render);
|
||||
rendererRegistry.register("error", render);
|
||||
rendererRegistry.register("surface", render);
|
||||
rendererRegistry.register("app-call", render);
|
||||
rendererRegistry.register("fallback", render);
|
||||
|
||||
function renderIncoming(item: ConversationItem): void {
|
||||
if (!rendererRegistry.render(item, undefined)) addSystem("收到当前客户端没有可信 Renderer 的内容。");
|
||||
}
|
||||
|
||||
function decodeBase64(value: string): string {
|
||||
try { return decodeURIComponent([...atob(value)].map(c => `%${c.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")); } catch { return value; }
|
||||
}
|
||||
@@ -179,7 +198,14 @@ async function syncLoop(): Promise<void> {
|
||||
const previousSeq = session.lastSeq;
|
||||
session.lastSeq = Math.max(session.lastSeq, message.message_seq);
|
||||
advanced ||= session.lastSeq > previousSeq;
|
||||
if (message.from_uid !== session.uid) render(decodeConversationItem(decodeBase64(message.payload)));
|
||||
if (message.from_uid !== session.uid) {
|
||||
const result = interactionKernel.ingest({
|
||||
raw: decodeBase64(message.payload),
|
||||
source: "sync",
|
||||
transport_id: String(message.message_seq),
|
||||
});
|
||||
for (const item of result.items) renderIncoming(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid a hot loop if an upstream response repeats a sequence that
|
||||
@@ -219,6 +245,7 @@ $<HTMLFormElement>("#login-form").addEventListener("submit", async event => {
|
||||
const response = await fetchLogin(session.api, phone, code);
|
||||
const body = await response.json().catch(() => ({})) as LoginResponse & { error?: string };
|
||||
if (!response.ok) throw new Error(body.error || "登录失败");
|
||||
interactionKernel.reset();
|
||||
session = { ...session, uid: body.uid, agentUID: body.agent_uid || session.agentUID, channelID: body.channel_id || session.channelID, channelType: body.channel_type || session.channelType, lastSeq: 0, active: true };
|
||||
loginView.hidden = true; chatView.hidden = false; addSystem("已进入与 AI Agent 的对话"); setPresence("正在同步消息…"); void syncLoop();
|
||||
} catch (reason) { error.textContent = loginFailureMessage(reason); }
|
||||
@@ -236,7 +263,7 @@ $<HTMLFormElement>("#message-form").addEventListener("submit", async event => {
|
||||
finally { send.disabled = false; input.focus(); }
|
||||
});
|
||||
|
||||
$<HTMLButtonElement>("#logout").addEventListener("click", () => { session.active = false; messages.replaceChildren(); chatView.hidden = true; loginView.hidden = false; });
|
||||
$<HTMLButtonElement>("#logout").addEventListener("click", () => { session.active = false; interactionKernel.reset(); messages.replaceChildren(); chatView.hidden = true; loginView.hidden = false; });
|
||||
|
||||
export function makeProtocolEvent(
|
||||
type: string,
|
||||
|
||||
Reference in New Issue
Block a user