Skip to content

Commit 97ec6b3

Browse files
committed
feat(settings): add custom headers support and improve provider configuration UI
- Add ScrollShadow component for better scrollable content handling in settings - Implement custom headers management for provider configurations - Add getProviderDefaultHeaders function to handle protocol-specific headers - Update provider switching logic to preserve headers across provider changes - Replace provider name field with displayName for better UI consistency - Refactor headers synchronization to use initialization instead of useEffect - Improve ProviderSettings layout with scrollable model configuration section - Update baseUrl field references from endpoint.default to baseUrl - Enhance settings persistence to include headers in provider configurations - Add Switch component import for future UI enhancements
1 parent 760a5f1 commit 97ec6b3

File tree

9 files changed

+417
-341
lines changed

9 files changed

+417
-341
lines changed

src/renderer/components/settings/SettingsModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export default function SettingsModal() {
9393
}, [llmConfig, providerConfigs, language, autoApprove, agentConfig, aiInstructions, webSearchConfig, mcpConfig, enableFileLogging])
9494

9595
const handleSave = useCallback(async () => {
96-
// 合并当前 provider 的配置
96+
// 合并当前 provider 的配置(包括 headers)
9797
const currentProviderLocalConfig = localProviderConfigs[localConfig.provider] || {}
9898
const finalProviderConfigs = {
9999
...localProviderConfigs,
@@ -103,11 +103,11 @@ export default function SettingsModal() {
103103
baseUrl: localConfig.baseUrl,
104104
timeout: localConfig.timeout,
105105
model: localConfig.model,
106-
advanced: currentProviderLocalConfig.advanced,
106+
headers: localConfig.headers, // 保存 headers
107107
}
108108
}
109109

110-
// 更新 Store 状态
110+
// 更新 Store 状态(包括 headers)
111111
set('llmConfig', localConfig)
112112
set('language', localLanguage)
113113
set('autoApprove', localAutoApprove)
@@ -189,7 +189,7 @@ export default function SettingsModal() {
189189
const providers = useMemo(() =>
190190
Object.entries(PROVIDERS).map(([id, p]) => ({
191191
id,
192-
name: p.name,
192+
name: p.displayName,
193193
models: [...(p.models || []), ...(providerConfigs[id]?.customModels || [])]
194194
})),
195195
[providerConfigs]

src/renderer/components/settings/tabs/ProviderSettings.tsx

Lines changed: 217 additions & 117 deletions
Large diffs are not rendered by default.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* ScrollShadow 组件
3+
*
4+
* 带上下虚拟阴影的滚动容器,隐藏滚动条
5+
* 当内容可滚动时自动显示渐变阴影效果
6+
*/
7+
8+
import { useRef, useState, useEffect, ReactNode } from 'react'
9+
10+
interface ScrollShadowProps {
11+
children: ReactNode
12+
className?: string
13+
maxHeight?: string
14+
showScrollbar?: boolean
15+
}
16+
17+
export function ScrollShadow({
18+
children,
19+
className = '',
20+
maxHeight = '400px',
21+
showScrollbar = false
22+
}: ScrollShadowProps) {
23+
const scrollRef = useRef<HTMLDivElement>(null)
24+
const [showTopShadow, setShowTopShadow] = useState(false)
25+
const [showBottomShadow, setShowBottomShadow] = useState(false)
26+
27+
const handleScroll = () => {
28+
const el = scrollRef.current
29+
if (!el) return
30+
31+
const { scrollTop, scrollHeight, clientHeight } = el
32+
33+
// 顶部阴影:滚动超过 5px 时显示
34+
setShowTopShadow(scrollTop > 5)
35+
36+
// 底部阴影:距离底部超过 5px 时显示
37+
setShowBottomShadow(scrollTop + clientHeight < scrollHeight - 5)
38+
}
39+
40+
useEffect(() => {
41+
const el = scrollRef.current
42+
if (!el) return
43+
44+
// 初始检查
45+
handleScroll()
46+
47+
// 监听滚动
48+
el.addEventListener('scroll', handleScroll)
49+
50+
// 监听内容变化(使用 ResizeObserver)
51+
const resizeObserver = new ResizeObserver(handleScroll)
52+
resizeObserver.observe(el)
53+
54+
return () => {
55+
el.removeEventListener('scroll', handleScroll)
56+
resizeObserver.disconnect()
57+
}
58+
}, [children])
59+
60+
return (
61+
<div className={`relative ${className}`}>
62+
{/* 顶部阴影 */}
63+
<div
64+
className={`absolute top-0 left-0 right-0 h-12 pointer-events-none z-10 transition-opacity duration-200 ${
65+
showTopShadow ? 'opacity-100' : 'opacity-0'
66+
}`}
67+
style={{
68+
background: 'linear-gradient(to bottom, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.1) 50%, transparent 100%)'
69+
}}
70+
/>
71+
72+
{/* 滚动容器 */}
73+
<div
74+
ref={scrollRef}
75+
className={`overflow-y-auto ${showScrollbar ? '' : 'scrollbar-none'}`}
76+
style={{ maxHeight }}
77+
>
78+
{children}
79+
</div>
80+
81+
{/* 底部阴影 */}
82+
<div
83+
className={`absolute bottom-0 left-0 right-0 h-12 pointer-events-none z-10 transition-opacity duration-200 ${
84+
showBottomShadow ? 'opacity-100' : 'opacity-0'
85+
}`}
86+
style={{
87+
background: 'linear-gradient(to top, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.1) 50%, transparent 100%)'
88+
}}
89+
/>
90+
</div>
91+
)
92+
}

src/renderer/components/ui/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export * from './Modal'
66
export * from './Tooltip'
77
export * from './Select'
88
export * from './ContextMenu'
9+
export * from './ScrollShadow'

src/renderer/settings/exportImport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function exportSettings(settings: SettingsState, includeApiKeys = false):
3535
baseUrl: config.baseUrl,
3636
timeout: config.timeout,
3737
customModels: config.customModels,
38-
advanced: config.advanced,
38+
headers: config.headers, // 导出 headers
3939
}
4040

4141
if (includeApiKeys && config.apiKey) {

src/renderer/settings/service.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
import {
2525
isBuiltinProvider,
2626
getBuiltinProvider,
27-
cleanAdvancedConfig,
2827
} from '@shared/config/providers'
2928
import type { ProviderConfig, LLMConfig } from '@shared/config/types'
3029

@@ -91,11 +90,7 @@ function cleanProviderConfig(
9190
if (isCurrentProvider && config.model) cleaned.model = config.model
9291
if (config.timeout && config.timeout !== 120000) cleaned.timeout = config.timeout
9392
if (config.customModels?.length) cleaned.customModels = config.customModels
94-
95-
if (config.advanced) {
96-
const cleanedAdvanced = cleanAdvancedConfig(providerId, config.advanced)
97-
if (cleanedAdvanced) cleaned.advanced = cleanedAdvanced
98-
}
93+
if (config.headers && Object.keys(config.headers).length > 0) cleaned.headers = config.headers
9994

10095
if (!isBuiltin) {
10196
if (config.displayName) cleaned.displayName = config.displayName
@@ -136,9 +131,6 @@ function mergeLLMConfig(
136131
const providerConfig = providerConfigs[providerId] ?? {}
137132
const builtinDef = getBuiltinProvider(providerId)
138133

139-
// 从 advanced 配置中提取 headers
140-
const advancedHeaders = providerConfig.advanced?.request?.headers
141-
142134
// 优先级:saved > providerConfig > builtinDef > defaults
143135
return {
144136
provider: providerId,
@@ -162,7 +154,7 @@ function mergeLLMConfig(
162154
maxRetries: saved.maxRetries ?? defaults.maxRetries,
163155
toolChoice: saved.toolChoice ?? defaults.toolChoice,
164156
parallelToolCalls: saved.parallelToolCalls ?? defaults.parallelToolCalls,
165-
headers: saved.headers ?? advancedHeaders ?? defaults.headers, // 优先级:saved > advanced > defaults
157+
headers: providerConfig.headers ?? defaults.headers, // 从 providerConfig 加载 headers
166158

167159
// 功能开关
168160
enableThinking: saved.enableThinking ?? defaults.enableThinking,

src/renderer/types/provider.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
* - 自定义厂商:存储完整配置,通过 id 以 "custom-" 前缀区分
77
*/
88

9-
import type { AdvancedConfig, ApiProtocol } from '@/shared/config/providers'
10-
11-
export type { AdvancedConfig }
9+
import type { ApiProtocol } from '@/shared/config/providers'
1210

1311
// ============ Provider 设置类型 ============
1412

@@ -20,7 +18,7 @@ export interface ProviderModelConfig {
2018
timeout?: number
2119
model?: string
2220
customModels?: string[] // 模型列表(内置厂商是额外添加的,自定义厂商是全部模型)
23-
advanced?: AdvancedConfig
21+
headers?: Record<string, string> // 每个 provider 独立的 headers
2422

2523
// 自定义厂商专用字段(custom- 前缀的 provider)
2624
displayName?: string // 显示名称

0 commit comments

Comments
 (0)