Merge pull request #345 from escapade-mckv/feat/ux

chore(ui): improvements in model selector
This commit is contained in:
Marko Kraemer 2025-05-17 14:58:15 +02:00 committed by GitHub
commit fab1b090fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 251 additions and 135 deletions

View File

@ -35,26 +35,32 @@ REDIS_RESPONSE_LIST_TTL = 3600 * 24
MODEL_NAME_ALIASES = {
# Short names to full names
"sonnet-3.7": "anthropic/claude-3-7-sonnet-latest",
"gpt-4.1": "openai/gpt-4.1-2025-04-14",
# "gpt-4.1": "openai/gpt-4.1-2025-04-14", # Commented out in constants.py
"gpt-4o": "openai/gpt-4o",
"gpt-4-turbo": "openai/gpt-4-turbo",
"gpt-4": "openai/gpt-4",
"gemini-flash-2.5": "openrouter/google/gemini-2.5-flash-preview",
"grok-3": "xai/grok-3-fast-latest",
# "gpt-4-turbo": "openai/gpt-4-turbo", # Commented out in constants.py
# "gpt-4": "openai/gpt-4", # Commented out in constants.py
# "gemini-flash-2.5": "openrouter/google/gemini-2.5-flash-preview", # Commented out in constants.py
# "grok-3": "xai/grok-3-fast-latest", # Commented out in constants.py
"deepseek": "openrouter/deepseek/deepseek-chat",
"grok-3-mini": "xai/grok-3-mini-fast-beta",
"qwen3": "openrouter/qwen/qwen3-235b-a22b",
# "deepseek-r1": "openrouter/deepseek/deepseek-r1",
# "grok-3-mini": "xai/grok-3-mini-fast-beta", # Commented out in constants.py
"qwen3": "openrouter/qwen/qwen3-235b-a22b", # Commented out in constants.py
# Also include full names as keys to ensure they map to themselves
"anthropic/claude-3-7-sonnet-latest": "anthropic/claude-3-7-sonnet-latest",
"openai/gpt-4.1-2025-04-14": "openai/gpt-4.1-2025-04-14",
# "openai/gpt-4.1-2025-04-14": "openai/gpt-4.1-2025-04-14", # Commented out in constants.py
"openai/gpt-4o": "openai/gpt-4o",
"openai/gpt-4-turbo": "openai/gpt-4-turbo",
"openai/gpt-4": "openai/gpt-4",
"openrouter/google/gemini-2.5-flash-preview": "openrouter/google/gemini-2.5-flash-preview",
"xai/grok-3-fast-latest": "xai/grok-3-fast-latest",
# "openai/gpt-4-turbo": "openai/gpt-4-turbo", # Commented out in constants.py
# "openai/gpt-4": "openai/gpt-4", # Commented out in constants.py
# "openrouter/google/gemini-2.5-flash-preview": "openrouter/google/gemini-2.5-flash-preview", # Commented out in constants.py
# "xai/grok-3-fast-latest": "xai/grok-3-fast-latest", # Commented out in constants.py
"deepseek/deepseek-chat": "openrouter/deepseek/deepseek-chat",
"xai/grok-3-mini-fast-beta": "xai/grok-3-mini-fast-beta",
# "deepseek/deepseek-r1": "openrouter/deepseek/deepseek-r1",
"qwen/qwen3-235b-a22b": "openrouter/qwen/qwen3-235b-a22b",
# "xai/grok-3-mini-fast-beta": "xai/grok-3-mini-fast-beta", # Commented out in constants.py
}
class AgentStartRequest(BaseModel):

View File

