mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-30 16:01:49 +08:00
chore(dashboard): address copilot review nits on #16861
- App.tsx doc comment: replace stale ChatPageHost reference with 'persistent chat host block rendered inline near the bottom of this file' so readers can find the actual code. - App.tsx persistent host: show a small spinner on /chat while plugin manifests are loading instead of a blank content area. Direct /chat deep-links used to paint empty for up to ~2s in the worst case (plugin-registration safety timeout) because both the route sink (null) and the persistent host (!pluginsLoading gate) render nothing during that window. Non-chat routes stay empty as before. - ChatPage.tsx: rename setter to match the 'raw' state — useState now destructures as [mobilePanelOpenRaw, setMobilePanelOpenRaw], and all four call sites (closeMobilePanel, matchMedia listener, open-button onClick, plus destructure) updated accordingly. No behavior change; matches the 'raw vs derived' convention the original comment set up.
This commit is contained in:
@@ -80,11 +80,12 @@ const CHAT_NAV_ITEM: NavItem = {
|
||||
|
||||
/**
|
||||
* Built-in routes except /chat. Chat is rendered persistently (outside
|
||||
* <Routes>) when embedded — see ChatPageHost below — so the PTY child,
|
||||
* WebSocket, and xterm instance survive when the user visits another tab
|
||||
* and comes back. A `display:none` toggle hides the terminal without
|
||||
* unmounting. Routing still owns the URL so /chat deep-links, browser
|
||||
* back/forward, and nav highlight keep working.
|
||||
* <Routes>) when embedded — see the persistent chat host block rendered
|
||||
* inline near the bottom of this file — so the PTY child, WebSocket,
|
||||
* and xterm instance survive when the user visits another tab and comes
|
||||
* back. A `display:none` toggle hides the terminal without unmounting.
|
||||
* Routing still owns the URL so /chat deep-links, browser back/forward,
|
||||
* and nav highlight keep working.
|
||||
*/
|
||||
const BUILTIN_ROUTES_CORE: Record<string, ComponentType> = {
|
||||
"/": RootRedirect,
|
||||
@@ -578,17 +579,37 @@ export default function App() {
|
||||
or idle-disconnect after N minutes hidden; neither is
|
||||
shipped today.
|
||||
*/}
|
||||
{embeddedChat && !pluginsLoading && !chatOverriddenByPlugin && (
|
||||
<div
|
||||
data-chat-active={isChatRoute ? "true" : "false"}
|
||||
className={cn(
|
||||
"min-h-0 min-w-0",
|
||||
isChatRoute ? "flex flex-1 flex-col" : "hidden",
|
||||
)}
|
||||
aria-hidden={!isChatRoute}
|
||||
>
|
||||
<ChatPage isActive={isChatRoute} />
|
||||
</div>
|
||||
{embeddedChat && !chatOverriddenByPlugin && (
|
||||
pluginsLoading ? (
|
||||
// Direct /chat deep-link: plugin manifests haven't resolved
|
||||
// yet, so we can't tell if a plugin is going to claim this
|
||||
// route. Show a lightweight placeholder instead of a
|
||||
// blank page. Typical wait is <50ms; worst case is the
|
||||
// 2s plugin-registration safety timeout.
|
||||
isChatRoute ? (
|
||||
<div
|
||||
className="flex min-h-0 min-w-0 flex-1 items-center justify-center"
|
||||
aria-busy="true"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden />
|
||||
<span>Loading chat…</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
) : (
|
||||
<div
|
||||
data-chat-active={isChatRoute ? "true" : "false"}
|
||||
className={cn(
|
||||
"min-h-0 min-w-0",
|
||||
isChatRoute ? "flex flex-1 flex-col" : "hidden",
|
||||
)}
|
||||
aria-hidden={!isChatRoute}
|
||||
>
|
||||
<ChatPage isActive={isChatRoute} />
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<PluginSlot name="post-main" />
|
||||
|
||||
@@ -128,11 +128,11 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
// /chat re-runs the effect (derived flips back to true) and re-locks.
|
||||
// Keying on the raw state would leak the body.overflow="hidden" across
|
||||
// tabs because the dep wouldn't change on tab switch.
|
||||
const [mobilePanelOpenRaw, setMobilePanelOpen] = useState(false);
|
||||
const [mobilePanelOpenRaw, setMobilePanelOpenRaw] = useState(false);
|
||||
const mobilePanelOpen = isActive && mobilePanelOpenRaw;
|
||||
const { setEnd } = usePageHeader();
|
||||
const { t } = useI18n();
|
||||
const closeMobilePanel = useCallback(() => setMobilePanelOpen(false), []);
|
||||
const closeMobilePanel = useCallback(() => setMobilePanelOpenRaw(false), []);
|
||||
const modelToolsLabel = useMemo(
|
||||
() => `${t.app.modelToolsSheetTitle} ${t.app.modelToolsSheetSubtitle}`,
|
||||
[t.app.modelToolsSheetSubtitle, t.app.modelToolsSheetTitle],
|
||||
@@ -174,7 +174,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia("(min-width: 1024px)");
|
||||
const onChange = (e: MediaQueryListEvent) => {
|
||||
if (e.matches) setMobilePanelOpen(false);
|
||||
if (e.matches) setMobilePanelOpenRaw(false);
|
||||
};
|
||||
mql.addEventListener("change", onChange);
|
||||
return () => mql.removeEventListener("change", onChange);
|
||||
@@ -194,7 +194,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
||||
setEnd(
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobilePanelOpen(true)}
|
||||
onClick={() => setMobilePanelOpenRaw(true)}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded border border-current/20",
|
||||
"px-2 py-1 text-[0.65rem] font-medium tracking-wide normal-case",
|
||||
|
||||
Reference in New Issue
Block a user