Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "codewiz",
"name": "wizai",
"version": "0.54.0",
"private": true,
"license": "BUSL-1.1",
Expand Down
5 changes: 4 additions & 1 deletion src/app/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default function ChatSessionPage({ params }: ChatSessionPageProps) {
const [sessionInfoLoaded, setSessionInfoLoaded] = useState(false);
const [sessionPermissionProfile, setSessionPermissionProfile] = useState<'default' | 'full_access'>('default');
const [sessionMode, setSessionMode] = useState<'code' | 'plan'>('code');
const [sessionAgentMode, setSessionAgentMode] = useState<'claude' | 'agent'>('claude');
const [sessionHasSummary, setSessionHasSummary] = useState(false);
const { setWorkingDirectory, setSessionId, setSessionTitle: setPanelSessionTitle, setFileTreeOpen, setGitPanelOpen, setDashboardPanelOpen } = usePanel();
const targetFilePath = searchParams.get('file') || undefined;
Expand All @@ -39,6 +40,7 @@ export default function ChatSessionPage({ params }: ChatSessionPageProps) {
setSessionModel('');
setSessionProviderId('');
setSessionInfoLoaded(false);
setSessionAgentMode('claude');

async function loadSession() {
try {
Expand All @@ -65,6 +67,7 @@ export default function ChatSessionPage({ params }: ChatSessionPageProps) {
setSessionProviderId(resolved.providerId);
setSessionPermissionProfile(data.session.permission_profile || 'default');
setSessionMode((data.session.mode as 'code' | 'plan') || 'code');
setSessionAgentMode((data.session.agent_mode as 'claude' | 'agent') || 'claude');
setSessionHasSummary(!!data.session.context_summary);
}
} catch {
Expand Down Expand Up @@ -186,7 +189,7 @@ export default function ChatSessionPage({ params }: ChatSessionPageProps) {

return (
<div className="flex h-full min-h-0 flex-col">
<ChatView key={id} sessionId={id} initialMessages={messages} initialHasMore={hasMore} modelName={sessionModel} providerId={sessionProviderId} initialPermissionProfile={sessionPermissionProfile} initialMode={sessionMode} initialHasSummary={sessionHasSummary} />
<ChatView key={id} sessionId={id} initialMessages={messages} initialHasMore={hasMore} modelName={sessionModel} providerId={sessionProviderId} initialPermissionProfile={sessionPermissionProfile} initialMode={sessionMode} initialHasSummary={sessionHasSummary} initialAgentMode={sessionAgentMode} />
</div>
);
}
226 changes: 113 additions & 113 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,126 +64,126 @@
}

:root {
--radius: 0.75rem;
--background: oklch(1 0 0);
--foreground: oklch(0.147 0.004 49.25);
--card: oklch(1 0 0);
--card-foreground: oklch(0.147 0.004 49.25);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.147 0.004 49.25);
--primary: oklch(0.546 0.245 262.881);
--primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.97 0.001 106.424);
--secondary-foreground: oklch(0.216 0.006 56.043);
--muted: oklch(0.97 0.001 106.424);
--muted-foreground: oklch(0.553 0.013 58.071);
--accent: oklch(0.97 0.001 106.424);
--accent-foreground: oklch(0.216 0.006 56.043);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.923 0.003 48.717);
--input: oklch(0.923 0.003 48.717);
--ring: oklch(0.546 0.245 262.881);
--chart-1: oklch(0.546 0.245 262.881);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.001 106.423);
--sidebar-foreground: oklch(0.147 0.004 49.25);
--sidebar-primary: oklch(0.546 0.245 262.881);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.97 0.001 106.424);
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
--sidebar-border: oklch(0.923 0.003 48.717);
--sidebar-ring: oklch(0.546 0.245 262.881);
--radius: 0.5rem;
--background: oklch(0.98 0.002 60);
--foreground: oklch(0.18 0.015 50);
--card: oklch(0.99 0.001 60);
--card-foreground: oklch(0.18 0.015 50);
--popover: oklch(0.99 0.001 60);
--popover-foreground: oklch(0.18 0.015 50);
--primary: oklch(0.65 0.19 55);
--primary-foreground: oklch(0.99 0.001 60);
--secondary: oklch(0.94 0.005 55);
--secondary-foreground: oklch(0.30 0.015 50);
--muted: oklch(0.93 0.005 55);
--muted-foreground: oklch(0.50 0.015 50);
--accent: oklch(0.94 0.005 55);
--accent-foreground: oklch(0.30 0.015 50);
--destructive: oklch(0.58 0.22 25);
--border: oklch(0.88 0.005 55);
--input: oklch(0.92 0.005 55);
--ring: oklch(0.65 0.19 55);
--chart-1: oklch(0.65 0.19 55);
--chart-2: oklch(0.60 0.15 180);
--chart-3: oklch(0.55 0.12 250);
--chart-4: oklch(0.70 0.14 85);
--chart-5: oklch(0.60 0.18 30);
--sidebar: oklch(0.22 0.01 50);
--sidebar-foreground: oklch(0.90 0.005 60);
--sidebar-primary: oklch(0.65 0.19 55);
--sidebar-primary-foreground: oklch(0.99 0.001 60);
--sidebar-accent: oklch(0.30 0.015 50);
--sidebar-accent-foreground: oklch(0.90 0.005 60);
--sidebar-border: oklch(0.32 0.01 50);
--sidebar-ring: oklch(0.65 0.19 55);
--user-bubble: oklch(0.22 0.005 250);
--user-bubble-foreground: oklch(0.985 0.001 106.423);
--user-bubble-foreground: oklch(0.95 0.002 60);
/* Status semantic tokens */
--status-success: oklch(0.527 0.154 150.069);
--status-success-foreground: oklch(0.393 0.124 148.697);
--status-success-muted: oklch(0.527 0.154 150.069 / 10%);
--status-success-border: oklch(0.527 0.154 150.069 / 20%);
--status-warning: oklch(0.705 0.213 47.604);
--status-warning-foreground: oklch(0.646 0.222 41.116);
--status-warning-muted: oklch(0.705 0.213 47.604 / 10%);
--status-warning-border: oklch(0.705 0.213 47.604 / 20%);
--status-error: oklch(0.577 0.245 27.325);
--status-error-foreground: oklch(0.577 0.245 27.325);
--status-error-muted: oklch(0.577 0.245 27.325 / 10%);
--status-error-border: oklch(0.577 0.245 27.325 / 20%);
--status-info: oklch(0.546 0.245 262.881);
--status-info-foreground: oklch(0.546 0.245 262.881);
--status-info-muted: oklch(0.546 0.245 262.881 / 10%);
--status-info-border: oklch(0.546 0.245 262.881 / 20%);
--status-success: oklch(0.60 0.18 148);
--status-success-foreground: oklch(0.40 0.12 148);
--status-success-muted: oklch(0.60 0.18 148 / 12%);
--status-success-border: oklch(0.60 0.18 148 / 25%);
--status-warning: oklch(0.72 0.18 80);
--status-warning-foreground: oklch(0.30 0.10 50);
--status-warning-muted: oklch(0.72 0.18 80 / 12%);
--status-warning-border: oklch(0.72 0.18 80 / 25%);
--status-error: oklch(0.60 0.22 25);
--status-error-foreground: oklch(0.99 0.001 60);
--status-error-muted: oklch(0.60 0.22 25 / 12%);
--status-error-border: oklch(0.60 0.22 25 / 25%);
--status-info: oklch(0.60 0.18 250);
--status-info-foreground: oklch(0.99 0.001 60);
--status-info-muted: oklch(0.60 0.18 250 / 12%);
--status-info-border: oklch(0.60 0.18 250 / 25%);
/* Terminal / code-block (bash) tokens */
--terminal-bg: oklch(0.13 0.004 49.25);
--terminal-foreground: oklch(0.87 0.006 106);
--terminal-muted: oklch(0.55 0.01 58);
--terminal-border: oklch(0.30 0.005 49);
--terminal-accent: oklch(0.55 0.17 155);
--terminal-hover-bg: oklch(0.25 0.005 49);
--terminal-gradient-from: oklch(0.13 0.004 49.25);
--terminal-bg: oklch(0.12 0.005 50);
--terminal-foreground: oklch(0.88 0.005 60);
--terminal-muted: oklch(0.55 0.01 55);
--terminal-border: oklch(0.30 0.005 50);
--terminal-accent: oklch(0.65 0.18 55);
--terminal-hover-bg: oklch(0.22 0.005 50);
--terminal-gradient-from: oklch(0.12 0.005 50);
}