@ -23,26 +23,32 @@ router = APIRouter(prefix="/billing", tags=["billing"])
MODEL_NAME_ALIASES = {
# Short names to full names
"sonnet-3.7": "anthropic/claude-3-7-sonnet-latest",
"gpt-4.1": "openai/gpt-4.1-2025-04-14",
# "gpt-4.1": "openai/gpt-4.1-2025-04-14", # Commented out in constants.py
"gpt-4o": "openai/gpt-4o",
"gpt-4-turbo": "openai/gpt-4-turbo",
"gpt-4": "openai/gpt-4",
"gemini-flash-2.5": "openrouter/google/gemini-2.5-flash-preview",
"grok-3": "xai/grok-3-fast-latest",
# "gpt-4-turbo": "openai/gpt-4-turbo", # Commented out in constants.py
# "gpt-4": "openai/gpt-4", # Commented out in constants.py
# "gemini-flash-2.5": "openrouter/google/gemini-2.5-flash-preview", # Commented out in constants.py
# "grok-3": "xai/grok-3-fast-latest", # Commented out in constants.py
"deepseek": "openrouter/deepseek/deepseek-chat",
"grok-3-mini": "xai/grok-3-mini-fast-beta",
"qwen3": "openrouter/qwen/qwen3-235b-a22b",
# "deepseek-r1": "openrouter/deepseek/deepseek-r1",
# "grok-3-mini": "xai/grok-3-mini-fast-beta", # Commented out in constants.py
"qwen3": "openrouter/qwen/qwen3-235b-a22b", # Commented out in constants.py
# Also include full names as keys to ensure they map to themselves
"anthropic/claude-3-7-sonnet-latest": "anthropic/claude-3-7-sonnet-latest",
"openai/gpt-4.1-2025-04-14": "openai/gpt-4.1-2025-04-14",
# "openai/gpt-4.1-2025-04-14": "openai/gpt-4.1-2025-04-14", # Commented out in constants.py
"openai/gpt-4o": "openai/gpt-4o",
"openai/gpt-4-turbo": "openai/gpt-4-turbo",
"openai/gpt-4": "openai/gpt-4",
"openrouter/google/gemini-2.5-flash-preview": "openrouter/google/gemini-2.5-flash-preview",
"xai/grok-3-fast-latest": "xai/grok-3-fast-latest",
# "openai/gpt-4-turbo": "openai/gpt-4-turbo", # Commented out in constants.py
# "openai/gpt-4": "openai/gpt-4", # Commented out in constants.py
# "openrouter/google/gemini-2.5-flash-preview": "openrouter/google/gemini-2.5-flash-preview", # Commented out in constants.py
# "xai/grok-3-fast-latest": "xai/grok-3-fast-latest", # Commented out in constants.py
"deepseek/deepseek-chat": "openrouter/deepseek/deepseek-chat",
"xai/grok-3-mini-fast-beta": "xai/grok-3-mini-fast-beta",
# "deepseek/deepseek-r1": "openrouter/deepseek/deepseek-r1",
"qwen/qwen3-235b-a22b": "openrouter/qwen/qwen3-235b-a22b",
# "xai/grok-3-mini-fast-beta": "xai/grok-3-mini-fast-beta", # Commented out in constants.py
}
SUBSCRIPTION_TIERS = {

View File

@ -1,6 +1,7 @@
MODEL_ACCESS_TIERS = {
"free": [
"openrouter/deepseek/deepseek-chat",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_2_20": [
"openrouter/deepseek/deepseek-chat",
@ -12,6 +13,8 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_6_50": [
"openrouter/deepseek/deepseek-chat",
@ -23,6 +26,8 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_12_100": [
"openrouter/deepseek/deepseek-chat",
@ -34,6 +39,8 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_25_200": [
"openrouter/deepseek/deepseek-chat",
@ -45,6 +52,8 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_50_400": [
"openrouter/deepseek/deepseek-chat",
@ -56,6 +65,8 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_125_800": [
"openrouter/deepseek/deepseek-chat",
@ -67,6 +78,8 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
"tier_200_1000": [
"openrouter/deepseek/deepseek-chat",
@ -78,5 +91,7 @@ MODEL_ACCESS_TIERS = {
# "openai/gpt-4",
"anthropic/claude-3-7-sonnet-latest",
# "openai/gpt-4.1-2025-04-14",
# "openrouter/deepseek/deepseek-r1",
"openrouter/qwen/qwen3-235b-a22b",
],
}

View File

@ -396,28 +396,34 @@ export function NavAgents() {
<>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={toggleMultiSelect}
className="h-7 w-7"
disabled={combinedThreads.length === 0}
>
<Checkbox className="h-4 w-4" />
<span className="sr-only">Select Multiple</span>
</Button>
<div>
<Button
variant="ghost"
size="icon"
onClick={toggleMultiSelect}
className="h-7 w-7"
disabled={combinedThreads.length === 0}
>
<div className="h-4 w-4 border rounded border-foreground/30 flex items-center justify-center">
{isMultiSelectActive && <Check className="h-3 w-3" />}
</div>
<span className="sr-only">Select</span>
</Button>
</div>
</TooltipTrigger>
<TooltipContent>Select Multiple</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Link
href="/dashboard"
className="text-muted-foreground hover:text-foreground h-7 w-7 flex items-center justify-center rounded-md"
>
<Plus className="h-4 w-4" />
<span className="sr-only">New Agent</span>
</Link>
<div>
<Link
href="/dashboard"
className="text-muted-foreground hover:text-foreground h-7 w-7 flex items-center justify-center rounded-md"
>
<Plus className="h-4 w-4" />
<span className="sr-only">New Agent</span>
</Link>
</div>
</TooltipTrigger>
<TooltipContent>New Agent</TooltipContent>
</Tooltip>
@ -432,12 +438,14 @@ export function NavAgents() {
<SidebarMenuItem>
<Tooltip>
<TooltipTrigger asChild>
<SidebarMenuButton asChild>
<Link href="/dashboard" className="flex items-center">
<Plus className="h-4 w-4" />
<span>New Agent</span>
</Link>
</SidebarMenuButton>
<div>
<SidebarMenuButton asChild>
<Link href="/dashboard" className="flex items-center">
<Plus className="h-4 w-4" />
<span>New Agent</span>
</Link>
</SidebarMenuButton>
</div>
</TooltipTrigger>
<TooltipContent>New Agent</TooltipContent>
</Tooltip>
@ -468,32 +476,35 @@ export function NavAgents() {
{state === 'collapsed' ? (
<Tooltip>
<TooltipTrigger asChild>
<SidebarMenuButton
asChild
className={
isActive ? 'bg-accent text-accent-foreground' :
isSelected ? 'bg-primary/10' : ''
}
>
<Link
href={thread.url}
onClick={(e) =>
handleThreadClick(e, thread.threadId, thread.url)
<div>
<SidebarMenuButton
asChild
className={
isActive ? 'bg-accent text-accent-foreground' :
isSelected ? 'bg-primary/10' : ''
}
>
{isMultiSelectActive ? (
<Checkbox
checked={isSelected}
className="h-4 w-4"
/>
) : isThreadLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<MessagesSquare className="h-4 w-4" />
)}
<span>{thread.projectName}</span>
</Link>
</SidebarMenuButton>
<Link
href={thread.url}
onClick={(e) =>
handleThreadClick(e, thread.threadId, thread.url)
}
>
{isMultiSelectActive ? (
<div
className={`h-4 w-4 border rounded flex items-center justify-center ${isSelected ? 'bg-primary border-primary' : 'border-foreground'}`}
>
{isSelected && <Check className="h-3 w-3 text-white" />}
</div>
) : isThreadLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<MessagesSquare className="h-4 w-4" />
)}
<span>{thread.projectName}</span>
</Link>
</SidebarMenuButton>
</div>
</TooltipTrigger>
<TooltipContent>{thread.projectName}</TooltipContent>
</Tooltip>
@ -515,17 +526,18 @@ export function NavAgents() {
}
className="flex items-center"
>
{isMultiSelectActive ? (
<Checkbox
checked={isSelected}
className="h-4 w-4 mr-2"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleThreadSelection(thread.threadId);
}}
/>
) : null}
{isMultiSelectActive ? (
<div
className={`h-4 w-4 border flex-shrink-0 hover:bg-muted transition rounded mr-2 flex items-center justify-center ${isSelected ? 'bg-primary border-primary' : 'border-foreground/30'}`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
toggleThreadSelection(thread.threadId);
}}
>
{isSelected && <Check className="h-3 w-3 text-white" />}
</div>
) : null}
{isThreadLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
@ -585,7 +597,6 @@ export function NavAgents() {
})}
</>
) : (
// Empty state
<SidebarMenuItem>
<SidebarMenuButton className="text-sidebar-foreground/70">
<MessagesSquare className="h-4 w-4" />
@ -595,7 +606,6 @@ export function NavAgents() {
)}
</SidebarMenu>
{/* Bulk delete progress indicator */}
{(isDeletingSingle || isDeletingMultiple) && totalToDelete > 0 && (
<div className="mt-2 px-2">
<div className="text-xs text-muted-foreground mb-1">

View File

@ -28,6 +28,7 @@ const MODEL_DESCRIPTIONS: Record<string, string> = {
'gemini-flash-2.5': 'Gemini Flash 2.5 - Google\'s fast, responsive AI model',
'grok-3': 'Grok-3 - xAI\'s latest large language model with enhanced capabilities',
'deepseek': 'DeepSeek - Free tier model with good general capabilities',
'deepseek-r1': 'DeepSeek R1 - Advanced model with enhanced reasoning and coding capabilities',
'grok-3-mini': 'Grok-3 Mini - Smaller, faster version of Grok-3 for simpler tasks',
'qwen3': 'Qwen3 - Alibaba\'s powerful multilingual language model'
};
@ -72,7 +73,7 @@ export const useModelSelection = () => {
];
}
const topModels = ['sonnet-3.7', 'gpt-4o', 'gemini-flash-2.5'];
const topModels = ['sonnet-3.7', 'gemini-flash-2.5'];
return modelsData.models.map(model => {
const shortName = model.short_name || model.id;

View File

@ -6,6 +6,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from '@/components/ui/dropdown-menu';
import {
Tooltip,
@ -15,11 +16,14 @@ import {
} from '@/components/ui/tooltip';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
import { Check, ChevronDown, Search } from 'lucide-react';
import { Check, ChevronDown, Search, AlertTriangle, Crown, ArrowUpRight } from 'lucide-react';
import { ModelOption, SubscriptionStatus } from './_use-model-selection';
import { PaywallDialog } from '@/components/payment/paywall-dialog';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
import { useRouter } from 'next/navigation';
const LOW_QUALITY_MODELS = ['deepseek', 'grok-3-mini', 'qwen3'];
interface ModelSelectorProps {
selectedModel: string;
@ -45,12 +49,19 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [autoSelect, setAutoSelect] = useState(initialAutoSelect);
const searchInputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
const filteredOptions = modelOptions.filter((opt) =>
opt.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
opt.id.toLowerCase().includes(searchQuery.toLowerCase())
);
const groupedOptions = {
premium: filteredOptions.filter(model => model.top),
standard: filteredOptions.filter(model => !model.top && !LOW_QUALITY_MODELS.includes(model.id)),
basic: filteredOptions.filter(model => LOW_QUALITY_MODELS.includes(model.id))
};
const getBestAvailableModel = () => {
if (subscriptionStatus === 'active') {
const sonnetModel = modelOptions.find(m => m.id === 'sonnet-3.7');
@ -90,6 +101,8 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
const selectedLabel =
modelOptions.find((o) => o.id === selectedModel)?.label || 'Select model';
const isLowQualitySelected = LOW_QUALITY_MODELS.includes(selectedModel);
const handleSelect = (id: string) => {
if (canAccessModel(id)) {
@ -111,6 +124,10 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
}
};
const handleUpgradeClick = () => {
router.push('/settings/billing');
};
const closeDialog = () => {
setPaywallOpen(false);
setLockedModel(null);
@ -118,7 +135,6 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
const handleSearchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.stopPropagation();
if (e.key === 'ArrowDown') {
e.preventDefault();
setHighlightedIndex((prev) =>
@ -138,6 +154,60 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
}
};
const renderModelOption = (opt: ModelOption, index: number) => {
const accessible = canAccessModel(opt.id);
const isHighlighted = filteredOptions.indexOf(opt) === highlightedIndex;
const isPremium = opt.requiresSubscription;
const isLowQuality = LOW_QUALITY_MODELS.includes(opt.id);
return (
<TooltipProvider key={opt.id}>
<Tooltip>
<TooltipTrigger asChild>
<div className='w-full'>
<DropdownMenuItem
className={cn(
"text-sm px-3 py-2 flex items-center justify-between cursor-pointer",
isHighlighted && "bg-accent",
!accessible && "opacity-70"
)}
onClick={() => handleSelect(opt.id)}
onMouseEnter={() => setHighlightedIndex(filteredOptions.indexOf(opt))}
>
<div className="flex items-center">
<span className="font-medium">{opt.label}</span>
{isLowQuality && (
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 ml-1.5" />
)}
</div>
<div className="flex items-center">
{isPremium && !accessible && (
<span className="text-xs text-purple-500 font-semibold mr-2">MAX</span>
)}
{selectedModel === opt.id && (
<Check className="mr-2 h-4 w-4 text-blue-500" />
)}
{opt.top && (
<Badge className="bg-purple-500/20 text-purple-500 rounded-full">TOP</Badge>
)}
</div>
</DropdownMenuItem>
</div>
</TooltipTrigger>
{!accessible ? (
<TooltipContent side="left" className="text-xs max-w-xs">
<p>Requires subscription to access premium model</p>
</TooltipContent>
) : isLowQuality ? (
<TooltipContent side="left" className="text-xs max-w-xs">
<p>Not recommended for complex tasks</p>
</TooltipContent>
) : null}
</Tooltip>
</TooltipProvider>
);
};
return (
<div className="relative">
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
@ -148,6 +218,18 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
className="h-8 rounded-md text-muted-foreground shadow-none border-none focus:ring-0 px-3"
>
<div className="flex items-center gap-1 text-sm font-medium">
{isLowQualitySelected && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<AlertTriangle className="h-3.5 w-3.5 text-amber-500 mr-1" />
</TooltipTrigger>
<TooltipContent side="bottom" className="text-xs">
<p>Basic model with limited capabilities</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<span>{selectedLabel}</span>
<ChevronDown className="h-3 w-3 opacity-50 ml-1" />
</div>
@ -190,53 +272,49 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
/>
</div>
</div>
<div className="max-h-[280px] overflow-y-auto w-full p-3 scrollbar-hide">
{subscriptionStatus !== 'active' && (
<div className="p-3 bg-primary/10 dark:bg-blue-950/40 border-b border-border">
<div className="flex flex-col space-y-2">
<div className="flex items-center">
<Crown className="h-4 w-4 text-primary mr-2" />
<div>
<p className="text-sm font-medium">Unlock Premium Models</p>
<p className="text-xs text-muted-foreground">Get better results with top-tier AI</p>
</div>
</div>
<Button
size="sm"
className="w-full h-8 font-medium"
onClick={handleUpgradeClick}
>
<span>Upgrade to Pro</span>
<ArrowUpRight className="h-3.5 w-3.5" />
</Button>
</div>
</div>
)}
<div className="max-h-[280px] overflow-y-auto w-full p-1 scrollbar-hide">
{filteredOptions.length > 0 ? (
filteredOptions.map((opt, index) => {
const accessible = canAccessModel(opt.id);
const isHighlighted = index === highlightedIndex;
const isPremium = opt.requiresSubscription;
return (
<TooltipProvider key={opt.id}>
<Tooltip>
<TooltipTrigger asChild>
<div className='w-full'>
<DropdownMenuItem
className={cn(
"text-sm px-3 py-1.5 flex items-center justify-between cursor-pointer",
isHighlighted && "bg-accent",
!accessible && "opacity-70"
)}
onClick={() => handleSelect(opt.id)}
onMouseEnter={() => setHighlightedIndex(index)}
>
<span className="font-medium">{opt.label}</span>
<div className="flex items-center">
{isPremium && !accessible && (
<span className="text-xs text-purple-500 font-semibold mr-2">MAX</span>
)}
{selectedModel === opt.id && (
<Check className="mr-2 h-4 w-4 text-blue-500" />
)}
{opt.top && (
<Badge className="bg-yellow-500/20 text-yellow-500 rounded-full">TOP</Badge>
)}
</div>
</DropdownMenuItem>
</div>
</TooltipTrigger>
{!accessible && (
<TooltipContent side="left" className="text-xs">
<p>Requires subscription to access</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
);
})
<div>
{groupedOptions.premium.length > 0 && (
<div>
{groupedOptions.premium.map(renderModelOption)}
</div>
)}
{groupedOptions.standard.length > 0 && (
<div>
{groupedOptions.standard.map(renderModelOption)}
</div>
)}
{groupedOptions.basic.length > 0 && (
<div>
{groupedOptions.basic.map(renderModelOption)}
</div>
)}
</div>
) : (
<div className="text-sm text-center py-2 text-muted-foreground">
<div className="text-sm text-center py-4 text-muted-foreground">
No models match your search
</div>
)}
@ -256,7 +334,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
? `Subscribe to access ${modelOptions.find(
(m) => m.id === lockedModel
)?.label}`
: 'Subscribe to access premium models'
: 'Subscribe to access premium models with enhanced capabilities'
}
ctaText="Subscribe Now"
cancelText="Maybe Later"