diff --git a/frontend/chat-ui/src/App.jsx b/frontend/chat-ui/src/App.jsx
index 61fb96b..c36eadf 100644
--- a/frontend/chat-ui/src/App.jsx
+++ b/frontend/chat-ui/src/App.jsx
@@ -5,7 +5,6 @@ import InputBox from './components/InputBox.jsx';
import Sidebar from './components/Sidebar.jsx';
import TopBar from './components/TopBar.jsx';
import TestApi from './components/TestApi.jsx';
-import ProfileMenu from './components/ProfileMenu.jsx';
function AppInner() {
const { state } = useStore();
@@ -25,7 +24,6 @@ function AppInner() {
-
);
}
diff --git a/frontend/chat-ui/src/components/ModeSelectorDemo.jsx b/frontend/chat-ui/src/components/ModeSelectorDemo.jsx
index 3be190b..decd3bf 100644
--- a/frontend/chat-ui/src/components/ModeSelectorDemo.jsx
+++ b/frontend/chat-ui/src/components/ModeSelectorDemo.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useId, useMemo, useReducer, useRef, useState } from "react";
+import React, { useEffect, useId, useReducer, useRef, useState } from "react";
import api, { API_BASE_URL } from "../api";
/**
@@ -585,18 +585,25 @@ function ChatComposer() {
dispatch({ type: "SET_RESULTS", results: { text: `You said: ${q}\n\n(Connect your backend to replace this mock.)` } });
} else if (state.mode === "deep") {
dispatch({ type: "DEEP_START" });
- try {
+ try {
const res = await deepResearch(q, (stage, log) => {
dispatch({ type: "DEEP_SET_STAGE", stage, log });
});
dispatch({ type: "SET_RESULTS", results: { text: res.text, citations: res.citations } });
- } catch (err) {
+ } catch (error) {
+ console.error("Deep research request failed", error);
+ const description =
+ error instanceof Error && error.message
+ ? error.message
+ : typeof error === "string" && error.length
+ ? error
+ : "Try again in a moment.";
dispatch({
type: "ADD_TOAST",
toast: {
id: uid("toast"),
- title: "Research failed",
- desc: err.message ?? String(err),
+ title: "Deep research failed",
+ desc: description,
kind: "error",
},
});
diff --git a/frontend/chat-ui/src/components/ProfileMenu.jsx b/frontend/chat-ui/src/components/ProfileMenu.jsx
index e5f3654..41ab45f 100644
--- a/frontend/chat-ui/src/components/ProfileMenu.jsx
+++ b/frontend/chat-ui/src/components/ProfileMenu.jsx
@@ -1,39 +1,91 @@
-import React, { useState } from 'react';
+import React, { useEffect, useId, useRef, useState } from 'react';
import { useStore } from '../store';
-export default function ProfileMenu() {
+/**
+ * Compact profile/settings menu that can be embedded in any toolbar.
+ * Positioned relative to its container, so callers can control placement
+ * with layout utilities (e.g. flex gap, margin).
+ */
+export default function ProfileMenu({ className = '' }) {
const { state, dispatch } = useStore();
const [open, setOpen] = useState(false);
+ const containerRef = useRef(null);
+ const menuId = useId();
- const toggleTheme = () => dispatch({ type: 'SET_THEME', theme: state.theme === 'light' ? 'dark' : 'light' });
+ useEffect(() => {
+ if (!open) return undefined;
+
+ const handleClick = (event) => {
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
+ setOpen(false);
+ }
+ };
+
+ const handleKey = (event) => {
+ if (event.key === 'Escape') setOpen(false);
+ };
+
+ document.addEventListener('mousedown', handleClick);
+ document.addEventListener('keydown', handleKey);
+ return () => {
+ document.removeEventListener('mousedown', handleClick);
+ document.removeEventListener('keydown', handleKey);
+ };
+ }, [open]);
+
+ const toggleTheme = () =>
+ dispatch({ type: 'SET_THEME', theme: state.theme === 'light' ? 'dark' : 'light' });
const toggleMemory = () => dispatch({ type: 'TOGGLE_MEMORY' });
return (
-
-