ui(chat): persist session in URL and stabilize picker
- Keep the selected chat session in ?session=... for deep links and reloads.\n- Only apply the query param on the Chat tab (avoid leaking it across navigation).\n- Render session <option> entries with stable keys to prevent label glitches.
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
import { repeat } from "lit/directives/repeat.js";
|
||||||
|
|
||||||
import type { AppViewState } from "./app-view-state";
|
import type { AppViewState } from "./app-view-state";
|
||||||
import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation";
|
import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation";
|
||||||
import { loadChatHistory } from "./controllers/chat";
|
import { loadChatHistory } from "./controllers/chat";
|
||||||
|
import { syncUrlWithSessionKey } from "./app-settings";
|
||||||
import type { SessionsListResult } from "./types";
|
import type { SessionsListResult } from "./types";
|
||||||
import type { ThemeMode } from "./theme";
|
import type { ThemeMode } from "./theme";
|
||||||
import type { ThemeTransitionContext } from "./theme-transition";
|
import type { ThemeTransitionContext } from "./theme-transition";
|
||||||
@@ -64,10 +66,13 @@ export function renderChatControls(state: AppViewState) {
|
|||||||
sessionKey: next,
|
sessionKey: next,
|
||||||
lastActiveSessionKey: next,
|
lastActiveSessionKey: next,
|
||||||
});
|
});
|
||||||
|
syncUrlWithSessionKey(state, next, true);
|
||||||
void loadChatHistory(state);
|
void loadChatHistory(state);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
${sessionOptions.map(
|
${repeat(
|
||||||
|
sessionOptions,
|
||||||
|
(entry) => entry.key,
|
||||||
(entry) =>
|
(entry) =>
|
||||||
html`<option value=${entry.key}>
|
html`<option value=${entry.key}>
|
||||||
${entry.displayName ?? entry.key}
|
${entry.displayName ?? entry.key}
|
||||||
@@ -119,9 +124,11 @@ function resolveSessionOptions(sessionKey: string, sessions: SessionsListResult
|
|||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
const options: Array<{ key: string; displayName?: string }> = [];
|
const options: Array<{ key: string; displayName?: string }> = [];
|
||||||
|
|
||||||
|
const resolvedCurrent = sessions?.sessions?.find((s) => s.key === sessionKey);
|
||||||
|
|
||||||
// Add current session key first
|
// Add current session key first
|
||||||
seen.add(sessionKey);
|
seen.add(sessionKey);
|
||||||
options.push({ key: sessionKey });
|
options.push({ key: sessionKey, displayName: resolvedCurrent?.displayName });
|
||||||
|
|
||||||
// Add sessions from the result
|
// Add sessions from the result
|
||||||
if (sessions?.sessions) {
|
if (sessions?.sessions) {
|
||||||
|
|||||||
@@ -85,9 +85,12 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
|||||||
const session = sessionRaw.trim();
|
const session = sessionRaw.trim();
|
||||||
if (session) {
|
if (session) {
|
||||||
host.sessionKey = session;
|
host.sessionKey = session;
|
||||||
|
applySettings(host, {
|
||||||
|
...host.settings,
|
||||||
|
sessionKey: session,
|
||||||
|
lastActiveSessionKey: session,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
params.delete("session");
|
|
||||||
shouldCleanUrl = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldCleanUrl) return;
|
if (!shouldCleanUrl) return;
|
||||||
@@ -225,6 +228,18 @@ export function onPopState(host: SettingsHost) {
|
|||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const resolved = tabFromPath(window.location.pathname, host.basePath);
|
const resolved = tabFromPath(window.location.pathname, host.basePath);
|
||||||
if (!resolved) return;
|
if (!resolved) return;
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const session = url.searchParams.get("session")?.trim();
|
||||||
|
if (session) {
|
||||||
|
host.sessionKey = session;
|
||||||
|
applySettings(host, {
|
||||||
|
...host.settings,
|
||||||
|
sessionKey: session,
|
||||||
|
lastActiveSessionKey: session,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setTabFromRoute(host, resolved);
|
setTabFromRoute(host, resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,9 +256,18 @@ export function syncUrlWithTab(host: SettingsHost, tab: Tab, replace: boolean) {
|
|||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
const targetPath = normalizePath(pathForTab(tab, host.basePath));
|
const targetPath = normalizePath(pathForTab(tab, host.basePath));
|
||||||
const currentPath = normalizePath(window.location.pathname);
|
const currentPath = normalizePath(window.location.pathname);
|
||||||
if (currentPath === targetPath) return;
|
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.pathname = targetPath;
|
|
||||||
|
if (tab === "chat" && host.sessionKey) {
|
||||||
|
url.searchParams.set("session", host.sessionKey);
|
||||||
|
} else {
|
||||||
|
url.searchParams.delete("session");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPath !== targetPath) {
|
||||||
|
url.pathname = targetPath;
|
||||||
|
}
|
||||||
|
|
||||||
if (replace) {
|
if (replace) {
|
||||||
window.history.replaceState({}, "", url.toString());
|
window.history.replaceState({}, "", url.toString());
|
||||||
} else {
|
} else {
|
||||||
@@ -251,6 +275,18 @@ export function syncUrlWithTab(host: SettingsHost, tab: Tab, replace: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function syncUrlWithSessionKey(
|
||||||
|
host: SettingsHost,
|
||||||
|
sessionKey: string,
|
||||||
|
replace: boolean,
|
||||||
|
) {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set("session", sessionKey);
|
||||||
|
if (replace) window.history.replaceState({}, "", url.toString());
|
||||||
|
else window.history.pushState({}, "", url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadOverview(host: SettingsHost) {
|
export async function loadOverview(host: SettingsHost) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadChannels(host as unknown as ClawdbotApp, false),
|
loadChannels(host as unknown as ClawdbotApp, false),
|
||||||
|
|||||||
Reference in New Issue
Block a user