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
1 change: 1 addition & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const nextConfig: NextConfig = {
serverExternalPackages: ['better-sqlite3', 'discord.js', '@discordjs/ws', 'zlib-sync'],
env: {
NEXT_PUBLIC_APP_VERSION: pkg.version,
NEXT_PUBLIC_SHOW_SOME: process.env.NEXT_PUBLIC_SHOW_SOME ?? 'true',
},
};

Expand Down
9 changes: 4 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@ai-sdk/google": "^3.0.31",
"@ai-sdk/google-vertex": "^4.0.80",
"@ai-sdk/openai": "^3.0.34",
"@anthropic-ai/claude-agent-sdk": "^0.2.62",
"@anthropic-ai/claude-agent-sdk": "^0.2.76",
"@google/genai": "^1.43.0",
"@larksuiteoapi/node-sdk": "^1.59.0",
"@lobehub/icons": "^4.6.0",
Expand Down
13 changes: 12 additions & 1 deletion src/app/api/providers/models/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { ErrorResponse, ProviderModelGroup } from '@/types';
const DEFAULT_MODELS = [
{ value: 'sonnet', label: 'Sonnet 4.6' },
{ value: 'opus', label: 'Opus 4.6' },
{ value: 'claude-opus-4-6[1m]', label: 'claude-opus-4-6[1m-c]' },
{ value: 'haiku', label: 'Haiku 4.5' },
];

