fix(openai): polish provider list overlays and i18n

This commit is contained in:
liucong2013
2026-04-22 17:48:58 +00:00
Unverified
parent b753145887
commit be92535c98
8 changed files with 76 additions and 49 deletions
+4 -1
View File
@@ -391,7 +391,10 @@ export function PageTransition({
}
>
<PageTransitionLayerContext.Provider
value={PAGE_TRANSITION_LAYER_CONTEXT_VALUES[layer.status]}
value={{
...PAGE_TRANSITION_LAYER_CONTEXT_VALUES[layer.status],
isAnimating,
}}
>
{render(layer.location)}
</PageTransitionLayerContext.Provider>
+4 -3
View File
@@ -5,15 +5,16 @@ export type LayerStatus = 'current' | 'exiting' | 'stacked';
export type PageTransitionLayerContextValue = {
status: LayerStatus;
isCurrentLayer: boolean;
isAnimating: boolean;
};
export const PageTransitionLayerContext =
createContext<PageTransitionLayerContextValue | null>(null);
export const PAGE_TRANSITION_LAYER_CONTEXT_VALUES: Record<LayerStatus, PageTransitionLayerContextValue> = {
current: { status: 'current', isCurrentLayer: true },
stacked: { status: 'stacked', isCurrentLayer: false },
exiting: { status: 'exiting', isCurrentLayer: false },
current: { status: 'current', isCurrentLayer: true, isAnimating: false },
stacked: { status: 'stacked', isCurrentLayer: false, isAnimating: false },
exiting: { status: 'exiting', isCurrentLayer: false, isAnimating: false },
};
export function usePageTransitionLayer() {
@@ -16,6 +16,7 @@ import {
import { type UsageDetailsByAuthIndex, type UsageDetailsBySource } from '@/utils/usageIndex';
import styles from '@/pages/AiProvidersPage.module.scss';
import { ProviderStatusBar } from '../ProviderStatusBar';
import { usePageTransitionLayer } from '@/components/common/PageTransitionLayer';
import {
collectOpenAIProviderUsageDetails,
getOpenAIProviderKey,
@@ -68,6 +69,8 @@ export function OpenAISection({
onDelete,
}: OpenAISectionProps) {
const { t } = useTranslation();
const pageTransitionLayer = usePageTransitionLayer();
const isTransitionAnimating = pageTransitionLayer?.isAnimating ?? false;
const actionsDisabled = disableControls || loading || isSwitching;
const [sortOption, setSortOption] = useState<SortOption>('priority');
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
@@ -85,7 +88,13 @@ export function OpenAISection({
const topDropdownRef = useRef<HTMLDivElement>(null);
const floatingDropdownRef = useRef<HTMLDivElement>(null);
const shouldRenderFloatingToolbar = !isTransitionAnimating && floatingToolbarStyle.visible;
useEffect(() => {
if (isTransitionAnimating) {
return;
}
const updateFloatingToolbar = () => {
const section = sectionRef.current;
const anchor = topToolbarAnchorRef.current;
@@ -131,7 +140,7 @@ export function OpenAISection({
window.removeEventListener('resize', updateFloatingToolbar);
window.removeEventListener('scroll', updateFloatingToolbar, true);
};
}, [configs.length, isDropdownOpen, selectedModels, sortDirection, sortOption]);
}, [configs.length, isDropdownOpen, isTransitionAnimating, selectedModels, sortDirection, sortOption]);
useEffect(() => {
if (!isDropdownOpen) {
@@ -239,11 +248,11 @@ export function OpenAISection({
const providerStats =
sortOption === 'recent-success'
? new Map(
sorted.map(({ config }) => [
config,
getOpenAIProviderStats(config, keyStats),
])
)
sorted.map(({ config }) => [
config,
getOpenAIProviderStats(config, keyStats),
])
)
: null;
switch (sortOption) {
@@ -333,7 +342,7 @@ export function OpenAISection({
);
const renderToolbar = (isFloating = false) => {
const isActiveToolbar = isFloating === floatingToolbarStyle.visible;
const isActiveToolbar = isFloating === shouldRenderFloatingToolbar;
const dropdownClassName =
dropdownLayout.openAbove ? `${styles.modelDropdownList} ${styles.modelDropdownListAbove}` : styles.modelDropdownList;
@@ -536,7 +545,7 @@ export function OpenAISection({
) : null}
{provider.testModel && (
<div className={styles.fieldRow}>
<span className={styles.fieldLabel}>Test Model:</span>
<span className={styles.fieldLabel}>{t('ai_providers.openai_test_model')}:</span>
<span className={styles.fieldValue}>{provider.testModel}</span>
</div>
)}
@@ -570,7 +579,7 @@ export function OpenAISection({
extra={
<div
ref={topToolbarAnchorRef}
className={floatingToolbarStyle.visible ? styles.openaiToolbarAnchorHidden : undefined}
className={shouldRenderFloatingToolbar ? styles.openaiToolbarAnchorHidden : undefined}
>
{renderToolbar(false)}
</div>
@@ -579,42 +588,42 @@ export function OpenAISection({
{loading && sortedConfigs.length === 0 ? (
<div className="hint">{t('common.loading')}</div>
) : configs.length > 0 && sortedConfigs.length === 0 ? (
<EmptyState
title={t('ai_providers.openai_filtered_empty_title')}
description={t('ai_providers.openai_filtered_empty_desc')}
action={(
<Button variant="secondary" size="sm" onClick={clearAllModels} disabled={actionsDisabled}>
{t('ai_providers.model_search_clear')}
</Button>
)}
/>
<EmptyState
title={t('ai_providers.openai_filtered_empty_title')}
description={t('ai_providers.openai_filtered_empty_desc')}
action={(
<Button variant="secondary" size="sm" onClick={clearAllModels} disabled={actionsDisabled}>
{t('ai_providers.model_search_clear')}
</Button>
)}
/>
) : sortedConfigs.length === 0 ? (
<EmptyState
title={t('ai_providers.openai_empty_title')}
description={t('ai_providers.openai_empty_desc')}
/>
) : (
<div className={styles.openaiProviderList}>{sortedConfigs.map(renderProviderCard)}</div>
)}
<EmptyState
title={t('ai_providers.openai_empty_title')}
description={t('ai_providers.openai_empty_desc')}
/>
) : (
<div className={styles.openaiProviderList}>{sortedConfigs.map(renderProviderCard)}</div>
)}
</Card>
</div>
{typeof document !== 'undefined' && floatingToolbarStyle.visible
{typeof document !== 'undefined' && shouldRenderFloatingToolbar
? createPortal(
<div
className={`card ${styles.openaiFloatingToolbar}`}
style={{
left: `${floatingToolbarStyle.left}px`,
top: `${floatingToolbarStyle.top}px`,
width: `${floatingToolbarStyle.width}px`,
}}
>
<div className="card-header">
<div className="title">{renderStaticTitle()}</div>
{renderToolbar(true)}
</div>
</div>,
document.body
)
<div
className={`card ${styles.openaiFloatingToolbar}`}
style={{
left: `${floatingToolbarStyle.left}px`,
top: `${floatingToolbarStyle.top}px`,
width: `${floatingToolbarStyle.width}px`,
}}
>
<div className="card-header">
<div className="title">{renderStaticTitle()}</div>
{renderToolbar(true)}
</div>
</div>,
document.body
)
: null}
</>
);
+3
View File
@@ -405,8 +405,11 @@
"openai_filtered_empty_title": "No matching providers",
"openai_filtered_empty_desc": "No providers match the current model filter. Clear the filter and try again.",
"sort_by_name": "Sort by Name",
"sort_ascending": "Sort ascending",
"sort_by_priority": "Sort by Priority",
"sort_by_recent_success": "Sort by Recent Success",
"sort_descending": "Sort descending",
"openai_test_model": "Test Model",
"openai_add_modal_title": "Add OpenAI Compatible Provider",
"openai_add_modal_name_label": "Provider Name:",
"openai_add_modal_name_placeholder": "e.g.: openrouter",
+3
View File
@@ -405,8 +405,11 @@
"openai_filtered_empty_title": "没有匹配的提供商",
"openai_filtered_empty_desc": "当前模型筛选下没有匹配的提供商,请清除筛选后重试。",
"sort_by_name": "按名称排序",
"sort_ascending": "升序排序",
"sort_by_priority": "按优先级排序",
"sort_by_recent_success": "按最近成功数排序",
"sort_descending": "降序排序",
"openai_test_model": "测试模型",
"openai_add_modal_title": "添加OpenAI兼容提供商",
"openai_add_modal_name_label": "提供商名称:",
"openai_add_modal_name_placeholder": "例如: openrouter",
+8
View File
@@ -402,6 +402,14 @@
"openai_add_button": "新增供應商",
"openai_empty_title": "暫無 OpenAI 相容供應商",
"openai_empty_desc": "點擊上方按鈕新增第一個供應商",
"openai_filtered_empty_title": "沒有匹配的供應商",
"openai_filtered_empty_desc": "目前模型篩選下沒有匹配的供應商,請清除篩選後再試一次。",
"sort_by_name": "依名稱排序",
"sort_ascending": "升冪排序",
"sort_by_priority": "依優先順序排序",
"sort_by_recent_success": "依最近成功排序",
"sort_descending": "降冪排序",
"openai_test_model": "測試模型",
"openai_add_modal_title": "新增 OpenAI 相容供應商",
"openai_add_modal_name_label": "供應商名稱:",
"openai_add_modal_name_placeholder": "例如: openrouter",
+3 -3
View File
@@ -255,8 +255,8 @@
height: 18px;
padding: 0;
border: none;
background: rgba(0, 0, 0, 0.15);
color: #000;
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 18px;
line-height: 1;
cursor: pointer;
@@ -265,7 +265,7 @@
flex-shrink: 0;
&:hover {
background: rgba(0, 0, 0, 0.3);
background: var(--bg-tertiary);
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 10;
z-index: $z-dropdown + 1;
width: 100%;
@media (max-width: $breakpoint-mobile) {