|
47 | 47 | {/if} |
48 | 48 | </svelte:head> |
49 | 49 |
|
50 | | -<div class="scrollbar-custom h-full overflow-y-auto py-12 max-sm:pt-8 md:py-24"> |
| 50 | +<div |
| 51 | + class="scrollbar-custom h-full overflow-y-auto bg-gray-50 py-12 max-sm:pt-8 dark:bg-gray-950 md:py-24" |
| 52 | +> |
51 | 53 | <div class="pt-42 mx-auto flex flex-col px-5 xl:w-[60rem] 2xl:w-[64rem]"> |
52 | 54 | <div class="flex items-center"> |
53 | 55 | <h1 class="text-2xl font-bold">Models</h1> |
|
83 | 85 | /> |
84 | 86 |
|
85 | 87 | <div class="mt-6 min-h-[50vh]"> |
86 | | - <div |
87 | | - class="overflow-hidden rounded-2xl border border-gray-200/60 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900" |
88 | | - > |
| 88 | + <div class="grid grid-cols-1 gap-6 p-1 md:grid-cols-2"> |
89 | 89 | {#each filteredModels as model, index (model.id)} |
90 | 90 | {@const isActive = model.id === $settings.activeModel} |
91 | | - {@const isLast = index === filteredModels.length - 1} |
92 | 91 | <a |
93 | 92 | href="{base}/models/{model.id}" |
94 | 93 | aria-label="Model card for {model.displayName}" |
95 | | - class="group flex cursor-pointer items-center gap-4 p-4 |
| 94 | + class="group relative rounded-3xl border p-6 shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-300 |
96 | 95 | {isActive |
97 | | - ? 'bg-gray-50 dark:bg-gray-800' |
98 | | - : 'bg-white hover:bg-gray-50 dark:bg-gray-900 dark:hover:bg-gray-800'} |
99 | | - {isLast ? '' : 'border-b border-gray-100 dark:border-gray-800'}" |
| 96 | + ? 'border-gray-900/10 bg-white shadow-lg ring-1 ring-black/5 dark:border-gray-700 dark:bg-gray-900 dark:ring-white/10' |
| 97 | + : 'border-transparent bg-white hover:-translate-y-1 hover:border-gray-200 hover:shadow-xl dark:bg-gray-900 dark:hover:border-gray-800'}" |
100 | 98 | > |
101 | | - <!-- Avatar --> |
102 | | - <div class="flex-shrink-0"> |
103 | | - {#if model.logoUrl} |
104 | | - <img |
105 | | - alt={model.displayName} |
106 | | - class="h-10 w-10 rounded-lg border border-gray-100 bg-gray-50 object-cover dark:border-gray-700 dark:bg-gray-100" |
107 | | - src={model.logoUrl} |
108 | | - /> |
109 | | - {:else} |
110 | | - <div |
111 | | - class="h-10 w-10 rounded-lg border border-gray-100 bg-gray-200 dark:border-gray-700 dark:bg-gray-700" |
112 | | - aria-hidden="true" |
113 | | - ></div> |
114 | | - {/if} |
115 | | - </div> |
116 | | - |
117 | | - <!-- Content --> |
118 | | - <div class="min-w-0 flex-1"> |
119 | | - <div class="flex items-center gap-2"> |
120 | | - <h3 |
121 | | - class="truncate font-medium text-gray-900 dark:text-gray-200" |
122 | | - class:font-bold={isActive} |
123 | | - class:dark:text-white={isActive} |
124 | | - > |
125 | | - {model.displayName} |
126 | | - </h3> |
127 | | - {#if index === 0 && model.isRouter && !isActive} |
128 | | - <span |
129 | | - class="rounded border border-gray-200 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-gray-500 dark:border-gray-700 dark:text-gray-400" |
130 | | - > |
131 | | - Default |
132 | | - </span> |
| 99 | + <!-- Header: Avatar + Icons --> |
| 100 | + <div class="mb-4 flex items-start justify-between"> |
| 101 | + <!-- Avatar --> |
| 102 | + <div class="relative"> |
| 103 | + {#if model.logoUrl} |
| 104 | + <img |
| 105 | + alt={model.displayName} |
| 106 | + class="h-12 w-12 rounded-2xl border border-gray-100 bg-white object-cover shadow-sm dark:border-gray-800 dark:bg-gray-800" |
| 107 | + src={model.logoUrl} |
| 108 | + /> |
| 109 | + {:else} |
| 110 | + <div |
| 111 | + class="h-12 w-12 rounded-2xl border border-gray-100 bg-gray-200 shadow-sm dark:border-gray-800 dark:bg-gray-700" |
| 112 | + aria-hidden="true" |
| 113 | + ></div> |
133 | 114 | {/if} |
134 | 115 | </div> |
135 | | - <p class="truncate pr-4 text-[13px] text-gray-500 dark:text-gray-400"> |
136 | | - {model.isRouter |
137 | | - ? "Routes your messages to the best model for your request." |
138 | | - : model.description || "-"} |
139 | | - </p> |
140 | | - </div> |
141 | 116 |
|
142 | | - <!-- Icons and badges --> |
143 | | - <div class="flex flex-shrink-0 items-center gap-1.5"> |
144 | | - <button |
145 | | - type="button" |
146 | | - title="Model settings" |
147 | | - aria-label="Model settings for {model.displayName}" |
148 | | - class="rounded-md border border-gray-200 p-1.5 text-gray-500 hover:bg-gray-100 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700" |
149 | | - onclick={(e) => { |
150 | | - e.preventDefault(); |
151 | | - e.stopPropagation(); |
152 | | - goto(`${base}/settings/${model.id}`); |
153 | | - }} |
154 | | - > |
155 | | - <LucideSettings class="h-3.5 w-3.5" /> |
156 | | - </button> |
157 | | - <div class="flex items-center gap-1.5"> |
| 117 | + <!-- Icons --> |
| 118 | + <div class="flex items-center gap-2"> |
158 | 119 | {#if $settings.toolsOverrides?.[model.id] ?? (model as { supportsTools?: boolean }).supportsTools} |
159 | 120 | <div |
160 | 121 | title="This model supports tool calling (functions)." |
161 | | - class="rounded-md bg-purple-50 p-1.5 text-purple-600 dark:bg-purple-900/20 dark:text-purple-400" |
| 122 | + class="flex h-8 w-8 items-center justify-center rounded-full bg-purple-50 text-purple-600 transition-colors dark:bg-purple-900/20 dark:text-purple-400" |
162 | 123 | > |
163 | 124 | <LucideHammer class="h-3.5 w-3.5" /> |
164 | 125 | </div> |
165 | 126 | {/if} |
166 | 127 | {#if $settings.multimodalOverrides?.[model.id] ?? model.multimodal} |
167 | 128 | <div |
168 | 129 | title="This model is multimodal and supports image inputs natively." |
169 | | - class="rounded-md bg-blue-50 p-1.5 text-blue-600 dark:bg-blue-900/20 dark:text-blue-400" |
| 130 | + class="flex h-8 w-8 items-center justify-center rounded-full bg-blue-50 text-blue-600 transition-colors dark:bg-blue-900/20 dark:text-blue-400" |
170 | 131 | > |
171 | 132 | <LucideImage class="h-3.5 w-3.5" /> |
172 | 133 | </div> |
173 | 134 | {/if} |
174 | | - </div> |
175 | | - {#if isActive} |
176 | | - <span |
177 | | - class="rounded-full bg-black px-2.5 py-1 text-xs font-bold text-white shadow-md dark:bg-white dark:text-black" |
| 135 | + <button |
| 136 | + type="button" |
| 137 | + title="Model settings" |
| 138 | + aria-label="Model settings for {model.displayName}" |
| 139 | + class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-50 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:bg-gray-800 dark:hover:bg-gray-700 dark:hover:text-gray-200" |
| 140 | + onclick={(e) => { |
| 141 | + e.preventDefault(); |
| 142 | + e.stopPropagation(); |
| 143 | + goto(`${base}/settings/${model.id}`); |
| 144 | + }} |
178 | 145 | > |
179 | | - Active |
180 | | - </span> |
181 | | - {/if} |
| 146 | + <LucideSettings class="h-3.5 w-3.5" /> |
| 147 | + </button> |
| 148 | + {#if isActive} |
| 149 | + <span |
| 150 | + class="ml-2 rounded-full bg-black px-2.5 py-1 text-xs font-bold text-white shadow-lg dark:bg-white dark:text-black" |
| 151 | + > |
| 152 | + Active |
| 153 | + </span> |
| 154 | + {/if} |
| 155 | + </div> |
| 156 | + </div> |
| 157 | + |
| 158 | + <!-- Content --> |
| 159 | + <div> |
| 160 | + <div class="mb-1 flex items-center gap-2"> |
| 161 | + <h3 class="text-lg font-semibold tracking-tight text-gray-900 dark:text-gray-100"> |
| 162 | + {model.displayName} |
| 163 | + </h3> |
| 164 | + {#if index === 0 && model.isRouter && !isActive} |
| 165 | + <span |
| 166 | + class="rounded-full border border-gray-200 px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider text-gray-500 dark:border-gray-700 dark:text-gray-400" |
| 167 | + > |
| 168 | + Default |
| 169 | + </span> |
| 170 | + {/if} |
| 171 | + </div> |
| 172 | + <p class="line-clamp-2 text-sm leading-relaxed text-gray-500 dark:text-gray-400"> |
| 173 | + {model.isRouter |
| 174 | + ? "Routes your messages to the best model for your request." |
| 175 | + : model.description || "-"} |
| 176 | + </p> |
182 | 177 | </div> |
183 | 178 | </a> |
184 | 179 | {/each} |
|
0 commit comments