.dark {
--background: oklch(0.147 0.004 49.25);
--foreground: oklch(0.985 0.001 106.423);
--card: oklch(0.216 0.006 56.043);
--card-foreground: oklch(0.985 0.001 106.423);
--popover: oklch(0.216 0.006 56.043);
--popover-foreground: oklch(0.985 0.001 106.423);
--primary: oklch(0.623 0.214 259.815);
--primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.268 0.007 34.298);
--secondary-foreground: oklch(0.985 0.001 106.423);
--muted: oklch(0.268 0.007 34.298);
--muted-foreground: oklch(0.709 0.01 56.259);
--accent: oklch(0.268 0.007 34.298);
--accent-foreground: oklch(0.985 0.001 106.423);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.623 0.214 259.815);
--chart-1: oklch(0.623 0.214 259.815);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.216 0.006 56.043);
--sidebar-foreground: oklch(0.985 0.001 106.423);
--sidebar-primary: oklch(0.623 0.214 259.815);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.268 0.007 34.298);
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.623 0.214 259.815);
--user-bubble: oklch(0.90 0.003 106);
--user-bubble-foreground: oklch(0.147 0.004 49.25);
--background: oklch(0.11 0.01 50);
--foreground: oklch(0.92 0.003 60);
--card: oklch(0.16 0.012 50);
--card-foreground: oklch(0.92 0.003 60);
--popover: oklch(0.16 0.012 50);
--popover-foreground: oklch(0.92 0.003 60);
--primary: oklch(0.70 0.18 55);
--primary-foreground: oklch(0.11 0.01 50);
--secondary: oklch(0.22 0.01 50);
--secondary-foreground: oklch(0.88 0.005 60);
--muted: oklch(0.22 0.01 50);
--muted-foreground: oklch(0.60 0.01 55);
--accent: oklch(0.28 0.015 50);
--accent-foreground: oklch(0.92 0.003 60);
--destructive: oklch(0.65 0.22 25);
--border: oklch(0.28 0.01 50);
--input: oklch(0.22 0.01 50);
--ring: oklch(0.70 0.18 55);
--chart-1: oklch(0.70 0.18 55);
--chart-2: oklch(0.62 0.16 180);
--chart-3: oklch(0.58 0.15 250);
--chart-4: oklch(0.68 0.15 85);
--chart-5: oklch(0.65 0.18 30);
--sidebar: oklch(0.11 0.01 50);
--sidebar-foreground: oklch(0.88 0.005 60);
--sidebar-primary: oklch(0.70 0.18 55);
--sidebar-primary-foreground: oklch(0.11 0.01 50);
--sidebar-accent: oklch(0.22 0.012 50);
--sidebar-accent-foreground: oklch(0.88 0.005 60);
--sidebar-border: oklch(0.26 0.01 50);
--sidebar-ring: oklch(0.70 0.18 55);
--user-bubble: oklch(0.70 0.18 220);
--user-bubble-foreground: oklch(0.11 0.01 50);
/* Status semantic tokens */
--status-success: oklch(0.527 0.154 150.069);
--status-success-foreground: oklch(0.448 0.133 148.697);
--status-success-muted: oklch(0.527 0.154 150.069 / 10%);
--status-success-border: oklch(0.527 0.154 150.069 / 20%);
--status-warning: oklch(0.705 0.213 47.604);
--status-warning-foreground: oklch(0.705 0.213 47.604);
--status-warning-muted: oklch(0.705 0.213 47.604 / 10%);
--status-warning-border: oklch(0.705 0.213 47.604 / 20%);
--status-error: oklch(0.704 0.191 22.216);
--status-error-foreground: oklch(0.704 0.191 22.216);
--status-error-muted: oklch(0.704 0.191 22.216 / 10%);
--status-error-border: oklch(0.704 0.191 22.216 / 20%);
--status-info: oklch(0.623 0.214 259.815);
--status-info-foreground: oklch(0.623 0.214 259.815);
--status-info-muted: oklch(0.623 0.214 259.815 / 10%);
--status-info-border: oklch(0.623 0.214 259.815 / 20%);
/* Terminal tokens — dark mode: stronger border contrast against dark page bg */
--terminal-bg: oklch(0.10 0.002 49);
--terminal-foreground: oklch(0.87 0.006 106);
--terminal-muted: oklch(0.60 0.01 58);
--terminal-border: oklch(0.35 0.006 49);
--terminal-accent: oklch(0.62 0.17 155);
--terminal-hover-bg: oklch(0.22 0.005 49);
--terminal-gradient-from: oklch(0.10 0.002 49);
--status-success: oklch(0.65 0.18 148);
--status-success-foreground: oklch(0.11 0.01 50);
--status-success-muted: oklch(0.65 0.18 148 / 15%);
--status-success-border: oklch(0.65 0.18 148 / 30%);
--status-warning: oklch(0.75 0.18 80);
--status-warning-foreground: oklch(0.11 0.01 50);
--status-warning-muted: oklch(0.75 0.18 80 / 15%);
--status-warning-border: oklch(0.75 0.18 80 / 30%);
--status-error: oklch(0.65 0.22 25);
--status-error-foreground: oklch(0.11 0.01 50);
--status-error-muted: oklch(0.65 0.22 25 / 15%);
--status-error-border: oklch(0.65 0.22 25 / 30%);
--status-info: oklch(0.65 0.18 250);
--status-info-foreground: oklch(0.11 0.01 50);
--status-info-muted: oklch(0.65 0.18 250 / 15%);
--status-info-border: oklch(0.65 0.18 250 / 30%);
/* Terminal tokens — dark mode: warm dark background */
--terminal-bg: oklch(0.09 0.008 50);
--terminal-foreground: oklch(0.88 0.005 60);
--terminal-muted: oklch(0.55 0.01 55);
--terminal-border: oklch(0.28 0.008 50);
--terminal-accent: oklch(0.70 0.18 55);
--terminal-hover-bg: oklch(0.18 0.008 50);
--terminal-gradient-from: oklch(0.09 0.008 50);
}

