Skip to content

Commit 8347cb9

Browse files
committed
fix(ui): redesign rate limit rules layout and improve modal styling
- Replace crowded 6-column grid with 2-row layout in rate limit rules - Use shared ToggleSwitch component instead of inline checkbox - Merge dual-column layout into single column in provider detail modal - Collapse headers textarea when empty for cleaner defaults - Fix grey input text by adding text-primary to inputs and modal frame
1 parent e2c2f83 commit 8347cb9

3 files changed

Lines changed: 395 additions & 16 deletions

File tree

frontend/src/features/accounts/components/AccountDetailModalFrame.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default function AccountDetailModalFrame({
2424
return (
2525
<div className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto bg-black/80 p-3 backdrop-blur-sm sm:p-6" onClick={onClose}>
2626
<div
27-
className="flex max-h-[calc(100vh-1.5rem)] w-full max-w-6xl flex-col overflow-hidden border-2 border-[var(--border-color)] bg-[var(--bg-main)] shadow-hard shadow-[var(--shadow-color)] sm:max-h-[calc(100vh-3rem)]"
27+
className="flex max-h-[calc(100vh-1.5rem)] w-full max-w-6xl flex-col overflow-hidden border-2 border-[var(--border-color)] bg-[var(--bg-main)] text-[var(--text-primary)] shadow-hard shadow-[var(--shadow-color)] sm:max-h-[calc(100vh-3rem)]"
2828
onClick={(event) => event.stopPropagation()}
2929
>
3030
{header || footer || error ? (

frontend/src/features/accounts/components/OpenAICompatibleDetailModal.tsx

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react';
12
import type { Translator } from '../model/types';
23
import {
34
resolveProviderDetailModelOptions,
@@ -7,13 +8,17 @@ import {
78
type ProviderRemoteModelsState,
89
type ProviderVerifyState,
910
} from '../model/openAICompatible';
11+
import type { RateLimitState, RateLimitStrategyMeta } from '../model/rateLimit';
1012
import AccountDetailModalFrame from './AccountDetailModalFrame';
13+
import RateLimitRulesSection from './RateLimitRulesSection';
1114

1215
interface OpenAICompatibleDetailModalProps {
1316
t: Translator;
1417
draft: OpenAICompatibleProviderDraft;
1518
verifyState: ProviderVerifyState;
1619
remoteModelsState?: ProviderRemoteModelsState;
20+
rateLimitStatus?: RateLimitState;
21+
rateLimitStrategies?: RateLimitStrategyMeta[];
1722
error: string;
1823
saving: boolean;
1924
onClose: () => void;
@@ -22,6 +27,7 @@ interface OpenAICompatibleDetailModalProps {
2227
onVerify: () => void;
2328
onFetchModels: () => void;
2429
onApplyFetchedModels: () => void;
30+
onRateLimitRulesChanged: () => void;
2531
}
2632

2733
function formatLastVerifiedAt(timestamp: number | null) {
@@ -36,6 +42,8 @@ export default function OpenAICompatibleDetailModal({
3642
draft,
3743
verifyState,
3844
remoteModelsState,
45+
rateLimitStatus,
46+
rateLimitStrategies,
3947
error,
4048
saving,
4149
onClose,
@@ -44,7 +52,9 @@ export default function OpenAICompatibleDetailModal({
4452
onVerify,
4553
onFetchModels,
4654
onApplyFetchedModels,
55+
onRateLimitRulesChanged,
4756
}: OpenAICompatibleDetailModalProps) {
57+
const [headersExpanded, setHeadersExpanded] = useState(false);
4858
const selectedPreset = resolveOpenAICompatibleProviderPreset({
4959
name: draft.name,
5060
baseUrl: draft.baseUrl,
@@ -55,6 +65,9 @@ export default function OpenAICompatibleDetailModal({
5565
});
5666
const suggestedModels: OpenAICompatibleModelRow[] = suggestedModelOptions.models;
5767
const effectiveVerifyModel = draft.verifyModel || suggestedModels[0]?.name || '';
68+
const rateLimitAccountName = draft.currentName || draft.name;
69+
const rateLimitAccountKey = `openai-compatible:${rateLimitAccountName}`;
70+
const rateLimitMatchKey = rateLimitStatus?.matchKey || `provider:${rateLimitAccountName.trim().toLowerCase()}`;
5871
const modelSourceLabel =
5972
suggestedModelOptions.source === 'remote'
6073
? t('accounts.openai_provider_models_source_remote')
@@ -117,8 +130,7 @@ export default function OpenAICompatibleDetailModal({
117130
</header>
118131

119132
<div className="min-h-0 flex-1 overflow-auto">
120-
<div className="grid gap-0 xl:grid-cols-[1.1fr_0.9fr]">
121-
<section className="min-h-0 px-6 py-6 xl:border-r-2 xl:border-[var(--border-color)]">
133+
<section className="min-h-0 px-6 py-6">
122134
<div className="space-y-6">
123135
<label className="space-y-2">
124136
<div className="text-[0.5625rem] font-black uppercase tracking-[0.2em] text-[var(--text-muted)]">
@@ -146,18 +158,29 @@ export default function OpenAICompatibleDetailModal({
146158
</label>
147159

148160
<div className="space-y-2">
149-
<div className="text-[0.5625rem] font-black uppercase tracking-[0.2em] text-[var(--text-muted)]">
161+
<button
162+
type="button"
163+
onClick={() => setHeadersExpanded((prev) => !prev)}
164+
className="flex w-full items-center gap-2 text-[0.5625rem] font-black uppercase tracking-[0.2em] text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]"
165+
>
166+
<span className="inline-grid h-3.5 w-3.5 place-items-center border border-[var(--border-color)] text-[0.5rem] leading-none">
167+
{draft.headersText || headersExpanded ? '−' : '+'}
168+
</span>
150169
{t('accounts.openai_provider_headers')}
151-
</div>
152-
<textarea
153-
value={draft.headersText}
154-
onChange={(event) => onChange({ ...draft, headersText: event.target.value })}
155-
className="input-swiss min-h-32 w-full resize-y font-mono !text-[0.6875rem] leading-6"
156-
placeholder={'Authorization: Bearer sk-...\nHTTP-Referer: https://example.com\nX-Title: GetTokens'}
157-
/>
158-
<div className="text-[0.5rem] font-black uppercase tracking-[0.14em] text-[var(--text-muted)]">
159-
{t('accounts.openai_provider_headers_hint')}
160-
</div>
170+
</button>
171+
{(draft.headersText || headersExpanded) ? (
172+
<>
173+
<textarea
174+
value={draft.headersText}
175+
onChange={(event) => onChange({ ...draft, headersText: event.target.value })}
176+
className="input-swiss min-h-32 w-full resize-y font-mono !text-[0.6875rem] leading-6"
177+
placeholder={'Authorization: Bearer sk-...\nHTTP-Referer: https://example.com\nX-Title: GetTokens'}
178+
/>
179+
<div className="text-[0.5rem] font-black uppercase tracking-[0.14em] text-[var(--text-muted)]">
180+
{t('accounts.openai_provider_headers_hint')}
181+
</div>
182+
</>
183+
) : null}
161184
</div>
162185

163186
<div className="space-y-3">
@@ -261,7 +284,7 @@ export default function OpenAICompatibleDetailModal({
261284
</div>
262285
</section>
263286

264-
<section className="min-h-0 space-y-6 bg-[var(--bg-surface)]/30 px-6 py-6">
287+
<section className="min-h-0 space-y-6 border-t-2 border-[var(--border-color)] bg-[var(--bg-surface)]/30 px-6 py-6">
265288
<div className="space-y-4">
266289
<div className="text-[0.5625rem] font-black uppercase tracking-[0.2em] text-[var(--text-muted)]">
267290
{t('accounts.openai_provider_test_model')}
@@ -311,7 +334,14 @@ export default function OpenAICompatibleDetailModal({
311334
</div>
312335
</div>
313336
</section>
314-
</div>
337+
<RateLimitRulesSection
338+
accountKey={rateLimitAccountKey}
339+
matchKey={rateLimitMatchKey}
340+
rateLimitStatus={rateLimitStatus}
341+
rateLimitStrategies={rateLimitStrategies}
342+
onRateLimitRulesChanged={onRateLimitRulesChanged}
343+
t={t}
344+
/>
315345
</div>
316346

317347
{error ? (

0 commit comments

Comments
 (0)