66 * DeepSeek/OpenRouter/Ollama/自定义)。密钥只写不回传——视图仅
77 * 显示"已配置";测试连接走 ai_provider_test(最小 chat 请求)。
88 *
9- * @line -limit-exemption: 338 行(300 上限)——v0.11.6 M1 code-review 修复
10- * (确认弹窗/内联密钥输入/textarea 模型列表)后越线,
9+ * @line -limit-exemption: 329 行(300 上限)——v0.11.6 M1 code-review 修复
10+ * (确认弹窗/内联密钥输入/presetOptions/ textarea 模型列表)后越线,
1111 * 已登记 docs/standards/line-limit-exemptions.md。
1212 */
1313import { useEffect , useState } from "react" ;
@@ -48,6 +48,8 @@ export default function AiProviderSettings() {
4848 const [ showAdd , setShowAdd ] = useState ( false ) ;
4949 const [ keyInputId , setKeyInputId ] = useState < string | null > ( null ) ;
5050 const [ keyInput , setKeyInput ] = useState ( "" ) ;
51+ // I-3:预设双源——后端 ai_provider_presets 优先,失败回退内置常量
52+ const [ presetOptions , setPresetOptions ] = useState ( PRESET_OPTIONS ) ;
5153
5254 useEffect ( ( ) => {
5355 void load ( ) ;
@@ -60,6 +62,11 @@ export default function AiProviderSettings() {
6062 } catch ( e ) {
6163 setMsg ( { kind : "err" , text : `读取 Provider 列表失败:${ e } ` } ) ;
6264 }
65+ // I-3:预设双源——从后端拉取预设模板,失败保持内置常量兜底
66+ try {
67+ const presets = await invoke < AiProviderView [ ] > ( "ai_provider_presets" ) ;
68+ setPresetOptions ( presets . map ( ( p ) => ( { kind : p . kind as AiProviderInput [ "kind" ] , name : p . name , baseUrl : p . baseUrl , models : p . models [ 0 ] ?? "" } ) ) ) ;
69+ } catch { /* 保持内置常量兜底 */ }
6370 } ;
6471
6572 const run = async ( op : ( ) => Promise < unknown > , okText ?: string ) => {
@@ -147,7 +154,7 @@ export default function AiProviderSettings() {
147154 { p . hasKey ? (
148155 < button style = { btn } onClick = { ( ) => void clearKey ( p . id ) } disabled = { busy } > 清除密钥</ button >
149156 ) : (
150- < button style = { btn } onClick = { ( ) => setKeyInputId ( p . id ) } disabled = { busy } > 配置密钥</ button >
157+ < button style = { btn } onClick = { ( ) => { setKeyInputId ( p . id ) ; setKeyInput ( "" ) ; } } disabled = { busy } > 配置密钥</ button >
151158 ) }
152159 { ! p . isDefault && (
153160 < button style = { { ...btn , color : "#dc2626" } } onClick = { ( ) => void removeProvider ( p . id ) } disabled = { busy } > 删除</ button >
@@ -166,9 +173,11 @@ export default function AiProviderSettings() {
166173 style = { { ...btn , background : "#0d9488" , color : "#fff" , border : "none" } }
167174 disabled = { busy }
168175 onClick = { ( ) => {
169- void run ( ( ) => invoke ( "ai_provider_save_key" , { id : p . id , apiKey : keyInput . trim ( ) } ) , "密钥已保存" ) ;
170- setKeyInput ( "" ) ;
171- setKeyInputId ( null ) ;
176+ void run ( async ( ) => {
177+ await invoke ( "ai_provider_save_key" , { id : p . id , apiKey : keyInput . trim ( ) } ) ;
178+ setKeyInput ( "" ) ;
179+ setKeyInputId ( null ) ;
180+ } , "密钥已保存" ) ;
172181 } }
173182 >
174183 保存
@@ -189,6 +198,7 @@ export default function AiProviderSettings() {
189198
190199 { showAdd && (
191200 < ProviderForm
201+ presetOptions = { presetOptions }
192202 onCancel = { ( ) => setShowAdd ( false ) }
193203 onDone = { ( text ) => {
194204 setShowAdd ( false ) ;
@@ -201,6 +211,7 @@ export default function AiProviderSettings() {
201211
202212 { editing && (
203213 < ProviderForm
214+ presetOptions = { presetOptions }
204215 initial = { editing }
205216 onCancel = { ( ) => setEditing ( null ) }
206217 onDone = { ( text ) => {
@@ -227,13 +238,15 @@ function ProviderForm({
227238 onCancel,
228239 onDone,
229240 onError,
241+ presetOptions = PRESET_OPTIONS ,
230242} : {
231243 initial ?: AiProviderView ;
232244 onCancel : ( ) => void ;
233245 onDone : ( text : string ) => void ;
234246 onError : ( text : string ) => void ;
247+ presetOptions : { kind : AiProviderInput [ "kind" ] ; name : string ; baseUrl : string ; models : string } [ ] ;
235248} ) {
236- const [ preset , setPreset ] = useState ( PRESET_OPTIONS [ 0 ] ) ;
249+ const [ preset , setPreset ] = useState ( presetOptions [ 0 ] ) ;
237250 const [ name , setName ] = useState ( initial ?. name ?? "" ) ;
238251 const [ baseUrl , setBaseUrl ] = useState ( initial ?. baseUrl ?? "" ) ;
239252 const [ models , setModels ] = useState ( initial ?. models . join ( "\n" ) ?? "" ) ;
@@ -242,7 +255,7 @@ function ProviderForm({
242255 const [ busy , setBusy ] = useState ( false ) ;
243256
244257 const applyPreset = ( i : number ) => {
245- const p = PRESET_OPTIONS [ i ] ;
258+ const p = presetOptions [ i ] ;
246259 setPreset ( p ) ;
247260 if ( ! initial ) {
248261 setName ( p . name ) ;
@@ -291,7 +304,7 @@ function ProviderForm({
291304 { ! initial && (
292305 < div style = { { marginBottom : 8 } } >
293306 < span style = { { marginRight : 6 } } > 模板:</ span >
294- { PRESET_OPTIONS . map ( ( p , i ) => (
307+ { presetOptions . map ( ( p , i ) => (
295308 < button
296309 key = { p . name }
297310 style = { { ...btn , marginRight : 4 , background : preset . name === p . name ? "#0d9488" : "#fff" , color : preset . name === p . name ? "#fff" : "#1f2937" , border : "none" } }
0 commit comments