Enhance layout

This commit is contained in:
decolua committed 2026-01-31 12:58:04 +07:00
1 parent 8c37b39eed
commit 8897df5036
23 files changed
+458 -326

No files matched your search

+89 -87
View File
@@ -120,12 +120,13 @@ export default function CombosPage() {
{combos.length === 0 ? (
<Card>
<div className="text-center py-12">
<span className="material-symbols-outlined text-5xl text-text-muted mb-3 block">
layers
</span>
<p className="text-text-muted mb-4">No combos yet</p>
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 text-primary mb-4">
<span className="material-symbols-outlined text-[32px]">layers</span>
</div>
<p className="text-text-main font-medium mb-1">No combos yet</p>
<p className="text-sm text-text-muted mb-4">Create model combos with fallback support</p>
<Button icon="add" onClick={() => setShowCreateModal(true)}>
Create your first combo
Create Combo
</Button>
</div>
</Card>
@@ -168,62 +169,57 @@ export default function CombosPage() {
function ComboCard({ combo, copied, onCopy, onEdit, onDelete }) {
return (
<Card>
<div className="flex items-start justify-between">
<div className="flex-1">
{/* Name + Copy */}
<div className="flex items-center gap-2 mb-3">
<span className="material-symbols-outlined text-primary">layers</span>
<code className="text-lg font-semibold font-mono">{combo.name}</code>
<button
onClick={() => onCopy(combo.name, `combo-${combo.id}`)}
className="p-1 hover:bg-sidebar rounded text-text-muted hover:text-primary"
title="Copy combo name"
>
<span className="material-symbols-outlined text-sm">
{copied === `combo-${combo.id}` ? "check" : "content_copy"}
</span>
</button>
<Card padding="sm" className="group">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3 flex-1 min-w-0">
<div className="size-8 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
<span className="material-symbols-outlined text-primary text-[18px]">layers</span>
</div>
{/* Models list */}
<div className="flex flex-col gap-1.5">
{combo.models.length === 0 ? (
<p className="text-sm text-text-muted italic">No models added</p>
) : (
combo.models.map((model, index) => (
<div key={index} className="flex items-center gap-2">
<span className="text-xs text-text-muted w-5">{index + 1}.</span>
<code className="text-sm font-mono bg-sidebar px-2 py-0.5 rounded">
{model}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<code className="text-sm font-medium font-mono truncate">{combo.name}</code>
<button
onClick={(e) => { e.stopPropagation(); onCopy(combo.name, `combo-${combo.id}`); }}
className="p-0.5 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary transition-colors opacity-0 group-hover:opacity-100"
title="Copy combo name"
>
<span className="material-symbols-outlined text-[14px]">
{copied === `combo-${combo.id}` ? "check" : "content_copy"}
</span>
</button>
</div>
<div className="flex items-center gap-1 mt-0.5 flex-wrap">
{combo.models.length === 0 ? (
<span className="text-xs text-text-muted italic">No models</span>
) : (
combo.models.slice(0, 3).map((model, index) => (
<code key={index} className="text-[10px] font-mono bg-black/5 dark:bg-white/5 px-1.5 py-0.5 rounded text-text-muted">
{model.split("/").pop()}
</code>
{index === 0 && (
<span className="text-xs text-primary font-medium">Primary</span>
)}
{index > 0 && (
<span className="text-xs text-text-muted">Fallback</span>
)}
</div>
))
)}
))
)}
{combo.models.length > 3 && (
<span className="text-[10px] text-text-muted">+{combo.models.length - 3} more</span>
)}
</div>
</div>
</div>
{/* Actions */}
<div className="flex gap-1">
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<button
onClick={onEdit}
className="p-2 hover:bg-sidebar rounded text-text-muted hover:text-primary"
className="p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded text-text-muted hover:text-primary transition-colors"
title="Edit"
>
<span className="material-symbols-outlined text-lg">edit</span>
<span className="material-symbols-outlined text-[16px]">edit</span>
</button>
<button
onClick={onDelete}
className="p-2 hover:bg-red-50 rounded text-red-500"
className="p-1.5 hover:bg-red-500/10 rounded text-red-500 transition-colors"
title="Delete"
>
<span className="material-symbols-outlined text-lg">delete</span>
<span className="material-symbols-outlined text-[16px]">delete</span>
</button>
</div>
</div>
@@ -321,9 +317,8 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders }) {
isOpen={isOpen}
onClose={onClose}
title={isEdit ? "Edit Combo" : "Create Combo"}
size="md"
>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
{/* Name */}
<div>
<Input
@@ -333,87 +328,94 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders }) {
placeholder="my-combo"
error={nameError}
/>
<p className="text-xs text-text-muted mt-1">
<p className="text-[10px] text-text-muted mt-0.5">
Only letters, numbers, - and _ allowed
</p>
</div>
{/* Models */}
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-sm font-medium">Models</label>
<Button
size="sm"
variant="secondary"
icon="add"
onClick={() => setShowModelSelect(true)}
>
Add Model
</Button>
</div>
<label className="text-sm font-medium mb-1.5 block">Models</label>
{models.length === 0 ? (
<div className="text-center py-6 border border-dashed border-border rounded-lg">
<p className="text-sm text-text-muted">No models added</p>
<p className="text-xs text-text-muted mt-1">Click &quot;Add Model&quot; to add</p>
<div className="text-center py-4 border border-dashed border-black/10 dark:border-white/10 rounded-lg bg-black/[0.01] dark:bg-white/[0.01]">
<span className="material-symbols-outlined text-text-muted text-xl mb-1">layers</span>
<p className="text-xs text-text-muted">No models added yet</p>
</div>
) : (
<div className="flex flex-col gap-2 max-h-[240px] overflow-y-auto">
<div className="flex flex-col gap-1 max-h-[200px] overflow-y-auto">
{models.map((model, index) => (
<div
key={index}
className="flex items-center gap-2"
className="group flex items-center gap-1.5 px-2 py-1 rounded-md bg-black/[0.02] dark:bg-white/[0.02] hover:bg-black/[0.04] dark:hover:bg-white/[0.04] transition-colors"
>
{/* Priority arrows */}
<div className="flex flex-col gap-0">
{/* Index badge */}
<span className="text-[10px] font-medium text-text-muted w-3 text-center shrink-0">{index + 1}</span>
{/* Model Input */}
<input
type="text"
value={model}
onChange={(e) => handleModelChange(index, e.target.value)}
placeholder="provider/model"
className="flex-1 min-w-0 px-1.5 py-0.5 text-xs font-mono bg-transparent border-0 focus:outline-none text-text-main placeholder:text-text-muted/50"
/>
{/* Priority arrows - horizontal, always visible */}
<div className="flex items-center gap-0.5">
<button
onClick={() => handleMoveUp(index)}
disabled={index === 0}
className={`p-0.5 rounded ${index === 0 ? "text-text-muted/30" : "hover:bg-surface text-text-muted hover:text-primary"}`}
className={`p-0.5 rounded ${index === 0 ? "text-text-muted/20 cursor-not-allowed" : "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"}`}
title="Move up"
>
<span className="material-symbols-outlined text-sm leading-none">keyboard_arrow_up</span>
<span className="material-symbols-outlined text-[12px]">arrow_upward</span>
</button>
<button
onClick={() => handleMoveDown(index)}
disabled={index === models.length - 1}
className={`p-0.5 rounded ${index === models.length - 1 ? "text-text-muted/30" : "hover:bg-surface text-text-muted hover:text-primary"}`}
className={`p-0.5 rounded ${index === models.length - 1 ? "text-text-muted/20 cursor-not-allowed" : "text-text-muted hover:text-primary hover:bg-black/5 dark:hover:bg-white/5"}`}
title="Move down"
>
<span className="material-symbols-outlined text-sm leading-none">keyboard_arrow_down</span>
<span className="material-symbols-outlined text-[12px]">arrow_downward</span>
</button>
</div>
{/* Model Input */}
<Input
value={model}
onChange={(e) => handleModelChange(index, e.target.value)}
placeholder="model-name"
className="flex-1"
/>
{/* Remove */}
{/* Remove - always visible */}
<button
onClick={() => handleRemoveModel(index)}
className="p-2 hover:bg-red-50 rounded text-red-500"
className="p-0.5 hover:bg-red-500/10 rounded text-text-muted hover:text-red-500 transition-all"
title="Remove"
>
<span className="material-symbols-outlined text-sm">close</span>
<span className="material-symbols-outlined text-[12px]">close</span>
</button>
</div>
))}
</div>
)}
{/* Add Model button - moved to bottom */}
<button
onClick={() => setShowModelSelect(true)}
className="w-full mt-2 py-2 border border-dashed border-black/10 dark:border-white/10 rounded-lg text-xs text-text-muted hover:text-primary hover:border-primary/30 transition-colors flex items-center justify-center gap-1"
>
<span className="material-symbols-outlined text-[16px]">add</span>
Add Model
</button>
</div>
{/* Actions */}
<div className="flex gap-2 pt-2">
<div className="flex gap-2 pt-1">
<Button onClick={onClose} variant="ghost" fullWidth size="sm">
Cancel
</Button>
<Button
onClick={handleSave}
fullWidth
size="sm"
disabled={!name.trim() || !!nameError || saving}
>
{saving ? "Saving..." : "Apply"}
</Button>
<Button onClick={onClose} variant="ghost" fullWidth>
Cancel
{saving ? "Saving..." : isEdit ? "Save" : "Create"}
</Button>
</div>
</div>