feat(AiProviders): improve layout and styling for OpenAI edit and models pages

This commit is contained in:
Supra4E8C
2026-02-14 20:35:16 +08:00
parent b702cd6e4c
commit d140fe1061
3 changed files with 156 additions and 81 deletions

View File

@@ -511,9 +511,9 @@ export function AiProvidersOpenAIEditPage() {
> >
<Card> <Card>
{invalidIndexParam || invalidIndex ? ( {invalidIndexParam || invalidIndex ? (
<div className="hint">{t('common.invalid_provider_index')}</div> <div className={styles.sectionHint}>{t('common.invalid_provider_index')}</div>
) : ( ) : (
<> <div className={styles.openaiEditForm}>
<Input <Input
label={t('ai_providers.openai_add_modal_name_label')} label={t('ai_providers.openai_add_modal_name_label')}
value={form.name} value={form.name}
@@ -579,7 +579,7 @@ export function AiProvidersOpenAIEditPage() {
</div> </div>
{/* 提示文本 */} {/* 提示文本 */}
<div className="hint">{t('ai_providers.openai_models_hint')}</div> <div className={styles.sectionHint}>{t('ai_providers.openai_models_hint')}</div>
{/* 模型列表 */} {/* 模型列表 */}
<ModelInputList <ModelInputList
@@ -649,14 +649,14 @@ export function AiProvidersOpenAIEditPage() {
)} )}
</div> </div>
<div className={`form-group ${styles.keyEntriesSection}`}> <div className={styles.keyEntriesSection}>
<div className={styles.keyEntriesHeader}> <div className={styles.keyEntriesHeader}>
<label>{t('ai_providers.openai_add_modal_keys_label')}</label> <label className={styles.keyEntriesTitle}>{t('ai_providers.openai_add_modal_keys_label')}</label>
<span className={styles.keyEntriesHint}>{t('ai_providers.openai_keys_hint')}</span> <span className={styles.keyEntriesHint}>{t('ai_providers.openai_keys_hint')}</span>
</div> </div>
{renderKeyEntries(form.apiKeyEntries)} {renderKeyEntries(form.apiKeyEntries)}
</div> </div>
</> </div>
)} )}
</Card> </Card>
</SecondaryScreenShell> </SecondaryScreenShell>

View File

@@ -153,70 +153,76 @@ export function AiProvidersOpenAIModelsPage() {
loadingLabel={t('common.loading')} loadingLabel={t('common.loading')}
> >
<Card> <Card>
<div className="hint" style={{ marginBottom: 8 }}> <div className={styles.openaiModelsContent}>
{t('ai_providers.openai_models_fetch_hint')} <div className={styles.sectionHint}>{t('ai_providers.openai_models_fetch_hint')}</div>
</div> <div className={styles.openaiModelsEndpointSection}>
<div className="form-group"> <label className={styles.openaiModelsEndpointLabel}>
<label>{t('ai_providers.openai_models_fetch_url_label')}</label> {t('ai_providers.openai_models_fetch_url_label')}
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}> </label>
<input className="input" readOnly value={endpoint} /> <div className={styles.openaiModelsEndpointControls}>
<Button <input
variant="secondary" className={`input ${styles.openaiModelsEndpointInput}`}
size="sm" readOnly
onClick={() => void fetchOpenaiModelDiscovery({ allowFallback: true })} value={endpoint}
loading={fetching} />
disabled={disableControls || saving} <Button
> variant="secondary"
{t('ai_providers.openai_models_fetch_refresh')} size="sm"
</Button> onClick={() => void fetchOpenaiModelDiscovery({ allowFallback: true })}
loading={fetching}
disabled={disableControls || saving}
>
{t('ai_providers.openai_models_fetch_refresh')}
</Button>
</div>
</div> </div>
</div> <Input
<Input label={t('ai_providers.openai_models_search_label')}
label={t('ai_providers.openai_models_search_label')} placeholder={t('ai_providers.openai_models_search_placeholder')}
placeholder={t('ai_providers.openai_models_search_placeholder')} value={search}
value={search} onChange={(e) => setSearch(e.target.value)}
onChange={(e) => setSearch(e.target.value)} disabled={fetching}
disabled={fetching} />
/> {error && <div className="error-box">{error}</div>}
{error && <div className="error-box">{error}</div>} {fetching ? (
{fetching ? ( <div className={styles.sectionHint}>{t('ai_providers.openai_models_fetch_loading')}</div>
<div className="hint">{t('ai_providers.openai_models_fetch_loading')}</div> ) : models.length === 0 ? (
) : models.length === 0 ? ( <div className={styles.sectionHint}>{t('ai_providers.openai_models_fetch_empty')}</div>
<div className="hint">{t('ai_providers.openai_models_fetch_empty')}</div> ) : filteredModels.length === 0 ? (
) : filteredModels.length === 0 ? ( <div className={styles.sectionHint}>{t('ai_providers.openai_models_search_empty')}</div>
<div className="hint">{t('ai_providers.openai_models_search_empty')}</div> ) : (
) : ( <div className={styles.modelDiscoveryList}>
<div className={styles.modelDiscoveryList}> {filteredModels.map((model) => {
{filteredModels.map((model) => { const checked = selected.has(model.name);
const checked = selected.has(model.name); return (
return ( <label
<label key={model.name}
key={model.name} className={`${styles.modelDiscoveryRow} ${
className={`${styles.modelDiscoveryRow} ${ checked ? styles.modelDiscoveryRowSelected : ''
checked ? styles.modelDiscoveryRowSelected : '' }`}
}`} >
> <input
<input type="checkbox"
type="checkbox" checked={checked}
checked={checked} onChange={() => toggleSelection(model.name)}
onChange={() => toggleSelection(model.name)} />
/> <div className={styles.modelDiscoveryMeta}>
<div className={styles.modelDiscoveryMeta}> <div className={styles.modelDiscoveryName}>
<div className={styles.modelDiscoveryName}> {model.name}
{model.name} {model.alias && (
{model.alias && ( <span className={styles.modelDiscoveryAlias}>{model.alias}</span>
<span className={styles.modelDiscoveryAlias}>{model.alias}</span> )}
</div>
{model.description && (
<div className={styles.modelDiscoveryDesc}>{model.description}</div>
)} )}
</div> </div>
{model.description && ( </label>
<div className={styles.modelDiscoveryDesc}>{model.description}</div> );
)} })}
</div> </div>
</label> )}
); </div>
})}
</div>
)}
</Card> </Card>
</SecondaryScreenShell> </SecondaryScreenShell>
); );

