mirror of https://github.com/kortix-ai/suna.git
ui: improved agent selector
This commit is contained in:
parent
d58c5e2cf1
commit
f61e3a7a3b
|
@ -25,7 +25,7 @@ import { useAccounts } from '@/hooks/use-accounts';
|
|||
import { config } from '@/lib/config';
|
||||
import { useInitiateAgentWithInvalidation } from '@/hooks/react-query/dashboard/use-initiate-agent';
|
||||
import { ModalProviders } from '@/providers/modal-providers';
|
||||
import { AgentSelector } from '@/components/dashboard/agent-selector';
|
||||
import { useAgents } from '@/hooks/react-query/agents/use-agents';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useModal } from '@/hooks/use-modal-store';
|
||||
import { Examples } from './suggestions/examples';
|
||||
|
@ -52,6 +52,20 @@ export function DashboardContent() {
|
|||
const initiateAgentMutation = useInitiateAgentWithInvalidation();
|
||||
const { onOpen } = useModal();
|
||||
|
||||
// Fetch agents to get the selected agent's name
|
||||
const { data: agentsResponse } = useAgents({
|
||||
limit: 100,
|
||||
sort_by: 'name',
|
||||
sort_order: 'asc'
|
||||
});
|
||||
|
||||
const agents = agentsResponse?.agents || [];
|
||||
const selectedAgent = selectedAgentId
|
||||
? agents.find(agent => agent.agent_id === selectedAgentId)
|
||||
: null;
|
||||
const displayName = selectedAgent?.name || 'Suna';
|
||||
const agentAvatar = selectedAgent?.avatar;
|
||||
|
||||
const threadQuery = useThreadQuery(initiatedThreadId || '');
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -189,24 +203,25 @@ export function DashboardContent() {
|
|||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[650px] max-w-[90%]">
|
||||
<div className="flex flex-col items-center text-center w-full">
|
||||
<div className="flex items-center gap-1">
|
||||
<h1 className="tracking-tight text-4xl text-muted-foreground leading-tight">
|
||||
Hey, I am
|
||||
</h1>
|
||||
<AgentSelector
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={setSelectedAgentId}
|
||||
variant="heading"
|
||||
/>
|
||||
<h1 className="tracking-tight text-4xl font-semibold leading-tight text-primary">
|
||||
{displayName}
|
||||
{agentAvatar && (
|
||||
<span className="text-muted-foreground ml-2">
|
||||
{agentAvatar}
|
||||
</span>
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="tracking-tight text-3xl font-normal text-muted-foreground/80 mt-2">
|
||||
What would you like to do today?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={cn(
|
||||
"w-full mb-2",
|
||||
"max-w-full",
|
||||
|
@ -220,12 +235,12 @@ export function DashboardContent() {
|
|||
value={inputValue}
|
||||
onChange={setInputValue}
|
||||
hideAttachments={false}
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={setSelectedAgentId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Examples onSelectPrompt={setInputValue} />
|
||||
</div>
|
||||
|
||||
<BillingErrorAlert
|
||||
message={billingError?.message}
|
||||
currentUsage={billingError?.currentUsage}
|
||||
|
|
|
@ -28,7 +28,7 @@ import { UnifiedMessage, ApiMessageType, ToolCallInput, Project } from '../_type
|
|||
import { useThreadData, useToolCalls, useBilling, useKeyboardShortcuts } from '../_hooks';
|
||||
import { ThreadError, UpgradeDialog, ThreadLayout } from '../_components';
|
||||
import { useVncPreloader } from '@/hooks/useVncPreloader';
|
||||
import { useAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useThreadAgent } from '@/hooks/react-query/agents/use-agents';
|
||||
|
||||
export default function ThreadPage({
|
||||
params,
|
||||
|
@ -124,7 +124,8 @@ export default function ThreadPage({
|
|||
const addUserMessageMutation = useAddUserMessageMutation();
|
||||
const startAgentMutation = useStartAgentMutation();
|
||||
const stopAgentMutation = useStopAgentMutation();
|
||||
const { data: agent } = useAgent(threadQuery.data?.agent_id);
|
||||
const { data: threadAgentData } = useThreadAgent(threadId);
|
||||
const agent = threadAgentData?.agent;
|
||||
const workflowId = threadQuery.data?.metadata?.workflow_id;
|
||||
|
||||
const { data: subscriptionData } = useSubscription();
|
||||
|
|
|
@ -288,6 +288,9 @@ export const ChatInput = forwardRef<ChatInputHandles, ChatInputProps>(
|
|||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={onAgentSelect}
|
||||
/>
|
||||
</CardContent>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Settings, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { ModelSelector } from './model-selector';
|
||||
import { SubscriptionStatus } from './_use-model-selection';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface ChatSettingsDialogProps {
|
||||
selectedModel: string;
|
||||
onModelChange: (model: string) => void;
|
||||
modelOptions: any[];
|
||||
subscriptionStatus: SubscriptionStatus;
|
||||
canAccessModel: (modelId: string) => boolean;
|
||||
refreshCustomModels?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function ChatSettingsDialog({
|
||||
selectedModel,
|
||||
onModelChange,
|
||||
modelOptions,
|
||||
subscriptionStatus,
|
||||
canAccessModel,
|
||||
refreshCustomModels,
|
||||
disabled = false,
|
||||
className,
|
||||
open: controlledOpen,
|
||||
onOpenChange: controlledOnOpenChange,
|
||||
}: ChatSettingsDialogProps) {
|
||||
const [internalOpen, setInternalOpen] = useState(false);
|
||||
|
||||
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
||||
const setOpen = controlledOnOpenChange || setInternalOpen;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
{controlledOpen === undefined && (
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
'h-8 w-8 p-0 text-muted-foreground hover:text-foreground',
|
||||
'rounded-lg',
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
)}
|
||||
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5" />
|
||||
Chat Settings
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="model-selector" className="text-sm font-medium">
|
||||
AI Model
|
||||
</Label>
|
||||
<div className="w-full">
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
hasBorder={true}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Choose the AI model that best fits your needs. Premium models offer better performance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,308 @@
|
|||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Settings, ChevronRight, Bot, Presentation, Video, Code, FileSpreadsheet, Search, Plus, Star, User, Sparkles, Database, MessageSquare, Calculator, Palette, Zap } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { ChatSettingsDialog } from './chat-settings-dialog';
|
||||
import { SubscriptionStatus } from './_use-model-selection';
|
||||
import { useAgents } from '@/hooks/react-query/agents/use-agents';
|
||||
import { useFeatureFlag } from '@/lib/feature-flags';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Label } from '@/components/ui/label';
|
||||
|
||||
interface PredefinedAgent {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
category: 'productivity' | 'creative' | 'development';
|
||||
}
|
||||
|
||||
const PREDEFINED_AGENTS: PredefinedAgent[] = [
|
||||
{
|
||||
id: 'slides',
|
||||
name: 'Slides Pro',
|
||||
description: 'Create stunning presentations and slide decks',
|
||||
icon: <Presentation className="h-4 w-4 mt-1" />,
|
||||
category: 'productivity'
|
||||
},
|
||||
{
|
||||
id: 'sheets',
|
||||
name: 'Data Analyst',
|
||||
description: 'Spreadsheet and data analysis expert',
|
||||
icon: <FileSpreadsheet className="h-4 w-4 mt-1" />,
|
||||
category: 'productivity'
|
||||
}
|
||||
];
|
||||
|
||||
interface ChatSettingsDropdownProps {
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: (agentId: string | undefined) => void;
|
||||
selectedModel: string;
|
||||
onModelChange: (model: string) => void;
|
||||
modelOptions: any[];
|
||||
subscriptionStatus: SubscriptionStatus;
|
||||
canAccessModel: (modelId: string) => boolean;
|
||||
refreshCustomModels?: () => void;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ChatSettingsDropdown({
|
||||
selectedAgentId,
|
||||
onAgentSelect,
|
||||
selectedModel,
|
||||
onModelChange,
|
||||
modelOptions,
|
||||
subscriptionStatus,
|
||||
canAccessModel,
|
||||
refreshCustomModels,
|
||||
disabled = false,
|
||||
className,
|
||||
}: ChatSettingsDropdownProps) {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
// Check if custom agents feature is enabled
|
||||
const { enabled: customAgentsEnabled, loading: flagsLoading } = useFeatureFlag('custom_agents');
|
||||
|
||||
// Fetch real agents from API only if feature is enabled
|
||||
const { data: agentsResponse, isLoading: agentsLoading, refetch: loadAgents } = useAgents({
|
||||
limit: 100,
|
||||
sort_by: 'name',
|
||||
sort_order: 'asc'
|
||||
});
|
||||
|
||||
const agents = (customAgentsEnabled && agentsResponse?.agents) || [];
|
||||
const defaultAgent = agents.find(agent => agent.is_default);
|
||||
|
||||
// Find selected agent - could be from real agents or predefined
|
||||
const selectedRealAgent = agents.find(a => a.agent_id === selectedAgentId);
|
||||
const selectedPredefinedAgent = PREDEFINED_AGENTS.find(a => a.id === selectedAgentId);
|
||||
const selectedAgent = selectedRealAgent || selectedPredefinedAgent;
|
||||
|
||||
const handleAgentSelect = (agentId: string | undefined) => {
|
||||
onAgentSelect?.(agentId);
|
||||
setDropdownOpen(false);
|
||||
};
|
||||
|
||||
const handleMoreOptions = () => {
|
||||
setDropdownOpen(false);
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleExploreAll = () => {
|
||||
setDropdownOpen(false);
|
||||
router.push('/agents');
|
||||
};
|
||||
|
||||
const getAgentDisplay = () => {
|
||||
if (selectedRealAgent) {
|
||||
return {
|
||||
name: selectedRealAgent.name,
|
||||
icon: <Bot className="h-4 w-4" />,
|
||||
avatar: selectedRealAgent.avatar
|
||||
};
|
||||
}
|
||||
if (selectedPredefinedAgent) {
|
||||
return {
|
||||
name: selectedPredefinedAgent.name,
|
||||
icon: selectedPredefinedAgent.icon,
|
||||
avatar: null
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: 'Suna',
|
||||
icon: <User className="h-4 w-4" />,
|
||||
avatar: null
|
||||
};
|
||||
};
|
||||
|
||||
const agentDisplay = getAgentDisplay();
|
||||
|
||||
const AgentCard = ({ agent, isSelected, onClick, type }: {
|
||||
agent: any;
|
||||
isSelected: boolean;
|
||||
onClick: () => void;
|
||||
type: 'predefined' | 'custom' | 'default';
|
||||
}) => (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"group relative rounded-lg p-1.5 cursor-pointer transition-all",
|
||||
"hover:bg-accent/50",
|
||||
isSelected ? "bg-accent border-accent-foreground/50" : ""
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex-shrink-0">
|
||||
{type === 'default' ? (
|
||||
<User className="h-4 w-4 mt-1" />
|
||||
) : type === 'custom' ? (
|
||||
agent.avatar
|
||||
) : (
|
||||
agent.icon
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="font-medium text-sm truncate">{agent.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
'h-8 w-8 p-0 text-muted-foreground hover:text-foreground relative',
|
||||
'rounded-lg',
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
{selectedAgentId && (
|
||||
<div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" />
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<p>
|
||||
{agentDisplay.name}
|
||||
{agentDisplay.avatar && ` ${agentDisplay.avatar}`}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<PopoverContent align="end" className="w-[480px] p-0" sideOffset={4}>
|
||||
<div className="p-4">
|
||||
<div className="mb-4">
|
||||
<h3 className="text-sm font-medium">Choose Your Agent</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Label className="text-xs text-muted-foreground font-medium">Default Agents</Label>
|
||||
</div>
|
||||
<AgentCard
|
||||
agent={{ name: 'Suna', description: 'Your personal AI assistant' }}
|
||||
isSelected={!selectedAgentId}
|
||||
onClick={() => handleAgentSelect(undefined)}
|
||||
type="default"
|
||||
/>
|
||||
<div className="space-y-1 max-h-[300px] overflow-y-auto">
|
||||
{PREDEFINED_AGENTS.map((agent) => (
|
||||
<AgentCard
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
isSelected={selectedAgentId === agent.id}
|
||||
onClick={() => handleAgentSelect(agent.id)}
|
||||
type="predefined"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Label className="text-xs text-muted-foreground font-medium">Your Agents</Label>
|
||||
</div>
|
||||
{agentsLoading ? (
|
||||
<div className="flex items-center justify-center py-8 text-sm text-muted-foreground">
|
||||
Loading...
|
||||
</div>
|
||||
) : agents.length > 0 ? (
|
||||
<div className="space-y-1 max-h-[300px] overflow-y-auto">
|
||||
{agents.map((agent) => (
|
||||
<AgentCard
|
||||
key={agent.agent_id}
|
||||
agent={agent}
|
||||
isSelected={selectedAgentId === agent.agent_id}
|
||||
onClick={() => handleAgentSelect(agent.agent_id)}
|
||||
type="custom"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||
<Bot className="h-8 w-8 text-muted-foreground/50 mb-2" />
|
||||
<p className="text-sm text-muted-foreground mb-2">No custom agents yet</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleExploreAll}
|
||||
className="text-xs"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
Get Started
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleExploreAll}
|
||||
className="text-xs"
|
||||
>
|
||||
<Search className="h-3 w-3" />
|
||||
Explore All
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleMoreOptions}
|
||||
className="text-xs"
|
||||
>
|
||||
<Settings className="h-3 w-3" />
|
||||
More Options
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<ChatSettingsDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -261,7 +261,6 @@ export const FileUploadHandler = forwardRef<
|
|||
) : (
|
||||
<Paperclip className="h-4 w-4" />
|
||||
)}
|
||||
<span className="text-sm sm:block hidden">Attachments</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
|
|
|
@ -7,8 +7,10 @@ import { UploadedFile } from './chat-input';
|
|||
import { FileUploadHandler } from './file-upload-handler';
|
||||
import { VoiceRecorder } from './voice-recorder';
|
||||
import { ModelSelector } from './model-selector';
|
||||
import { ChatSettingsDropdown } from './chat-settings-dropdown';
|
||||
import { SubscriptionStatus } from './_use-model-selection';
|
||||
import { isLocalMode } from '@/lib/config';
|
||||
import { useFeatureFlag } from '@/lib/feature-flags';
|
||||
import { TooltipContent } from '@/components/ui/tooltip';
|
||||
import { Tooltip } from '@/components/ui/tooltip';
|
||||
import { TooltipProvider, TooltipTrigger } from '@radix-ui/react-tooltip';
|
||||
|
@ -41,6 +43,8 @@ interface MessageInputProps {
|
|||
subscriptionStatus: SubscriptionStatus;
|
||||
canAccessModel: (modelId: string) => boolean;
|
||||
refreshCustomModels?: () => void;
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: (agentId: string | undefined) => void;
|
||||
}
|
||||
|
||||
export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
|
||||
|
@ -73,9 +77,14 @@ export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
|
|||
subscriptionStatus,
|
||||
canAccessModel,
|
||||
refreshCustomModels,
|
||||
|
||||
selectedAgentId,
|
||||
onAgentSelect,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const { enabled: customAgentsEnabled, loading: flagsLoading } = useFeatureFlag('custom_agents');
|
||||
|
||||
useEffect(() => {
|
||||
const textarea = ref as React.RefObject<HTMLTextAreaElement>;
|
||||
if (!textarea.current) return;
|
||||
|
@ -147,17 +156,14 @@ export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
|
|||
messages={messages}
|
||||
/>
|
||||
)}
|
||||
<VoiceRecorder
|
||||
onTranscription={onTranscription}
|
||||
disabled={loading || (disabled && !isAgentRunning)}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{subscriptionStatus === 'no_subscription' && !isLocalMode() &&
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<p className='text-sm text-amber-500 hidden sm:block'>Upgrade for full performance</p>
|
||||
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>The free tier is severely limited by inferior models; upgrade to experience the true full Suna experience.</p>
|
||||
|
@ -165,15 +171,37 @@ export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
|
|||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
}
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
{/* Show model selector inline if custom agents are disabled, otherwise show settings dropdown */}
|
||||
{!customAgentsEnabled || flagsLoading ? (
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
/>
|
||||
) : (
|
||||
<ChatSettingsDropdown
|
||||
selectedAgentId={selectedAgentId}
|
||||
onAgentSelect={onAgentSelect}
|
||||
selectedModel={selectedModel}
|
||||
onModelChange={onModelChange}
|
||||
modelOptions={modelOptions}
|
||||
subscriptionStatus={subscriptionStatus}
|
||||
canAccessModel={canAccessModel}
|
||||
refreshCustomModels={refreshCustomModels}
|
||||
disabled={loading || (disabled && !isAgentRunning)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<VoiceRecorder
|
||||
onTranscription={onTranscription}
|
||||
disabled={loading || (disabled && !isAgentRunning)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={isAgentRunning && onStopAgent ? onStopAgent : onSubmit}
|
||||
|
|
|
@ -46,6 +46,7 @@ interface ModelSelectorProps {
|
|||
canAccessModel: (modelId: string) => boolean;
|
||||
subscriptionStatus: SubscriptionStatus;
|
||||
refreshCustomModels?: () => void;
|
||||
hasBorder?: boolean;
|
||||
}
|
||||
|
||||
export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
||||
|
@ -55,6 +56,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||
canAccessModel,
|
||||
subscriptionStatus,
|
||||
refreshCustomModels,
|
||||
hasBorder = false,
|
||||
}) => {
|
||||
const [paywallOpen, setPaywallOpen] = useState(false);
|
||||
const [billingModalOpen, setBillingModalOpen] = useState(false);
|
||||
|
@ -508,7 +510,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
variant={hasBorder ? "outline" : "ghost"}
|
||||
size="default"
|
||||
className="h-8 rounded-lg text-muted-foreground shadow-none border-none focus:ring-0 px-3"
|
||||
>
|
||||
|
|
Loading…
Reference in New Issue