fix(ui): move chat controls to header, add new session, fix settings layout
Chat page: - Move session selector, refresh, thinking toggle to the Header bar (same level as "Chat" title) instead of inside the chat content area - Add "New Session" button (+ icon) to create fresh chat sessions - Remove duplicate toolbar from chat body Settings page: - Remove max-w-2xl constraint so cards fill available width - Redesign provider cards: compact layout with key + actions in one row - Shorten API key display (sk-...df67 format instead of full masked key) - Move edit/delete/star buttons inside the key row background area - Remove duplicate "AI Providers" heading (already in card header)
This commit is contained in:
@@ -101,15 +101,9 @@ export function ProvidersSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex justify-end">
|
||||||
<div>
|
<Button size="sm" onClick={() => setShowAddDialog(true)}>
|
||||||
<h3 className="text-lg font-medium">AI Providers</h3>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Configure your AI model providers and API keys
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button onClick={() => setShowAddDialog(true)}>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Add Provider
|
Add Provider
|
||||||
</Button>
|
</Button>
|
||||||
@@ -180,6 +174,21 @@ interface ProviderCardProps {
|
|||||||
onValidateKey: (key: string) => Promise<{ valid: boolean; error?: string }>;
|
onValidateKey: (key: string) => Promise<{ valid: boolean; error?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorten a masked key to a more readable format.
|
||||||
|
* e.g. "sk-or-v1-a20a****df67" -> "sk-...df67"
|
||||||
|
*/
|
||||||
|
function shortenKeyDisplay(masked: string | null): string {
|
||||||
|
if (!masked) return 'No key';
|
||||||
|
// Show first 4 chars + last 4 chars
|
||||||
|
if (masked.length > 12) {
|
||||||
|
const prefix = masked.substring(0, 4);
|
||||||
|
const suffix = masked.substring(masked.length - 4);
|
||||||
|
return `${prefix}...${suffix}`;
|
||||||
|
}
|
||||||
|
return masked;
|
||||||
|
}
|
||||||
|
|
||||||
function ProviderCard({
|
function ProviderCard({
|
||||||
provider,
|
provider,
|
||||||
isDefault,
|
isDefault,
|
||||||
@@ -225,33 +234,30 @@ function ProviderCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={cn(isDefault && 'ring-2 ring-primary')}>
|
<Card className={cn(isDefault && 'ring-2 ring-primary')}>
|
||||||
<CardHeader className="pb-3">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-start justify-between">
|
{/* Top row: icon + name + toggle */}
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-2xl">{typeInfo?.icon || '⚙️'}</span>
|
<span className="text-xl">{typeInfo?.icon || '⚙️'}</span>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CardTitle className="text-lg">{provider.name}</CardTitle>
|
<span className="font-semibold">{provider.name}</span>
|
||||||
{isDefault && (
|
{isDefault && (
|
||||||
<Badge variant="default" className="text-xs">Default</Badge>
|
<Badge variant="default" className="text-xs">Default</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<CardDescription className="capitalize">{provider.type}</CardDescription>
|
<span className="text-xs text-muted-foreground capitalize">{provider.type}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Switch
|
<Switch
|
||||||
checked={provider.enabled}
|
checked={provider.enabled}
|
||||||
onCheckedChange={onToggleEnabled}
|
onCheckedChange={onToggleEnabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</CardHeader>
|
{/* Key row */}
|
||||||
<CardContent>
|
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>API Key</Label>
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<Input
|
<Input
|
||||||
@@ -259,55 +265,55 @@ function ProviderCard({
|
|||||||
placeholder={typeInfo?.placeholder}
|
placeholder={typeInfo?.placeholder}
|
||||||
value={newKey}
|
value={newKey}
|
||||||
onChange={(e) => setNewKey(e.target.value)}
|
onChange={(e) => setNewKey(e.target.value)}
|
||||||
className="pr-10"
|
className="pr-10 h-9 text-sm"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowKey(!showKey)}
|
onClick={() => setShowKey(!showKey)}
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
{showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
{showKey ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
onClick={handleSaveKey}
|
onClick={handleSaveKey}
|
||||||
disabled={!newKey || validating || saving}
|
disabled={!newKey || validating || saving}
|
||||||
>
|
>
|
||||||
{validating || saving ? (
|
{validating || saving ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Check className="h-4 w-4" />
|
<Check className="h-3.5 w-3.5" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" onClick={onCancelEdit}>
|
<Button variant="ghost" size="sm" onClick={onCancelEdit}>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between rounded-md bg-muted/50 px-3 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<Key className="h-4 w-4 text-muted-foreground" />
|
<Key className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
<span className="text-sm font-mono">
|
<span className="text-sm font-mono text-muted-foreground truncate">
|
||||||
{provider.hasKey ? provider.keyMasked : 'No API key set'}
|
{provider.hasKey ? shortenKeyDisplay(provider.keyMasked) : 'No API key set'}
|
||||||
</span>
|
</span>
|
||||||
{provider.hasKey && (
|
{provider.hasKey && (
|
||||||
<Badge variant="secondary" className="text-xs">Configured</Badge>
|
<Badge variant="secondary" className="text-xs shrink-0">Configured</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-0.5 shrink-0 ml-2">
|
||||||
{!isDefault && (
|
{!isDefault && (
|
||||||
<Button variant="ghost" size="icon" onClick={onSetDefault} title="Set as default">
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={onSetDefault} title="Set as default">
|
||||||
<Star className="h-4 w-4" />
|
<Star className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button variant="ghost" size="icon" onClick={onEdit} title="Edit API key">
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={onEdit} title="Edit API key">
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="icon" onClick={onDelete} title="Delete provider">
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={onDelete} title="Delete provider">
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function Settings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 max-w-2xl">
|
<div className="space-y-6 p-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">Settings</h1>
|
<h1 className="text-2xl font-bold">Settings</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user