View File

@@ -322,7 +322,7 @@
gap: 6px; gap: 6px;
max-height: 360px; max-height: 360px;
overflow-y: auto; overflow-y: auto;
margin-top: 8px; margin-top: 0;
padding-right: 4px; padding-right: 4px;
} }
@@ -561,11 +561,72 @@
// Model Config Section - Unified Layout // Model Config Section - Unified Layout
// ============================================ // ============================================
.modelConfigSection { .openaiEditForm {
margin-bottom: $spacing-md;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $spacing-md;
:global(.form-group) {
margin-bottom: 0;
}
:global(.status-badge) {
margin-bottom: 0;
align-self: flex-start;
}
}
.sectionHint {
margin: 0;
font-size: 13px;
line-height: 1.5;
color: var(--text-secondary);
}
.openaiModelsContent {
display: flex;
flex-direction: column;
gap: $spacing-md;
:global(.form-group) {
margin-bottom: 0;
}
}
.openaiModelsEndpointSection {
display: flex;
flex-direction: column;
gap: $spacing-xs;
}
.openaiModelsEndpointLabel {
display: block;
margin: 0;
font-weight: 600;
color: var(--text-primary);
}
.openaiModelsEndpointControls {
display: flex;
align-items: center;
gap: $spacing-sm; gap: $spacing-sm;
@include mobile {
flex-direction: column;
align-items: stretch;
}
}
.openaiModelsEndpointInput {
flex: 1;
min-width: 0;
}
.modelConfigSection {
margin-bottom: 0;
display: flex;
flex-direction: column;
gap: $spacing-md;
} }
.modelConfigHeader { .modelConfigHeader {
@@ -581,10 +642,11 @@
} }
.modelConfigTitle { .modelConfigTitle {
margin: 0;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
font-size: 14px; font-size: 14px;
line-height: 1.4; line-height: 1.5;
} }
.modelConfigToolbar { .modelConfigToolbar {
@@ -646,7 +708,7 @@
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: $spacing-md; gap: $spacing-md;
margin-top: $spacing-sm; margin-top: 0;
padding: $spacing-sm $spacing-md; padding: $spacing-sm $spacing-md;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: $radius-md; border-radius: $radius-md;
@@ -661,7 +723,7 @@
.modelTestMeta { .modelTestMeta {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 6px;
min-width: 0; min-width: 0;
} }
@@ -669,13 +731,13 @@
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: var(--text-secondary); color: var(--text-secondary);
line-height: 1.4; line-height: 1.5;
} }
.modelTestHint { .modelTestHint {
font-size: 12px; font-size: 12px;
color: var(--text-tertiary); color: var(--text-tertiary);
line-height: 1.4; line-height: 1.5;
} }
.modelTestControls { .modelTestControls {
@@ -697,22 +759,29 @@
.keyEntriesSection { .keyEntriesSection {
margin-bottom: 0; margin-bottom: 0;
display: flex;
flex-direction: column;
gap: $spacing-sm;
} }
.keyEntriesHeader { .keyEntriesHeader {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 6px;
margin-bottom: $spacing-sm; margin-bottom: 0;
}
label { .keyEntriesTitle {
margin: 0; display: block;
} margin: 0;
font-weight: 600;
color: var(--text-primary);
line-height: 1.5;
} }
.keyEntriesHint { .keyEntriesHint {
font-size: 13px; font-size: 13px;
line-height: 1.4; line-height: 1.5;
color: var(--text-secondary); color: var(--text-secondary);
} }