@layer base {
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const geistMono = Geist_Mono({
});

export const metadata: Metadata = {
title: "CodePilot",
title: "WizAI",
description: "A multi-model AI agent desktop client",
};

Expand Down
39 changes: 38 additions & 1 deletion src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,13 @@ interface ChatViewProps {
initialPermissionProfile?: 'default' | 'full_access';
initialMode?: 'code' | 'plan';
initialHasSummary?: boolean;
initialAgentMode?: 'claude' | 'agent';
}

/** Maximum messages kept in React state. Older messages are trimmed and reloaded on scroll. */
const MAX_MESSAGES_IN_MEMORY = 300;

export function ChatView({ sessionId, initialMessages = [], initialHasMore = false, modelName, providerId, initialPermissionProfile, initialMode, initialHasSummary }: ChatViewProps) {
export function ChatView({ sessionId, initialMessages = [], initialHasMore = false, modelName, providerId, initialPermissionProfile, initialMode, initialHasSummary, initialAgentMode }: ChatViewProps) {
const { setStreamingSessionId, workingDirectory, setPendingApprovalSessionId, setDashboardPanelOpen, setFileTreeOpen, setIsAssistantWorkspace } = usePanel();
const { t } = useTranslation();
const router = useRouter();
Expand Down Expand Up @@ -127,6 +128,7 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal
});
}, []);
const [mode, setMode] = useState<string>(initialMode || 'code');
const [agentMode, setAgentMode] = useState<'claude' | 'agent'>(initialAgentMode || 'claude');
const [currentModel, setCurrentModel] = useState(() => modelName || (typeof window !== 'undefined' ? localStorage.getItem('codepilot:last-model') : null) || 'sonnet');
const [currentProviderId, setCurrentProviderId] = useState(() => providerId || (typeof window !== 'undefined' ? localStorage.getItem('codepilot:last-provider-id') : null) || '');
const [selectedEffort, setSelectedEffort] = useState<string | undefined>(undefined);
Expand Down Expand Up @@ -235,6 +237,19 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal
const sendMessageRef = useRef<(content: string, files?: FileAttachment[], systemPromptAppend?: string, displayOverride?: string, mentions?: MentionRef[]) => Promise<void>>(undefined);
const initMetaRef = useRef<{ tools?: unknown; slash_commands?: unknown; skills?: unknown } | null>(null);

