diff --git a/ui/client-app/src/App.js b/ui/client-app/src/App.js index 8e60cd7..9a76f5b 100644 --- a/ui/client-app/src/App.js +++ b/ui/client-app/src/App.js @@ -156,7 +156,7 @@ Icon={Icon} /> )} -
+
{renderPage()}
diff --git a/ui/client-app/src/components/ChatArea.css b/ui/client-app/src/components/ChatArea.css index 7481fed..0d86e34 100644 --- a/ui/client-app/src/components/ChatArea.css +++ b/ui/client-app/src/components/ChatArea.css @@ -1,5 +1,4 @@ -.chat-area-fixed-height { - height: calc(100vh - 72px); /* Subtract input + padding */ - } - - \ No newline at end of file +/* ChatArea styles moved to inline Tailwind where possible */ +.custom-scrollbar::-webkit-scrollbar { + width: 6px; +} \ No newline at end of file diff --git a/ui/client-app/src/components/ChatArea.js b/ui/client-app/src/components/ChatArea.js index e5172cc..3080fea 100644 --- a/ui/client-app/src/components/ChatArea.js +++ b/ui/client-app/src/components/ChatArea.js @@ -30,7 +30,7 @@ }, [chatHistory]); return ( -
+
{/* Scrollable ChatWindow */}
@@ -52,8 +52,8 @@ type="submit" disabled={isProcessing} className={`p-3 rounded-lg text-white font-bold transition-colors flex-shrink-0 ${isProcessing - ? "bg-gray-400 dark:bg-gray-600 cursor-not-allowed" - : "bg-indigo-600 hover:bg-indigo-700" + ? "bg-gray-400 dark:bg-gray-600 cursor-not-allowed" + : "bg-indigo-600 hover:bg-indigo-700" }`} > Send diff --git a/ui/client-app/src/components/SessionSidebar.js b/ui/client-app/src/components/SessionSidebar.js index 964b02d..028a60d 100644 --- a/ui/client-app/src/components/SessionSidebar.js +++ b/ui/client-app/src/components/SessionSidebar.js @@ -12,6 +12,7 @@ const [sessions, setSessions] = useState([]); const [tokenHoverData, setTokenHoverData] = useState({}); const [isLoading, setIsLoading] = useState(false); + const [confirmModal, setConfirmModal] = useState({ isOpen: false, title: '', message: '', onConfirm: null }); useEffect(() => { if (isOpen) fetchSessions(); @@ -37,26 +38,39 @@ } catch (err) { /* silent */ } }; - const handleDelete = async (e, sessionId) => { + const handleDelete = (e, sessionId) => { e.stopPropagation(); - if (!window.confirm('Delete this session?')) return; - try { - await deleteSession(sessionId); - fetchSessions(); - if (Number(currentSessionId) === sessionId) { - localStorage.removeItem(`sessionId_${featureName}`); - if (onNewSession) onNewSession(); + setConfirmModal({ + isOpen: true, + title: 'Delete Session', + message: 'Are you sure you want to delete this session? This action cannot be undone.', + onConfirm: async () => { + try { + await deleteSession(sessionId); + fetchSessions(); + if (Number(currentSessionId) === sessionId) { + localStorage.removeItem(`sessionId_${featureName}`); + if (onNewSession) onNewSession(); + } + } catch { alert('Failed to delete session.'); } } - } catch { alert('Failed to delete session.'); } + }); }; - const handleDeleteAll = async () => { - if (!window.confirm('Delete ALL history for this feature?')) return; - try { - await deleteAllSessions(featureName); - fetchSessions(); - if (onNewSession) onNewSession(); - } catch { alert('Failed to delete all sessions.'); } + const handleDeleteAll = (e) => { + if (e) e.stopPropagation(); + setConfirmModal({ + isOpen: true, + title: 'Clear All History', + message: 'Are you sure you want to delete ALL history for this feature? This action is permanent.', + onConfirm: async () => { + try { + await deleteAllSessions(featureName); + fetchSessions(); + if (onNewSession) onNewSession(); + } catch { alert('Failed to delete all sessions.'); } + } + }); }; const formatDate = (iso) => { @@ -86,7 +100,7 @@

{prettyFeatureName} History

-
@@ -134,6 +148,7 @@
)} + {/* Custom Confirmation Modal */} + {confirmModal.isOpen && ( +
+
+
+ +
+

{confirmModal.title}

+

{confirmModal.message}

+
+ + +
+
+
+ )} ); }; diff --git a/ui/client-app/src/hooks/useCodeAssistant.js b/ui/client-app/src/hooks/useCodeAssistant.js index ec8bcde..6353335 100644 --- a/ui/client-app/src/hooks/useCodeAssistant.js +++ b/ui/client-app/src/hooks/useCodeAssistant.js @@ -110,7 +110,9 @@ if (text.trim().toLowerCase() === "/new") { setChatHistory([]); localStorage.removeItem("sessionId_coding_assistant"); - const newSid = await getSessionId("coding_assistant", localActiveLLM || "gemini"); + const prefProvider = userConfigData?.effective?.llm?.active_provider || "gemini"; + const newSid = await getSessionId("coding_assistant", prefProvider); + setLocalActiveLLM(prefProvider); setSessionId(newSid); sessionIdRef.current = newSid; fetchTokenUsage(); diff --git a/ui/client-app/src/hooks/useVoiceChat.js b/ui/client-app/src/hooks/useVoiceChat.js index b003ac6..3564e91 100644 --- a/ui/client-app/src/hooks/useVoiceChat.js +++ b/ui/client-app/src/hooks/useVoiceChat.js @@ -455,14 +455,24 @@ try { const audioDuration = audioBlob.size / (48000 * 2 * 1) * 1000; if (audioDuration < MINIMUM_AUDIO_DURATION_MS) { + const msg = "Your recording was too short. Please speak for at least half a second."; console.log(`Audio too short (${audioDuration.toFixed(2)}ms), skipping.`); - setStatus("Audio was too short. Please speak a little longer."); + setStatus(msg); + if (!isAutoMode) { + setErrorMessage(msg); + setShowErrorModal(true); + } lastRequestTimeRef.current = Date.now(); return; } if (audioBlob.size === 0) { + const msg = "Recording stopped, but no audio was captured. Please try again."; console.warn("Audio blob is empty, skipping STT API call."); - setStatus("Recording stopped, but no audio was captured. Please try again."); + setStatus(msg); + if (!isAutoMode) { + setErrorMessage(msg); + setShowErrorModal(true); + } lastRequestTimeRef.current = Date.now(); return; } @@ -670,11 +680,13 @@ setIsBusy(true); setStatus("Starting new session..."); + const prefProvider = userConfigData?.effective?.llm?.active_provider || "gemini"; try { - const newSessionId = await getSessionId("voice_chat", localActivePrefs.llm || "gemini", { - stt_provider_name: localActivePrefs.stt, - tts_provider_name: localActivePrefs.tts + const newSessionId = await getSessionId("voice_chat", prefProvider, { + stt_provider_name: localActivePrefs.stt || userConfigData?.effective?.stt?.active_provider, + tts_provider_name: localActivePrefs.tts || userConfigData?.effective?.tts?.active_provider }); + setLocalActivePrefs(prev => ({ ...prev, llm: prefProvider })); setSessionId(newSessionId); sessionIdRef.current = newSessionId; fetchTokenUsage(); diff --git a/ui/client-app/src/pages/CodingAssistantPage.js b/ui/client-app/src/pages/CodingAssistantPage.js index eed9113..56659bc 100644 --- a/ui/client-app/src/pages/CodingAssistantPage.js +++ b/ui/client-app/src/pages/CodingAssistantPage.js @@ -46,7 +46,7 @@ }, [chatHistory]); return ( -
+
{/* Main content area */} -
-
+
+
{/* Chat Area */} -
-
+
+

@@ -113,12 +113,12 @@ onClick={() => handleSendChat("/new")} className="text-xs font-bold px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg transition-all shadow-md hover:shadow-indigo-500/20 active:scale-95" > - + NEW CHAT + + NEW SESSION

-
+
+

Welcome to Cortex AI! 🧠 diff --git a/ui/client-app/src/pages/SettingsPage.js b/ui/client-app/src/pages/SettingsPage.js index 08034db..0833dfe 100644 --- a/ui/client-app/src/pages/SettingsPage.js +++ b/ui/client-app/src/pages/SettingsPage.js @@ -870,7 +870,7 @@ }); return ( -
+

Configuration

diff --git a/ui/client-app/src/pages/VoiceChatPage.js b/ui/client-app/src/pages/VoiceChatPage.js index ada91a9..7f0562f 100644 --- a/ui/client-app/src/pages/VoiceChatPage.js +++ b/ui/client-app/src/pages/VoiceChatPage.js @@ -66,7 +66,7 @@ }; return ( -
+
{/* Main content area */} -
-
+
+
{/* Chat Area Box */} -
-
+
+
{/* Box Header */}

diff --git a/ui/client-app/src/services/apiService.js b/ui/client-app/src/services/apiService.js index 98ea236..5bce459 100644 --- a/ui/client-app/src/services/apiService.js +++ b/ui/client-app/src/services/apiService.js @@ -118,7 +118,7 @@ */ export const getUserSessions = async (featureName = "default") => { const userId = getUserId(); - const params = new URLSearchParams({ user_id: userId, feature_name: featureName }); + const params = new URLSearchParams({ user_id: userId, feature_name: featureName, _t: Date.now() }); const response = await fetch(`${SESSIONS_GET_ALL_ENDPOINT}?${params.toString()}`, { method: "GET", headers: { "X-User-ID": userId }, @@ -223,7 +223,7 @@ */ export const deleteAllSessions = async (featureName = "default") => { const userId = getUserId(); - const params = new URLSearchParams({ user_id: userId, feature_name: featureName }); + const params = new URLSearchParams({ user_id: userId, feature_name: featureName, _t: Date.now() }); const response = await fetch(`${SESSIONS_GET_ALL_ENDPOINT}?${params.toString()}`, { method: "DELETE", headers: { "X-User-ID": userId },