feat(webui): upgrade settings and sidebar controls (#3906)

* feat(settings): expand settings api payload

* feat(webui): build app-style settings center

* feat(webui): add centered chat search dialog

* fix(webui): shorten chat search label

* fix(webui): center dialog entrance animation

* fix(webui): simplify chat search results

* fix(webui): tighten mobile settings navigation

* feat(webui): persist sidebar state

* feat(webui): add sidebar organization controls

* refactor(webui): organize backend helpers

* refactor(webui): remove utils compatibility shims

* refactor(session): move shared webui helpers out of webui package

* feat(webui): add image generation settings

* style(webui): refine settings overview layout

* fix(webui): localize settings zh-CN copy

* style(webui): add settings status indicators

* feat(webui): show sidebar run indicators

* fix(webui): persist sidebar run indicators

* fix(webui): highlight settings pending status

* fix(webui): align settings test with provider update

* fix(utils): preserve legacy webui helper imports
This commit is contained in:
Xubin Ren
2026-05-19 22:42:38 +08:00
committed by GitHub
parent 30fc05c746
commit 57d5276da1
45 changed files with 6306 additions and 1012 deletions
+22 -1
View File
@@ -56,6 +56,7 @@ type StatusHandler = (status: ConnectionStatus) => void;
type RuntimeModelHandler = (modelName: string | null, modelPreset?: string | null) => void;
type SessionUpdateScope = "metadata" | "thread" | string;
type SessionUpdateHandler = (chatId: string, scope?: SessionUpdateScope) => void;
type RunStatusHandler = (chatId: string, startedAt: number | null) => void;
/** Structured connection-level errors surfaced to the UI.
*
@@ -102,6 +103,7 @@ export class NanobotClient {
private statusHandlers = new Set<StatusHandler>();
private runtimeModelHandlers = new Set<RuntimeModelHandler>();
private sessionUpdateHandlers = new Set<SessionUpdateHandler>();
private runStatusHandlers = new Set<RunStatusHandler>();
private errorHandlers = new Set<ErrorHandler>();
// chat_id -> handlers listening on it
private chatHandlers = new Map<string, Set<EventHandler>>();
@@ -172,6 +174,16 @@ export class NanobotClient {
};
}
onRunStatus(handler: RunStatusHandler): Unsubscribe {
this.runStatusHandlers.add(handler);
for (const [chatId, startedAt] of this.runStartedAtByChatId) {
handler(chatId, startedAt);
}
return () => {
this.runStatusHandlers.delete(handler);
};
}
/** Subscribe to transport-level faults (see :type:`StreamError`). */
onError(handler: ErrorHandler): Unsubscribe {
this.errorHandlers.add(handler);
@@ -194,9 +206,12 @@ export class NanobotClient {
private recordGoalStatusForRunStrip(chatId: string, ev: InboundEvent): void {
if (ev.event !== "goal_status") return;
if (ev.status === "running" && typeof ev.started_at === "number") {
const previous = this.runStartedAtByChatId.get(chatId);
this.runStartedAtByChatId.set(chatId, ev.started_at);
} else {
if (previous !== ev.started_at) this.emitRunStatus(chatId, ev.started_at);
} else if (this.runStartedAtByChatId.has(chatId)) {
this.runStartedAtByChatId.delete(chatId);
this.emitRunStatus(chatId, null);
}
}
@@ -389,6 +404,12 @@ export class NanobotClient {
}
}
private emitRunStatus(chatId: string, startedAt: number | null): void {
for (const handler of this.runStatusHandlers) {
handler(chatId, startedAt);
}
}
private dispatch(chatId: string, ev: InboundEvent): void {
const handlers = this.chatHandlers.get(chatId);
if (handlers !== undefined && handlers.size > 0) {