Skip to content
Draft
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
19 changes: 19 additions & 0 deletions chartsmith-app/app/hooks/useSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,27 @@ export const useSession = (redirectIfNotLoggedIn: boolean = false) => {
validate(token);
}, [router, redirectIfNotLoggedIn]);

const refreshSession = useCallback(async () => {
const token = document.cookie
.split("; ")
.find((cookie) => cookie.startsWith("session="))
?.split("=")[1];

if (!token) {
return;
}

try {
const sess = await validateSession(token);
setSession(sess);
} catch (error) {
logger.error("Session refresh failed:", error);
}
}, []);

return {
isLoading,
session,
refreshSession,
};
};
39 changes: 38 additions & 1 deletion chartsmith-app/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,30 @@ export const CodeEditor = React.memo(function CodeEditor({
updateCurrentDiffIndex(allFilesWithContentPending);
}, [selectedFile, allFilesWithContentPending, updateCurrentDiffIndex]);

// Auto-accept patches when YOLO mode is enabled
useEffect(() => {
const shouldAutoAccept = session.user?.settings?.automaticallyAcceptPatches === true;
const hasPatches = allFilesWithContentPending.length > 0;
const planIsApplied = mostRecentPlan?.status === "applied";

if (shouldAutoAccept && hasPatches && planIsApplied && workspace) {
console.log("YOLO mode enabled: automatically accepting all patches");
// Use setTimeout to avoid state update conflicts
setTimeout(async () => {
try {
await acceptAllPatchesAction(session, workspace.id, workspace.currentRevisionNumber);
// Reload workspace after auto-accept
const freshWorkspace = await getWorkspaceAction(session, workspace.id);
if (freshWorkspace) {
setWorkspace(freshWorkspace);
}
} catch (error) {
console.error("Auto-accept failed:", error);
}
}, 100);
}
}, [allFilesWithContentPending, mostRecentPlan?.status, session.user?.settings?.automaticallyAcceptPatches, session, workspace, setWorkspace]);

// Handle outside clicks for dropdowns
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
Expand Down Expand Up @@ -626,7 +650,18 @@ export const CodeEditor = React.memo(function CodeEditor({
<div className="flex items-center gap-2">
{mostRecentPlan?.status === "applied" && (
<>
<div ref={acceptButtonRef} className="relative">
{session.user?.settings?.automaticallyAcceptPatches ? (
<div className={`px-3 py-1 text-xs rounded-md flex items-center gap-1 font-mono ${
theme === "dark"
? "bg-green-600 text-white"
: "bg-green-600 text-white"
}`}>
<Check className="w-3 h-3" />
Auto-accepting changes...
</div>
) : (
<>
<div ref={acceptButtonRef} className="relative">
<div className="flex">
<button
className={`px-3 py-1 text-xs rounded-l-md flex items-center gap-1 font-mono ${
Expand Down Expand Up @@ -737,6 +772,8 @@ export const CodeEditor = React.memo(function CodeEditor({
</div>
)}
</div>
</>
)}
</>
)}

Expand Down
202 changes: 173 additions & 29 deletions chartsmith-app/components/SettingsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client"

import React, { useState, useEffect } from 'react';
import { X, Trash2, Key, Check, Loader2 } from 'lucide-react';
import { X, Trash2, Key, Check, Loader2, Lock } from 'lucide-react';
import { useTheme, Theme } from '@/contexts/ThemeContext';
import { useAuth } from '@/contexts/AuthContext';
import { updateUserSettingAction } from '@/lib/auth/actions/update-user-setting';
Expand All @@ -22,14 +22,21 @@ interface SettingsSection {
}

export function SettingsModal({ isOpen, onClose, session }: SettingsModalProps) {
const { refreshSession } = useSession();
const { theme, setTheme } = useTheme();
const [activeSection, setActiveSection] = useState<'general' | 'replicated' | 'appearance' | 'editor' | 'changes'>('general');
const [autoAcceptChanges, setAutoAcceptChanges] = useState(session.user?.settings?.automaticallyAcceptPatches || false);
const [validateBeforeAccept, setValidateBeforeAccept] = useState(session.user?.settings?.evalBeforeAccept || false);
const [autoAcceptChanges, setAutoAcceptChanges] = useState(session.user?.settings?.automaticallyAcceptPatches ?? false);
const [validateBeforeAccept, setValidateBeforeAccept] = useState(session.user?.settings?.evalBeforeAccept ?? false);
const [isDeleting, setIsDeleting] = useState(false);
const [apiToken, setApiToken] = useState('');
const [savingAutoAccept, setSavingAutoAccept] = useState(false);
const [savingValidate, setSavingValidate] = useState(false);
const [showMinimap, setShowMinimap] = useState(session.user?.settings?.showMinimap ?? false);
const [tabSize, setTabSize] = useState(session.user?.settings?.tabSize ?? '2 spaces');
const [localTheme, setLocalTheme] = useState(session.user?.settings?.theme ?? 'auto');
const [savingMinimap, setSavingMinimap] = useState(false);
const [savingTabSize, setSavingTabSize] = useState(false);
const [isChangingTheme, setIsChangingTheme] = useState(false);
const [publicEnv, setPublicEnv] = useState<Record<string, string>>({});

useEffect(() => {
Expand All @@ -49,11 +56,32 @@ export function SettingsModal({ isOpen, onClose, session }: SettingsModalProps)

useEffect(() => {
if (session.user?.settings) {
setAutoAcceptChanges(session.user.settings.automaticallyAcceptPatches);
setValidateBeforeAccept(session.user.settings.evalBeforeAccept);
setAutoAcceptChanges(session.user.settings.automaticallyAcceptPatches ?? false);
setValidateBeforeAccept(session.user.settings.evalBeforeAccept ?? false);
setShowMinimap(session.user.settings.showMinimap ?? false);
setTabSize(session.user.settings.tabSize ?? '2 spaces');

// Only sync theme from database if there's a significant difference and we're not changing themes
// Remove automatic sync to prevent hydration conflicts - let user manually change theme
const dbTheme = session.user.settings.theme ?? 'auto';
setLocalTheme(dbTheme);
}
}, [session.user?.settings]);

// Sync localTheme only when modal opens (isOpen changes from false to true)
useEffect(() => {
if (isOpen && session.user?.settings) {
const dbTheme = session.user.settings.theme ?? 'auto';
console.log('Modal opened, syncing localTheme to:', dbTheme);
setLocalTheme(dbTheme);
}
}, [isOpen, session.user?.settings?.theme]);

// Debug localTheme changes
useEffect(() => {
console.log('localTheme state changed to:', localTheme);
}, [localTheme]);

if (!isOpen) return null;

const handleDeleteChats = () => {
Expand Down Expand Up @@ -83,26 +111,119 @@ export function SettingsModal({ isOpen, onClose, session }: SettingsModalProps)

const handleAutoAcceptChange = async (checked: boolean) => {
if (!session.user) return;
console.log('handleAutoAcceptChange called', { checked, currentValue: session.user.settings.automaticallyAcceptPatches });
setSavingAutoAccept(true);
try {
setAutoAcceptChanges(checked);
await updateUserSettingAction(session, 'automatically_accept_patches', checked.toString());
console.log('About to save automatically_accept_patches to database');
const result = await updateUserSettingAction(session, 'automatically_accept_patches', checked.toString());
console.log('Database save result:', result);
// Update session locally to reflect the change immediately
session.user.settings.automaticallyAcceptPatches = checked;
console.log('Updated local session state:', session.user.settings.automaticallyAcceptPatches);
} finally {
setSavingAutoAccept(false);
}
};

const handleValidateBeforeAcceptChange = async (checked: boolean) => {
if (!session.user) return;
console.log('handleValidateBeforeAcceptChange called', { checked, currentValue: session.user.settings.evalBeforeAccept });
setSavingValidate(true);
try {
setValidateBeforeAccept(checked);
await updateUserSettingAction(session, 'eval_before_accept', checked.toString());
console.log('About to save eval_before_accept to database');
const result = await updateUserSettingAction(session, 'eval_before_accept', checked.toString());
console.log('Database save result:', result);
// Update session locally to reflect the change immediately
session.user.settings.evalBeforeAccept = checked;
console.log('Updated local session state:', session.user.settings.evalBeforeAccept);
} finally {
setSavingValidate(false);
}
};


const handleThemeChange = async (newTheme: string) => {
if (!session.user) return;
console.log('handleThemeChange called', {
newTheme,
currentValue: session.user.settings.theme,
currentLocalTheme: localTheme,
currentContextTheme: theme
});
try {
// Set flag to prevent automatic theme sync from interfering
setIsChangingTheme(true);

// Update local theme state immediately for UI responsiveness
console.log('Setting localTheme to:', newTheme);
setLocalTheme(newTheme);

// Update theme context (this will also set the cookie)
console.log('Setting theme context to:', newTheme);
setTheme(newTheme as Theme);

console.log('About to save theme to database');
const result = await updateUserSettingAction(session, 'theme', newTheme);
console.log('Database save result:', result);

// Update session locally with the result from database
if (result.theme) {
session.user.settings.theme = result.theme;
console.log('Updated local session state:', session.user.settings.theme);
}

console.log('After all updates:', {
localTheme,
contextTheme: theme,
sessionTheme: session.user.settings.theme
});
} catch (error) {
console.error('Failed to save theme:', error);
// Revert both local theme and context theme on error
setLocalTheme(session.user.settings.theme);
setTheme(session.user.settings.theme as Theme);
} finally {
// Clear the flag after a short delay to allow state updates to settle
setTimeout(() => setIsChangingTheme(false), 500);
}
};

const handleTabSizeChange = async (newTabSize: string) => {
if (!session.user) return;
console.log('handleTabSizeChange called', { newTabSize, currentValue: session.user.settings.tabSize });
setSavingTabSize(true);
try {
setTabSize(newTabSize);
console.log('About to save tab_size to database');
const result = await updateUserSettingAction(session, 'tab_size', newTabSize);
console.log('Database save result:', result);
// Update session locally to reflect the change immediately
session.user.settings.tabSize = newTabSize;
console.log('Updated local session state:', session.user.settings.tabSize);
} finally {
setSavingTabSize(false);
}
};

const handleMinimapChange = async (checked: boolean) => {
if (!session.user) return;
console.log('handleMinimapChange called', { checked, currentValue: session.user.settings.showMinimap });
setSavingMinimap(true);
try {
setShowMinimap(checked);
console.log('About to save show_minimap to database');
const result = await updateUserSettingAction(session, 'show_minimap', checked.toString());
console.log('Database save result:', result);
// Update session locally to reflect the change immediately
session.user.settings.showMinimap = checked;
console.log('Updated local session state:', session.user.settings.showMinimap);
} finally {
setSavingMinimap(false);
}
};

const sections: SettingsSection[] = [
{
id: 'general',
Expand Down Expand Up @@ -199,9 +320,16 @@ export function SettingsModal({ isOpen, onClose, session }: SettingsModalProps)
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Theme
</label> <select
value={theme}
onChange={(e) => setTheme(e.target.value as Theme)}
</label>
<select
value={localTheme}
onChange={(e) => {
console.log('Select onChange triggered:', {
selectedValue: e.target.value,
currentLocalTheme: localTheme
});
handleThemeChange(e.target.value);
}}
className={`w-full px-3 py-2 rounded-lg transition-colors ${
theme === 'dark'
? 'bg-dark border-dark-border text-gray-300'
Expand Down Expand Up @@ -229,26 +357,42 @@ export function SettingsModal({ isOpen, onClose, session }: SettingsModalProps)
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Tab Size
</label>
<select className={`w-full px-3 py-2 rounded-lg transition-colors ${
theme === 'dark'
? 'bg-dark border-dark-border text-gray-300'
: 'bg-white border-gray-300 text-gray-900'
} border focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent`}>
<option>2 spaces</option>
<option>4 spaces</option>
<option>8 spaces</option>
</select>
{savingTabSize ? (
<div className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
<span className="text-sm text-gray-500">Saving...</span>
</div>
) : (
<select
value={tabSize}
onChange={(e) => handleTabSizeChange(e.target.value)}
className={`w-full px-3 py-2 rounded-lg transition-colors ${
theme === 'dark'
? 'bg-dark border-dark-border text-gray-300'
: 'bg-white border-gray-300 text-gray-900'
} border focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent`}>
<option value="2 spaces">2 spaces</option>
<option value="4 spaces">4 spaces</option>
<option value="8 spaces">8 spaces</option>
</select>
)}
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="minimap"
className={`rounded border transition-colors ${
theme === 'dark'
? 'border-dark-border bg-dark text-primary'
: 'border-gray-300 bg-white text-primary'
} focus:ring-primary`}
/>
{savingMinimap ? (
<Loader2 className="w-4 h-4 animate-spin text-primary" />
) : (
<input
type="checkbox"
id="minimap"
checked={showMinimap}
onChange={(e) => handleMinimapChange(e.target.checked)}
className={`rounded border transition-colors ${
theme === 'dark'
? 'border-dark-border bg-dark text-primary'
: 'border-gray-300 bg-white text-primary'
} focus:ring-primary`}
/>
)}
<label htmlFor="minimap" className={`text-sm ${
theme === 'dark' ? 'text-gray-300' : 'text-gray-700'
}`}>
Expand Down Expand Up @@ -317,7 +461,7 @@ export function SettingsModal({ isOpen, onClose, session }: SettingsModalProps)
</div>
</div>
),
}
},
];

return (
Expand Down
Loading