Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 32 additions & 31 deletions frontend/src/App.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are conflicts in this App.js file. Could you please help resolve these conflicts? Then, I can approve and merge this PR.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function App() {
const [activeTab, setActiveTab] = useState('chat');
const [settings, setSettings] = useState({
model: 'gpt-4o-mini',
memoryModel: 'gpt-4o-mini',
persona: 'helpful_assistant',
timezone: 'America/New_York',
serverUrl: 'http://localhost:47283'
Expand Down Expand Up @@ -57,7 +58,7 @@ function App() {
if (response.ok) {
const data = await response.json();
console.log('API key status:', data);

if (forceOpen || (data.requires_api_key && data.missing_keys.length > 0)) {
if (forceOpen) {
console.log('Manual API key update requested');
Expand Down Expand Up @@ -88,10 +89,10 @@ function App() {
// Refresh backend-dependent data after successful connection
const refreshBackendData = useCallback(async () => {
console.log('πŸ”„ Refreshing backend-dependent data...');

// Check API keys after successful backend connection
await checkApiKeys();

// Trigger refresh of other backend-dependent components
// This will cause components like SettingsPanel to re-fetch their data
setSettings(prev => ({ ...prev, lastBackendRefresh: Date.now() }));
Expand All @@ -101,7 +102,7 @@ function App() {
const checkBackendHealth = useCallback(async () => {
let shouldProceed = true;
let currentVisibility = false;

// Check if health check is already in progress and capture current visibility
setBackendLoading(prev => {
if (prev.isChecking) {
Expand Down Expand Up @@ -133,7 +134,7 @@ function App() {

if (response.ok) {
console.log('βœ… Backend is healthy - hiding loading modal');

setBackendLoading(prev => ({
...prev,
isVisible: false,
Expand All @@ -148,7 +149,7 @@ function App() {
console.log('πŸ”„ Backend reconnected - refreshing data...');
await refreshBackendData();
}

return true;
} else {
throw new Error(`Health check failed with status: ${response.status}`);
Expand Down Expand Up @@ -187,15 +188,15 @@ function App() {
useEffect(() => {
const performInitialHealthCheck = async () => {
// Show loading modal immediately for initial startup
setBackendLoading(prev => ({
...prev,
isVisible: true,
setBackendLoading(prev => ({
...prev,
isVisible: true,
isReconnection: false // This is initial startup, not reconnection
}));

// Wait a moment for the UI to update
await new Promise(resolve => setTimeout(resolve, 500));

// Check backend health (will automatically refresh data if modal was visible)
await checkBackendHealth();
};
Expand All @@ -208,17 +209,17 @@ function App() {
const interval = setInterval(() => {
setBackendLoading(prev => {
const timeSinceLastCheck = Date.now() - (prev.lastCheckTime || 0);

// Check more frequently when modal is visible, less frequently when not
const shouldCheck = prev.isVisible
const shouldCheck = prev.isVisible
? !prev.isChecking // Every 5 seconds when modal is visible
: timeSinceLastCheck > 30000 && !prev.isChecking; // Every 30 seconds when modal is hidden

if (shouldCheck) {
console.log('πŸ”„ Periodic health check triggered. Modal visible:', prev.isVisible);
checkBackendHealth();
}

return prev; // Don't actually update state, just check conditions
});
}, 5000); // Check every 5 seconds
Expand All @@ -230,7 +231,7 @@ function App() {
useEffect(() => {
const handleWindowFocus = async () => {
console.log('πŸ” Window focused - checking backend health silently...');

// Check backend health silently - only show modal if it actually fails
const healthCheckResult = await checkBackendHealth();
// Loading modal will be shown automatically by checkBackendHealth if it fails
Expand All @@ -239,7 +240,7 @@ function App() {
const handleVisibilityChange = async () => {
if (!document.hidden) {
console.log('πŸ” Document became visible - checking backend health silently...');

// Check backend health silently - only show modal if it actually fails
const healthCheckResult = await checkBackendHealth();
// Loading modal will be shown automatically by checkBackendHealth if it fails
Expand All @@ -264,7 +265,7 @@ function App() {
const handleApiKeySubmit = async () => {
// Refresh API key status after submission
await checkApiKeys();

// If there's a pending model change, retry it now
if (pendingModelChange.retryFunction) {
console.log(`Retrying ${pendingModelChange.type} model change to '${pendingModelChange.model}' after API key update`);
Expand All @@ -285,7 +286,7 @@ function App() {
useEffect(() => {
// Listen for menu events from Electron
const cleanupFunctions = [];

if (window.electronAPI) {
const cleanupNewChat = window.electronAPI.onMenuNewChat(() => {
setActiveTab('chat');
Expand All @@ -309,7 +310,7 @@ function App() {
// Handle Electron window events - check backend health silently
const cleanupWindowShow = window.electronAPI.onWindowShow(async () => {
console.log('πŸ” Electron window shown - checking backend health silently...');

// Check backend health silently - only show modal if it actually fails
const healthCheckResult = await checkBackendHealth();
// Loading modal will be shown automatically by checkBackendHealth if it fails
Expand All @@ -318,7 +319,7 @@ function App() {

const cleanupAppActivate = window.electronAPI.onAppActivate(async () => {
console.log('πŸ” Electron app activated - checking backend health silently...');

// Check backend health silently - only show modal if it actually fails
const healthCheckResult = await checkBackendHealth();
// Loading modal will be shown automatically by checkBackendHealth if it fails
Expand All @@ -344,32 +345,32 @@ function App() {
<div className="App">
<div className="app-header">
<div className="app-title">
<Logo
size="small"
showText={false}
<Logo
size="small"
showText={false}
/>
<span className="version">v0.1.5</span>
</div>
<div className="tabs">
<button
<button
className={`tab ${activeTab === 'chat' ? 'active' : ''}`}
onClick={() => setActiveTab('chat')}
>
{t('tabs.chat')}
</button>
<button
<button
className={`tab ${activeTab === 'screenshots' ? 'active' : ''}`}
onClick={() => setActiveTab('screenshots')}
>
{t('tabs.screenshots')}
</button>
<button
<button
className={`tab ${activeTab === 'memory' ? 'active' : ''}`}
onClick={() => setActiveTab('memory')}
>
{t('tabs.memory')}
</button>
<button
<button
className={`tab ${activeTab === 'settings' ? 'active' : ''}`}
onClick={() => setActiveTab('settings')}
>
Expand All @@ -380,7 +381,7 @@ function App() {

<div className="app-content">
{/* Keep ChatWindow always mounted to maintain streaming state */}
<div style={{
<div style={{
display: activeTab === 'chat' ? 'flex' : 'none',
flexDirection: 'column',
height: '100%'
Expand All @@ -401,8 +402,8 @@ function App() {
</div>
{/* Keep ScreenshotMonitor always mounted to maintain monitoring state */}
<div style={{ display: activeTab === 'screenshots' ? 'block' : 'none' }}>
<ScreenshotMonitor
settings={settings}
<ScreenshotMonitor
settings={settings}
onMonitoringStatusChange={setIsScreenMonitoring}
/>
</div>
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/components/SettingsPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,14 @@ const SettingsPanel = ({ settings, onSettingsChange, onApiKeyCheck, onApiKeyRequ
'gemini-2.5-flash',
'gemini-2.5-flash-lite',
'gemini-1.5-pro',
'gemini-2.0-flash-lite'
'gemini-2.0-flash-lite',
'llama3.2',
'mistral',
'gemma2',
'qwen2.5-coder-32b',
'deepseek-r1-distill-llama-70b',
'deepseek-v3.1:671b-cloud',
'qwen3-vl:235b-cloud'
];

// Combine base models with custom models
Expand All @@ -999,6 +1006,13 @@ const SettingsPanel = ({ settings, onSettingsChange, onApiKeyCheck, onApiKeyRequ
'gpt-4o',
'gpt-4.1-mini',
'gpt-4.1',
'llama3.2',
'mistral',
'gemma2',
'qwen2.5-coder-32b',
'deepseek-r1-distill-llama-70b',
'deepseek-v3.1:671b-cloud',
'qwen3-vl:235b-cloud'
];

// Combine base memory models with custom models
Expand Down
1 change: 1 addition & 0 deletions mirix/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,7 @@ def _get_ai_reply(
f"Attempt {attempt} failed: {he}. Retrying in {delay} seconds..."
)
time.sleep(delay)
continue

except Exception as e:
log_telemetry(
Expand Down
16 changes: 15 additions & 1 deletion mirix/agent/agent_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
GEMINI_MODELS,
MAXIMUM_NUM_IMAGES_IN_CLOUD,
OPENAI_MODELS,
OLLAMA_MODELS,
TEMPORARY_MESSAGE_LIMIT,
)

Expand Down Expand Up @@ -882,6 +883,8 @@ def _determine_model_provider(
return "anthropic"
elif model_name in OPENAI_MODELS:
return "openai"
elif model_name in OLLAMA_MODELS:
return "ollama"
else:
raise ValueError(f"Invalid model provider: {model_name}")

Expand Down Expand Up @@ -962,6 +965,15 @@ def _create_llm_config_for_provider(
model_wrapper=None,
context_window=128000,
)

elif provider == "ollama":
llm_config = LLMConfig(
model=model_name,
model_endpoint_type="ollama",
model_endpoint=self.agent_config.get("ollama_base_url") or model_settings.ollama_base_url or "http://localhost:11434/v1",
model_wrapper=None,
context_window=128000,
)
else:
# Custom provider - use custom config or fallback to OpenAI-compatible
if custom_agent_config is not None:
Expand Down Expand Up @@ -1051,7 +1063,7 @@ def set_memory_model(self, new_model, custom_agent_config: dict = None):
"""Set the model specifically for memory management operations"""

# Define allowed memory models
ALLOWED_MEMORY_MODELS = GEMINI_MODELS + OPENAI_MODELS
ALLOWED_MEMORY_MODELS = GEMINI_MODELS + OPENAI_MODELS + OLLAMA_MODELS

# Determine the effective provider
provider = self._determine_model_provider(new_model, custom_agent_config)
Expand Down Expand Up @@ -1105,6 +1117,8 @@ def set_memory_model(self, new_model, custom_agent_config: dict = None):
required_keys = ["GEMINI_API_KEY"]
elif new_model in OPENAI_MODELS:
required_keys = ["OPENAI_API_KEY"]
elif new_model in OLLAMA_MODELS:
required_keys = [] # Ollama usually doesn't require an API key

return {
"success": True,
Expand Down
10 changes: 10 additions & 0 deletions mirix/agent/app_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
"gpt-5",
]

OLLAMA_MODELS = [
"llama3.2",
"mistral",
"gemma2",
"qwen2.5-coder-32b",
"deepseek-r1-distill-llama-70b",
"deepseek-v3.1:671b-cloud",
"qwen3-vl:235b-cloud",
]

STUCK_TIMEOUT = 10
RUNNING_TIMEOUT = 30
TOTAL_TIMEOUT = 60
Expand Down
6 changes: 6 additions & 0 deletions mirix/configs/mirix.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
agent_name: mirix
model_name: gemini-2.5-flash-lite
# Ollama support - uncomment to use Ollama instead
# model_name: qwen3-vl:235b-cloud
# model_endpoint_type: ollama
# model_endpoint: http://localhost:11434/v1
# context_window: 16384
# put_inner_thoughts_in_kwargs: true
6 changes: 6 additions & 0 deletions mirix/configs/mirix_monitor.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
agent_name: mirix
model_name: gemini-2.0-flash
# Ollama support - uncomment to use Ollama instead
# model_name: qwen3-vl:235b-cloud
# model_endpoint_type: ollama
# model_endpoint: http://localhost:11434/v1
# context_window: 16384
# put_inner_thoughts_in_kwargs: true
is_screen_monitor: true
6 changes: 6 additions & 0 deletions mirix/configs/mirix_ollama.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
agent_name: mirix
model_name: qwen3-vl:235b-cloud
model_endpoint_type: ollama
model_endpoint: http://localhost:11434/v1
context_window: 16384
put_inner_thoughts_in_kwargs: true
Loading