Conversation
… chat-centric layout, render the `end` event as the main AI response
Theme System: - Add full Light/Dark/System theme switching with CSS variables - Implement semantic color system with theme-aware utility classes - Persist theme preference to localStorage with system preference detection - Update all core components (Dashboard, MessageBubble, TaskInputPanel, etc.) Agent Activity Panel: - Add AgentActivityPanel component showing detailed agent progress - Display current task, tool usage, output, and activity log per agent - Add MonitoringPanel with tabs to switch between Task Map and Agents - Extend agentStatusStore with lastInput/lastOutput fields Bug Fixes: - Fix stale closure in useSSEHandler causing agent states not updating on task end
There was a problem hiding this comment.
Pull request overview
This PR focuses on UI/UX enhancements across the dashboard and conversation experience, while also adding a backend-driven fallback for model/API-key configuration.
Changes:
- Added a theme system with persistence and “light/dark/system” support, plus new theme toggles in UI.
- Restructured the Dashboard layout with a new Conversation panel and a right-side Monitoring panel (Task Map + Agent Activity).
- Added backend config detection and env-defaulting for model/API key, plus a backend endpoint to report config status.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/stores/uiStore.ts | Adds persisted theme state + DOM theme application and system-theme listening. |
| frontend/src/stores/apiConfigStore.ts | Adds backend config status check and uses it as fallback when no local key exists. |
| frontend/src/stores/agentStatusStore.ts | Stores last agent input/output for “thinking” and expanded activity displays. |
| frontend/src/pages/DashboardPage.tsx | Reworks layout into Conversation center + animated Monitoring sidebar + theme toggle. |
| frontend/src/index.css | Introduces CSS-variable-based theme palette and additional UI utilities/animations. |
| frontend/src/hooks/useSnapshots.ts | Adjusts snapshot fetching behavior to capture final screenshot on completion. |
| frontend/src/hooks/useSSEHandler.ts | Fixes agent-store reads by pulling fresh state via getState(). |
| frontend/src/components/task-panel/TaskDecompositionPanel.tsx | Updates Task Map visuals and adds a terminal-style footer. |
| frontend/src/components/task-panel/MonitoringPanel.tsx | New tabbed wrapper for Task Map and Agent Activity. |
| frontend/src/components/task-panel/AgentActivityPanel.tsx | New agent activity UI with expandable cards and parsed I/O display. |
| frontend/src/components/resources/BrowserSnapshots.tsx | Restyles snapshots panel and adds empty/loading state when agent absent. |
| frontend/src/components/layout/Header.tsx | Replaces simple toggle with a theme dropdown (light/dark/system). |
| frontend/src/components/layout/ErrorBanner.tsx | Restyles banner to match the new theme tokens. |
| frontend/src/components/conversation/ThinkingIndicator.tsx | New animated “thinking/planning/working” indicator based on stores. |
| frontend/src/components/conversation/MessageBubble.tsx | New chat bubble UI for user/assistant/system messages. |
| frontend/src/components/conversation/ConversationPanel.tsx | New conversation view with empty state and scroll management. |
| frontend/src/components/chat/TaskInputPanel.tsx | Updates input styling; allows backend API key fallback by sending empty api_key. |
| frontend/src/components/api-key/ApiKeyModal.tsx | Restyles API key modal to match new theme tokens. |
| backend/requirements.txt | Adds a pip requirements file with pinned/minimum versions. |
| backend/app/model/chat.py | Allows model config fields to default from environment variables. |
| backend/app/controller/health_controller.py | Adds /config/status endpoint reporting backend config presence. |
| backend/app/controller/chat_controller.py | Avoids overwriting OPENAI_API_KEY when api_key is empty. |
| backend/app/init.py | Loads backend .env at startup. |
| backend/.env_example | Adds example env file for backend model configuration. |
Comments suppressed due to low confidence (1)
frontend/src/stores/apiConfigStore.ts:1
- If there’s no valid localStorage key and the backend also has no API key, the modal may never open because isModalOpen defaults to false and checkBackendConfig() doesn’t set it to true on "no key". Fix by explicitly opening the modal when backend reports has_api_key=false (or after checkBackendConfig resolves), e.g., in checkBackendConfig set state.isModalOpen = !has_api_key when no frontend key is present.
import { create } from 'zustand';
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| initializeTheme: () => { | ||
| const { theme } = get() | ||
| const resolved = resolveTheme(theme) | ||
| set((state) => { | ||
| state.resolvedTheme = resolved | ||
| }) | ||
| applyTheme(resolved) | ||
|
|
||
| // Listen for system theme changes | ||
| if (typeof window !== 'undefined') { | ||
| const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') | ||
| const handler = () => { | ||
| const currentTheme = get().theme | ||
| if (currentTheme === 'system') { | ||
| const newResolved = getSystemTheme() | ||
| set((state) => { | ||
| state.resolvedTheme = newResolved | ||
| }) | ||
| applyTheme(newResolved) | ||
| } | ||
| } | ||
| mediaQuery.addEventListener('change', handler) | ||
| } | ||
| }, |
There was a problem hiding this comment.
initializeTheme() registers a matchMedia "change" listener but never removes it, and module-level initialization will re-register on HMR / repeated module evaluation. This can leak listeners and duplicate updates. Consider registering the listener exactly once (track a module-level flag and store the handler), and provide cleanup via removeEventListener (e.g., in a dedicated setup function called from the app root).
| // Initialize theme listener when module loads | ||
| if (typeof window !== 'undefined') { | ||
| // Delay to ensure store is ready | ||
| setTimeout(() => { | ||
| useUIStore.getState().initializeTheme() | ||
| }, 0) | ||
| } No newline at end of file |
There was a problem hiding this comment.
initializeTheme() registers a matchMedia "change" listener but never removes it, and module-level initialization will re-register on HMR / repeated module evaluation. This can leak listeners and duplicate updates. Consider registering the listener exactly once (track a module-level flag and store the handler), and provide cleanup via removeEventListener (e.g., in a dedicated setup function called from the app root).
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| const applyTheme = (theme: 'light' | 'dark') => { | ||
| const root = document.documentElement | ||
| root.classList.remove('light', 'dark') | ||
| root.classList.add(theme) | ||
| } |
There was a problem hiding this comment.
applyTheme() accesses document unconditionally; setTheme() can be invoked in non-DOM contexts (tests, SSR, pre-render), which will throw. Align with your other guards by adding a typeof window/document check inside applyTheme() (or before calling it).
| setTheme: (theme) => | ||
| set((state) => { | ||
| state.theme = theme | ||
| const resolved = resolveTheme(theme) | ||
| state.resolvedTheme = resolved | ||
| applyTheme(resolved) | ||
| }), |
There was a problem hiding this comment.
applyTheme() accesses document unconditionally; setTheme() can be invoked in non-DOM contexts (tests, SSR, pre-render), which will throw. Align with your other guards by adding a typeof window/document check inside applyTheme() (or before calling it).
| startPolling(taskId); | ||
| } else { | ||
| stopPolling(); | ||
| // Do a final fetch when the agent finishes to capture the last screenshot | ||
| fetchSnapshots(); | ||
| } | ||
|
|
||
| // Initial fetch | ||
| fetchSnapshots(); | ||
| }, [taskId, agentState, startPolling, stopPolling, fetchSnapshots]); |
There was a problem hiding this comment.
This removes the unconditional initial fetch. If startPolling() doesn’t immediately fetch, the UI may show no snapshots until the first polling interval elapses. Consider performing fetchSnapshots() when polling starts as well (e.g., call fetchSnapshots() before/after startPolling(taskId)) while keeping the “final fetch” on stop.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| <button | ||
| key={suggestion.text} | ||
| className={cn( | ||
| "flex items-center gap-2 px-4 py-2.5 rounded-xl", | ||
| "bg-card border border-card-border", | ||
| "text-sm text-foreground-secondary", | ||
| "hover:border-accent hover:text-accent hover:bg-accent-light/50", | ||
| "transition-all duration-200 cursor-default" | ||
| )} | ||
| > | ||
| <suggestion.icon className="w-4 h-4" /> | ||
| {suggestion.text} | ||
| </button> |
There was a problem hiding this comment.
These are rendered as elements but have no onClick behavior and are styled with cursor-default. For accessibility/semantics, either add an action (e.g., populate the input with the suggestion) or change them to non-interactive elements (e.g.,
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| <button className="flex items-center gap-1 hover:text-accent transition-colors"> | ||
| <ExternalLink className="w-3 h-3" /> | ||
| <span>Sources</span> | ||
| </button> |
There was a problem hiding this comment.
This renders an interactive button with no click handler (and no disabled state), which is confusing for keyboard/screen-reader users. Either implement the “Sources” action (open a sources panel/modal) or render it as non-interactive text until the feature exists.
| <button className="flex items-center gap-1 hover:text-accent transition-colors"> | |
| <ExternalLink className="w-3 h-3" /> | |
| <span>Sources</span> | |
| </button> | |
| <span className="flex items-center gap-1 hover:text-accent transition-colors"> | |
| <ExternalLink className="w-3 h-3" /> | |
| <span>Sources</span> | |
| </span> |
…er-configured model (from .env) via ModelFactory.create() in multi_modal.py. Fix user message disappearing after send: startChat() was clearing all messages including the optimistically-added user message. Now reads fresh state via getState() to preserve it across the clear. Fix UnicodeDecodeError on non-English Windows: override blocking shell_exec path with errors="replace" in subprocess.Popen so non-UTF-8 system codepage output doesn't crash the readerthread. Fix ModernChatWindow sync dropping images and ignoring empty state.
…COMPLEX)
- SIMPLE questions get direct answers without agent orchestration
- Dynamic agent creation: only instantiate required agents based on triage
- Save uploaded base64 images to project folder, pass file paths to agents
(fixes abnormal token counts caused by passing raw base64 to ImageAnalysisToolkit)
| image_analysis_toolkit = ImageAnalysisToolkit(options.project_id) | ||
| toolkit_model = ModelFactory.create( | ||
| model_platform=options.model_platform.lower(), | ||
| model_type=options.model_type, | ||
| api_key=options.api_key, | ||
| url=options.api_url, | ||
| ) | ||
| image_analysis_toolkit = ImageAnalysisToolkit( | ||
| options.project_id, model=toolkit_model | ||
| ) |
|
|
||
| # Medical Assistant Coordinator prompt for triage and direct answering | ||
| MEDICAL_COORDINATOR_PROMPT = """You are MedGemma, a knowledgeable and helpful medical assistant. | ||
|
|
||
| <your_role> | ||
| You serve as both a medical information assistant AND a coordinator for complex tasks. | ||
| For simple medical questions, you provide direct, accurate answers. | ||
| For complex tasks requiring specialized tools (image analysis, web search, document creation), | ||
| you coordinate with specialized agents. | ||
| </your_role> | ||
|
|
| # Normalize format extension | ||
| ext_map = {"jpeg": "jpg", "png": "png", "gif": "gif", "webp": "webp"} | ||
| ext = ext_map.get(image_format, image_format) |
backend/requirements.txt
Outdated
There was a problem hiding this comment.
We don't need requirements.txt. Better to use "uv sync" instead.
Let me remove it
UI/UX Enhancements