Skip to content
Open
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
94 changes: 75 additions & 19 deletions frontend/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuGroup,
} from '@/frontend/components/ui/dropdown-menu';
import useAutoResizeTextarea from '@/hooks/useAutoResizeTextArea';
import { UseChatHelpers, useCompletion } from '@ai-sdk/react';
Expand Down Expand Up @@ -187,6 +190,7 @@ const ChatInput = memo(PureChatInput, (prevProps, nextProps) => {
const PureChatModelDropdown = () => {
const getKey = useAPIKeyStore((state) => state.getKey);
const { selectedModel, setModel } = useModelStore();
const navigate = useNavigate();

const isModelEnabled = useCallback(
(model: AIModel) => {
Expand All @@ -197,6 +201,35 @@ const PureChatModelDropdown = () => {
[getKey]
);

const groupedModels = useMemo(() => {
const groups: Record<string, { models: AIModel[]; hasApiKey: boolean; keyStatus: string }> = {};

AI_MODELS.forEach((model) => {
const config = getModelConfig(model);
const provider = config.provider;
const apiKey = getKey(provider);
const hasKey = !!apiKey;

if (!groups[provider]) {
groups[provider] = {
models: [],
hasApiKey: hasKey,
keyStatus: hasKey ? `API key configured` : 'No API key'
};
}

groups[provider].models.push(model);
});

return groups;
}, [getKey]);

const providerDisplayNames = {
google: 'Google AI',
openai: 'OpenAI',
openrouter: 'OpenRouter'
};
Comment on lines +227 to +231
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type safety for provider display name mapping.

The provider display names mapping should include type checking to handle unknown providers gracefully.

- const providerDisplayNames = {
+ const providerDisplayNames: Record<string, string> = {
   google: 'Google AI',
   openai: 'OpenAI',
-  openrouter: 'OpenRouter'
+  openrouter: 'OpenRouter',
+  // Add fallback for unknown providers
+ };
+ 
+ const getProviderDisplayName = (provider: string): string => {
+   return providerDisplayNames[provider] || provider;
  };

Then update the usage in line 261:

- {providerDisplayNames[provider as keyof typeof providerDisplayNames]}
+ {getProviderDisplayName(provider)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const providerDisplayNames = {
google: 'Google AI',
openai: 'OpenAI',
openrouter: 'OpenRouter'
};
// original context…
const providerDisplayNames: Record<string, string> = {
google: 'Google AI',
openai: 'OpenAI',
openrouter: 'OpenRouter',
// Add fallback for unknown providers
};
const getProviderDisplayName = (provider: string): string => {
return providerDisplayNames[provider] || provider;
};
// …later in your JSX (around line 261)
- {providerDisplayNames[provider as keyof typeof providerDisplayNames]}
+ {getProviderDisplayName(provider)}
🤖 Prompt for AI Agents
In frontend/components/ChatInput.tsx around lines 227 to 231, the
providerDisplayNames object lacks type safety, which can cause issues with
unknown providers. Define a TypeScript type or interface for the provider keys
and use it to type the providerDisplayNames object. Then, update the usage at
line 261 to safely access the display name, providing a fallback value for
unknown providers to handle them gracefully.


return (
<div className="flex items-center gap-2">
<DropdownMenu>
Expand All @@ -213,30 +246,53 @@ const PureChatModelDropdown = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className={cn('min-w-[10rem]', 'border-border', 'bg-popover')}
className={cn('min-w-[14rem]', 'border-border', 'bg-popover')}
>
{AI_MODELS.map((model) => {
const isEnabled = isModelEnabled(model);
return (
<DropdownMenuItem
key={model}
onSelect={() => isEnabled && setModel(model)}
disabled={!isEnabled}
{Object.entries(groupedModels).map(([provider, group], index) => (
<DropdownMenuGroup key={provider}>
<DropdownMenuLabel
className={cn(
'flex items-center justify-between gap-2',
'cursor-pointer'
"flex items-center justify-between px-2 py-1.5",
!group.hasApiKey && "cursor-pointer hover:bg-accent/50 transition-colors"
)}
onClick={!group.hasApiKey ? () => navigate('/settings') : undefined}
>
<span>{model}</span>
{selectedModel === model && (
<Check
className="w-4 h-4 text-blue-500"
aria-label="Selected"
/>
<span className="text-sm font-medium">
{providerDisplayNames[provider as keyof typeof providerDisplayNames]}
</span>
{!group.hasApiKey && (
<span className="text-xs px-1.5 py-0.5 rounded-sm bg-muted text-muted-foreground hover:bg-muted/80">
Click to configure
</span>
)}
</DropdownMenuItem>
);
})}
</DropdownMenuLabel>
{group.models.map((model) => {
const isEnabled = isModelEnabled(model);
return (
<DropdownMenuItem
key={model}
onSelect={() => isEnabled && setModel(model)}
disabled={!isEnabled}
className={cn(
'flex items-center justify-between gap-2',
'cursor-pointer ml-2'
)}
>
<span>{model}</span>
{selectedModel === model && (
<Check
className="w-4 h-4 text-blue-500"
aria-label="Selected"
/>
)}
</DropdownMenuItem>
);
})}
{index < Object.keys(groupedModels).length - 1 && (
<DropdownMenuSeparator />
)}
</DropdownMenuGroup>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down