Expand Down Expand Up @@ -69,7 +70,7 @@ export async function GET() {
const { getCachedModels } = await import('@/lib/agent-sdk-capabilities');
const sdkModels = getCachedModels('env');
if (sdkModels.length > 0) {
groups[0].models = sdkModels.map(m => {
const mapped: Array<Record<string, unknown> & { value: string; label: string }> = sdkModels.map(m => {
const cw = getContextWindow(m.value);
return {
value: m.value,
Expand All @@ -81,6 +82,16 @@ export async function GET() {
...(cw != null ? { contextWindow: cw } : {}),
};
});
// Inject claude-opus-4-6[1m] if not already present from SDK
if (!mapped.some(m => m.value === 'claude-opus-4-6[1m]')) {
const cw = getContextWindow('claude-opus-4-6[1m]');
mapped.push({
value: 'claude-opus-4-6[1m]',
label: 'claude-opus-4-6[1m-c]',
...(cw != null ? { contextWindow: cw } : {}),
});
}
groups[0].models = mapped;
}
} catch {
// SDK capabilities not available, keep defaults
Expand Down
116 changes: 73 additions & 43 deletions src/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,25 @@ export default function NewChatPage() {
localStorage.setItem('codepilot:last-working-directory', path);
}, []);

const handleNewChat = useCallback(async () => {
const dir = workingDir || localStorage.getItem('codepilot:last-working-directory') || '';
if (!dir) return;
try {
const lastModel = localStorage.getItem('codepilot:last-model') || currentModel;
const lastProvider = localStorage.getItem('codepilot:last-provider-id') || currentProviderId || '';
const res = await fetch('/api/chat/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ working_directory: dir, model: lastModel, provider_id: lastProvider }),
});
if (res.ok) {
const data = await res.json();
router.push(`/chat/${data.session.id}`);
window.dispatchEvent(new CustomEvent('session-created'));
}
} catch { /* ignore */ }
}, [workingDir, currentModel, currentProviderId, router]);

const stopStreaming = useCallback(() => {
abortControllerRef.current?.abort();
abortControllerRef.current = null;
Expand Down Expand Up @@ -503,13 +522,18 @@ export default function NewChatPage() {
}
}, [sendFirstMessage]);

const showSome = process.env.NEXT_PUBLIC_SHOW_SOME !== 'false';
const showEmptyState = messages.length === 0 && !isStreaming && (!workingDir.trim() || !hasProvider);
const hideComposer = showEmptyState && !showSome && !hasProvider;

return (
<div className="flex h-full min-h-0 flex-col">
{messages.length === 0 && !isStreaming && (!workingDir.trim() || !hasProvider) ? (
{showEmptyState ? (
<ChatEmptyState
hasDirectory={!!workingDir.trim()}
hasProvider={hasProvider}
onSelectFolder={handleSelectFolder}
onNewChat={handleNewChat}
recentProjects={recentProjects}
onSelectProject={handleSelectProject}
/>
Expand All @@ -525,49 +549,55 @@ export default function NewChatPage() {
statusText={statusText}
/>
)}
{errorBanner && (
<ErrorBanner
message={errorBanner.message}
description={errorBanner.description}
className="mx-4 mb-2"
onDismiss={() => setErrorBanner(null)}
actions={[
{ label: t('error.retry'), onClick: () => setErrorBanner(null) },
]}
/>
)}
<PermissionPrompt
pendingPermission={pendingPermission}
permissionResolved={permissionResolved}
onPermissionResponse={handlePermissionResponse}
toolUses={toolUses}
/>
<MessageInput
onSend={sendFirstMessage}
onCommand={handleCommand}
onStop={stopStreaming}
disabled={false}
isStreaming={isStreaming}
modelName={currentModel}
onModelChange={setCurrentModel}
providerId={currentProviderId}
onProviderModelChange={(pid, model) => {
setCurrentProviderId(pid);
setCurrentModel(model);
}}
workingDirectory={workingDir}
effort={selectedEffort}
onEffortChange={setSelectedEffort}
/>
<ChatComposerActionBar
left={<ImageGenToggle />}
center={
<ChatPermissionSelector
permissionProfile={permissionProfile}
onPermissionChange={setPermissionProfile}
{!hideComposer && (
<>
{errorBanner && (
<ErrorBanner
message={errorBanner.message}
description={errorBanner.description}
className="mx-4 mb-2"
onDismiss={() => setErrorBanner(null)}
actions={[
{ label: t('error.retry'), onClick: () => setErrorBanner(null) },
]}
/>
)}
<PermissionPrompt
pendingPermission={pendingPermission}
permissionResolved={permissionResolved}
onPermissionResponse={handlePermissionResponse}
toolUses={toolUses}
/>
}
/>
<MessageInput
onSend={sendFirstMessage}
onCommand={handleCommand}
onStop={stopStreaming}
disabled={false}
isStreaming={isStreaming}
modelName={currentModel}
onModelChange={setCurrentModel}
providerId={currentProviderId}
onProviderModelChange={(pid, model) => {
setCurrentProviderId(pid);
setCurrentModel(model);
}}
workingDirectory={workingDir}
effort={selectedEffort}
onEffortChange={setSelectedEffort}
/>
{showSome && (
<ChatComposerActionBar
left={<ImageGenToggle />}
center={
<ChatPermissionSelector
permissionProfile={permissionProfile}
onPermissionChange={setPermissionProfile}
/>
}
/>
)}
</>
)}
<FolderPicker
open={folderPickerOpen}
onOpenChange={setFolderPickerOpen}
Expand Down
26 changes: 23 additions & 3 deletions src/components/chat/ChatEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client';

import { Button } from '@/components/ui/button';
import { FolderOpen } from '@/components/ui/icon';
import { FolderOpen, Plus } from '@/components/ui/icon';
import { useTranslation } from '@/hooks/useTranslation';

interface ChatEmptyStateProps {
hasDirectory: boolean;
hasProvider: boolean;
onSelectFolder: () => void;
onNewChat?: () => void;
recentProjects?: string[];
onSelectProject?: (path: string) => void;
}
Expand All @@ -16,12 +17,24 @@ export function ChatEmptyState({
hasDirectory,
hasProvider,
onSelectFolder,
onNewChat,
recentProjects,
onSelectProject,
}: ChatEmptyStateProps) {
const { t } = useTranslation();
const showSome = process.env.NEXT_PUBLIC_SHOW_SOME !== 'false';

if (hasDirectory && hasProvider) {
if (hasDirectory && (hasProvider || !showSome)) {
if (!showSome && onNewChat) {
return (
<div className="flex flex-1 items-center justify-center p-8">
<Button size="sm" className="gap-1.5" onClick={onNewChat}>
<Plus size={14} />
{t('chatList.newConversation')}
</Button>
</div>
);
}
return (
<div className="flex flex-1 items-center justify-center p-8">
<p className="text-sm text-muted-foreground">{t('chat.empty.ready')}</p>
Expand Down Expand Up @@ -66,7 +79,7 @@ export function ChatEmptyState({
</div>
)}

{!hasProvider && (
{!hasProvider && showSome && (
<div className="space-y-2">
<p className="text-sm font-medium">{t('chat.empty.noProvider')}</p>
<Button
Expand All @@ -78,6 +91,13 @@ export function ChatEmptyState({
</Button>
</div>
)}

{!hasProvider && !showSome && onNewChat && (
<Button size="sm" className="gap-1.5" onClick={onNewChat}>
<Plus size={14} />
{t('chatList.newConversation')}
</Button>
)}
</div>
</div>
);
Expand Down
34 changes: 18 additions & 16 deletions src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -437,22 +437,24 @@ export function ChatView({ sessionId, initialMessages = [], initialHasMore = fal
onEffortChange={setSelectedEffort}
sdkInitMeta={initMetaRef.current}
/>
<ChatComposerActionBar
left={<ImageGenToggle />}
center={
<ChatPermissionSelector
sessionId={sessionId}
permissionProfile={permissionProfile}
onPermissionChange={setPermissionProfile}
/>
}
right={
<ContextUsageIndicator
messages={messages}
modelName={currentModel}
/>
}
/>
{process.env.NEXT_PUBLIC_SHOW_SOME !== 'false' && (
<ChatComposerActionBar
left={<ImageGenToggle />}
center={
<ChatPermissionSelector
sessionId={sessionId}
permissionProfile={permissionProfile}
onPermissionChange={setPermissionProfile}
/>
}
right={
<ContextUsageIndicator
messages={messages}
modelName={currentModel}
/>
}
/>
)}
</div>
);
}
14 changes: 8 additions & 6 deletions src/components/chat/ModelSelectorDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,14 @@ export function ModelSelectorDropdown({
</div>
)}
</CommandListItems>
<CommandListFooter>
<CommandListFooterAction onClick={() => { setModelMenuOpen(false); setModelSearch(''); window.location.href = '/settings'; }}>
<Gear size={14} />
{t('composer.manageProviders' as TranslationKey)}
</CommandListFooterAction>
</CommandListFooter>
{process.env.NEXT_PUBLIC_SHOW_SOME !== 'false' && (
<CommandListFooter>
<CommandListFooterAction onClick={() => { setModelMenuOpen(false); setModelSearch(''); window.location.href = '/settings'; }}>
<Gear size={14} />
{t('composer.manageProviders' as TranslationKey)}
</CommandListFooterAction>
</CommandListFooter>
)}
</CommandList>
)}
</div>
Expand Down
16 changes: 9 additions & 7 deletions src/components/layout/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,15 @@ export function AppShell({ children }: { children: React.ReactNode }) {
<BatchImageGenContext.Provider value={batchImageGenValue}>
<TooltipProvider delayDuration={300}>
<div className="flex h-screen overflow-hidden">
<NavRail
chatListOpen={chatListOpen}
onToggleChatList={() => setChatListOpen(!chatListOpen)}
hasUpdate={updateContextValue.updateInfo?.updateAvailable ?? false}
readyToInstall={updateContextValue.updateInfo?.readyToInstall ?? false}
skipPermissionsActive={skipPermissionsActive}
/>
{process.env.NEXT_PUBLIC_SHOW_SOME !== 'false' && (
<NavRail
chatListOpen={chatListOpen}
onToggleChatList={() => setChatListOpen(!chatListOpen)}
hasUpdate={updateContextValue.updateInfo?.updateAvailable ?? false}
readyToInstall={updateContextValue.updateInfo?.readyToInstall ?? false}
skipPermissionsActive={skipPermissionsActive}
/>
)}
<ErrorBoundary>
<ChatListPanel open={chatListOpen} width={chatListWidth} />
</ErrorBoundary>
Expand Down
Loading