ui: add /public/chat route (public webchat shell)
This commit is contained in:
parent
699784dbee
commit
7ad2ee3af5
@ -607,3 +607,16 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Public chat mode: minimal chrome for shareable/mobile use. */
|
||||
.public-chat {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.public-chat .chat {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@ -86,6 +86,80 @@ import { loadLogs } from "./controllers/logs";
|
||||
const AVATAR_DATA_RE = /^data:/i;
|
||||
const AVATAR_HTTP_RE = /^https?:\/\//i;
|
||||
|
||||
function renderPublicChat(state: AppViewState) {
|
||||
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
|
||||
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||
const assistantAvatarUrl = resolveAssistantAvatarUrl(state);
|
||||
const chatAvatarUrl = state.chatAvatarUrl ?? assistantAvatarUrl ?? null;
|
||||
|
||||
return html`
|
||||
<div class="public-chat">
|
||||
${renderChat({
|
||||
sessionKey: state.sessionKey,
|
||||
onSessionKeyChange: (next) => {
|
||||
state.sessionKey = next;
|
||||
state.chatMessage = "";
|
||||
state.chatAttachments = [];
|
||||
state.chatStream = null;
|
||||
state.chatStreamStartedAt = null;
|
||||
state.chatRunId = null;
|
||||
state.chatQueue = [];
|
||||
state.resetToolStream();
|
||||
state.resetChatScroll();
|
||||
state.applySettings({
|
||||
...state.settings,
|
||||
sessionKey: next,
|
||||
lastActiveSessionKey: next,
|
||||
});
|
||||
void state.loadAssistantIdentity();
|
||||
void loadChatHistory(state);
|
||||
void refreshChatAvatar(state);
|
||||
},
|
||||
thinkingLevel: state.chatThinkingLevel,
|
||||
showThinking,
|
||||
loading: state.chatLoading,
|
||||
sending: state.chatSending,
|
||||
compactionStatus: state.compactionStatus,
|
||||
assistantAvatarUrl: chatAvatarUrl,
|
||||
messages: state.chatMessages,
|
||||
toolMessages: state.chatToolMessages,
|
||||
stream: state.chatStream,
|
||||
streamStartedAt: state.chatStreamStartedAt,
|
||||
draft: state.chatMessage,
|
||||
queue: state.chatQueue,
|
||||
connected: state.connected,
|
||||
canSend: state.connected,
|
||||
disabledReason: chatDisabledReason,
|
||||
error: state.lastError,
|
||||
// Public chat should not expose session lists.
|
||||
sessions: null,
|
||||
// Always "focus" (no sidebar/nav).
|
||||
focusMode: true,
|
||||
onRefresh: () => {
|
||||
state.resetToolStream();
|
||||
return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]);
|
||||
},
|
||||
onToggleFocusMode: () => {
|
||||
// no-op
|
||||
},
|
||||
onChatScroll: (event) => state.handleChatScroll(event),
|
||||
onDraftChange: (next) => (state.chatMessage = next),
|
||||
attachments: state.chatAttachments,
|
||||
onAttachmentsChange: (next) => (state.chatAttachments = next),
|
||||
onSend: () => state.handleSendChat(),
|
||||
canAbort: Boolean(state.chatRunId),
|
||||
onAbort: () => void state.handleAbortChat(),
|
||||
onQueueRemove: (id) => state.removeQueuedMessage(id),
|
||||
onNewSession: () => state.handleSendChat("/new", { restoreDraft: true }),
|
||||
// Hide tool sidebar in public chat.
|
||||
sidebarOpen: false,
|
||||
assistantName: state.assistantName,
|
||||
assistantAvatar: state.assistantAvatar,
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function resolveAssistantAvatarUrl(state: AppViewState): string | undefined {
|
||||
const list = state.agentsList?.agents ?? [];
|
||||
const parsed = parseAgentSessionKey(state.sessionKey);
|
||||
@ -102,6 +176,10 @@ function resolveAssistantAvatarUrl(state: AppViewState): string | undefined {
|
||||
}
|
||||
|
||||
export function renderApp(state: AppViewState) {
|
||||
if (state.tab === "public-chat") {
|
||||
return renderPublicChat(state);
|
||||
}
|
||||
|
||||
const presenceCount = state.presenceEntries.length;
|
||||
const sessionsCount = state.sessionsResult?.count ?? null;
|
||||
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
|
||||
|
||||
@ -129,6 +129,7 @@ describe("pathForTab", () => {
|
||||
describe("tabFromPath", () => {
|
||||
it("returns tab for valid path", () => {
|
||||
expect(tabFromPath("/chat")).toBe("chat");
|
||||
expect(tabFromPath("/public/chat")).toBe("public-chat");
|
||||
expect(tabFromPath("/overview")).toBe("overview");
|
||||
expect(tabFromPath("/sessions")).toBe("sessions");
|
||||
});
|
||||
@ -148,6 +149,7 @@ describe("tabFromPath", () => {
|
||||
|
||||
it("is case-insensitive", () => {
|
||||
expect(tabFromPath("/CHAT")).toBe("chat");
|
||||
expect(tabFromPath("/Public/Chat")).toBe("public-chat");
|
||||
expect(tabFromPath("/Overview")).toBe("overview");
|
||||
});
|
||||
});
|
||||
|
||||
@ -19,6 +19,7 @@ export type Tab =
|
||||
| "skills"
|
||||
| "nodes"
|
||||
| "chat"
|
||||
| "public-chat"
|
||||
| "config"
|
||||
| "debug"
|
||||
| "logs";
|
||||
@ -32,6 +33,7 @@ const TAB_PATHS: Record<Tab, string> = {
|
||||
skills: "/skills",
|
||||
nodes: "/nodes",
|
||||
chat: "/chat",
|
||||
"public-chat": "/public/chat",
|
||||
config: "/config",
|
||||
debug: "/debug",
|
||||
logs: "/logs",
|
||||
@ -103,6 +105,7 @@ export function inferBasePathFromPathname(pathname: string): string {
|
||||
export function iconForTab(tab: Tab): IconName {
|
||||
switch (tab) {
|
||||
case "chat":
|
||||
case "public-chat":
|
||||
return "messageSquare";
|
||||
case "overview":
|
||||
return "barChart";
|
||||
@ -133,6 +136,8 @@ export function titleForTab(tab: Tab) {
|
||||
switch (tab) {
|
||||
case "overview":
|
||||
return "Overview";
|
||||
case "public-chat":
|
||||
return "Chat";
|
||||
case "channels":
|
||||
return "Channels";
|
||||
case "instances":
|
||||
@ -146,6 +151,7 @@ export function titleForTab(tab: Tab) {
|
||||
case "nodes":
|
||||
return "Nodes";
|
||||
case "chat":
|
||||
case "public-chat":
|
||||
return "Chat";
|
||||
case "config":
|
||||
return "Config";
|
||||
@ -162,6 +168,8 @@ export function subtitleForTab(tab: Tab) {
|
||||
switch (tab) {
|
||||
case "overview":
|
||||
return "Gateway status, entry points, and a fast health read.";
|
||||
case "public-chat":
|
||||
return "Public web chat (mobile-friendly).";
|
||||
case "channels":
|
||||
return "Manage channels and settings.";
|
||||
case "instances":
|
||||
@ -176,6 +184,8 @@ export function subtitleForTab(tab: Tab) {
|
||||
return "Paired devices, capabilities, and command exposure.";
|
||||
case "chat":
|
||||
return "Direct gateway chat session for quick interventions.";
|
||||
case "public-chat":
|
||||
return "A minimal chat UI intended for sharing.";
|
||||
case "config":
|
||||
return "Edit ~/.clawdbot/moltbot.json safely.";
|
||||
case "debug":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user