const handleAgentModeChange = useCallback((newAgentMode: 'claude' | 'agent') => {
setAgentMode(newAgentMode);
if (sessionId) {
fetch(`/api/chat/sessions/${sessionId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ agent_mode: newAgentMode }),
}).then(() => {
window.dispatchEvent(new CustomEvent('session-updated'));
}).catch(() => { /* silent */ });
}
}, [sessionId]);

const handleModeChange = useCallback((newMode: string) => {
setMode(newMode);
if (sessionId) {
Expand Down Expand Up @@ -876,6 +891,28 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal
</Button>
</div>
)}
{/* Agent mode toggle */}
<div className="flex items-center justify-end gap-2 border-b border-border/50 px-4 py-1.5">
<span className="text-xs text-muted-foreground">Agent:</span>
<button
onClick={() => handleAgentModeChange(agentMode === 'claude' ? 'agent' : 'claude')}
className={`
relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring
${agentMode === 'agent' ? 'bg-primary' : 'bg-muted'}
`}
aria-label="Toggle agent mode"
>
<span
className={`
inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200
${agentMode === 'agent' ? 'translate-x-6' : 'translate-x-1'}
`}
/>
</button>
<span className={`text-xs font-medium ${agentMode === 'agent' ? 'text-primary' : 'text-muted-foreground'}`}>
{agentMode === 'agent' ? 'Agent' : 'Claude'}
</span>
</div>
<MessageList
messages={messages}
streamingContent={streamingContent}
Expand Down
Loading
Loading