1616// write will actually send. The editor and its commands arrive as props — the provider CRUD
1717// is `models`' duty, not `settings`'.
1818
19- import { useRef , useState , type Dispatch , type ReactElement , type SetStateAction } from "react" ;
19+ import { useEffect , useRef , useState , type Dispatch , type ReactElement , type SetStateAction } from "react" ;
2020import { DndContext , KeyboardSensor , PointerSensor , closestCenter , useSensor , useSensors , type DragEndEvent } from "@dnd-kit/core" ;
2121import { SortableContext , sortableKeyboardCoordinates , useSortable , verticalListSortingStrategy } from "@dnd-kit/sortable" ;
2222import { CSS } from "@dnd-kit/utilities" ;
@@ -115,6 +115,7 @@ export interface WebuiProviderFormPanelProps {
115115 readonly setProviderActionError : ( message ?: string ) => void ;
116116 readonly editor : WebuiProviderEditor ;
117117 readonly snapshot : WebuiProviderEditorSnapshot ;
118+ readonly onRegisterAddProviderAction : ( action ?: ( ) => void ) => void ;
118119 readonly providerPendingDelete ?: WebuiModelProviderView ;
119120 readonly setProviderPendingDelete : Dispatch < SetStateAction < WebuiModelProviderView | undefined > > ;
120121}
@@ -222,7 +223,7 @@ function ProviderPresetPicker({ controlId, presets, selected, selectedName, onSe
222223
223224export function ProviderFormPanel ( {
224225 providers, presets, commands, setProviderActionError,
225- editor, snapshot,
226+ editor, snapshot, onRegisterAddProviderAction ,
226227 providerPendingDelete, setProviderPendingDelete,
227228} : WebuiProviderFormPanelProps ) : ReactElement {
228229 const { dom } = useWebuiBrowserCapabilities ( ) ;
@@ -271,6 +272,12 @@ export function ProviderFormPanel({
271272 try { await commands . loadPresets ( ) ; }
272273 catch ( error ) { setProviderActionError ( error instanceof Error ? error . message : String ( error ) ) ; }
273274 } ;
275+ const openNewProviderRef = useRef < ( ) => void > ( ( ) => { } ) ;
276+ openNewProviderRef . current = ( ) => { void openProviderForm ( ) ; } ;
277+ useEffect ( ( ) => {
278+ onRegisterAddProviderAction ( ( ) => openNewProviderRef . current ( ) ) ;
279+ return ( ) => onRegisterAddProviderAction ( undefined ) ;
280+ } , [ onRegisterAddProviderAction ] ) ;
274281 // The dialog closes only on a successful delete; a refusal stays beside it,
275282 // in the editor's error channel.
276283 const confirmDeleteProvider = async ( ) => {
@@ -322,7 +329,6 @@ export function ProviderFormPanel({
322329 } } > < SortableContext items = { ids } strategy = { verticalListSortingStrategy } > < div className = "w-full rounded-[16px] bg-bg_grouped_tertiary p-1" > { models . map ( ( model , index ) => < SortableModelRow key = { ids [ index ] } id = { ids [ index ] ! } model = { model } onTest = { ( ) => void commands . testModel ( { providerId : id , modelId : textOf ( model . modelId ) } ) } onToggle = { ( enabled ) => void toggleModel ( id , model , enabled ) } /> ) } </ div > </ SortableContext > </ DndContext > : null }
323330 </ WebuiStack > ;
324331 } ) }
325- < WebuiButton variant = "plain" className = "h-9 w-fit px-3" disabled = { ! commands . canManageProviders } onClick = { ( ) => void openProviderForm ( ) } > 添加模型</ WebuiButton >
326332 < WebuiDialog id = "provider-form" open = { showProviderForm } title = { editingProvider ? "编辑提供商" : "添加模型" } onClose = { ( ) => editor . close ( ) } dismissOnEscape portalTarget = { portalTarget } width = "standard" footer = { < >
327333 < WebuiInline as = "label" className = "webui-provider-form-footer-leading" > < WebuiCheckbox checked = { skipTest } onChange = { ( event ) => editor . setSkipConnectionTest ( event . target . checked ) } /> 跳过连通检测</ WebuiInline >
328334 < WebuiDialogActions >
@@ -346,7 +352,7 @@ export function ProviderFormPanel({
346352 < WebuiInline justify = "between" gap = "medium" className = "webui-provider-form-models-header" > < span className = "text-[14px] font-medium leading-5 text-text_default_primary" > 模型</ span > < WebuiInline gap = "small" > < WebuiButton variant = "outline-secondary" size = "compact" onClick = { ( ) => editor . addModel ( ) } > 添加模型</ WebuiButton > < WebuiButton variant = "quiet-highlight" size = "compact" disabled = { ! snapshot . canDiscover || formBusy } onClick = { ( ) => void editor . discover ( ) } > 自动获取</ WebuiButton > </ WebuiInline > </ WebuiInline >
347353 { draftModels . length ? draftModels . map ( ( model , index ) => < ProviderModelCard key = { model . id } model = { model } index = { index } busy = { formBusy } onChange = { ( patch ) => editor . changeModel ( model . id , patch ) } onTest = { ( ) => void editor . testModel ( model . id ) } onRemove = { ( ) => editor . removeModel ( model . id ) } /> ) : < FeedbackEmpty className = "webui-provider-form-models-empty" title = { "请至少添加一个模型" } /> }
348354 </ WebuiStack >
349- < WebuiStack gap = "small" className = "webui-provider-form-headers" > < div className = "text-[13px] font-medium leading-5 text-text_default_primary" > 自定义 Headers</ div > { draftHeaders . map ( ( header ) => < WebuiInline key = { header . id } gap = "small" className = "webui-provider-form-header-row" > < WebuiTextInput controlSize = "dense" className = "flex-1" aria-label = "Header 名称" placeholder = "Header 名称" value = { header . name } onChange = { ( event ) => editor . setHeaders ( draftHeaders . map ( ( item ) => item . id === header . id ? { ...item , name : event . target . value } : item ) ) } /> < WebuiTextInput controlSize = "dense" className = "flex-1" aria-label = "Header 值" placeholder = { header . persistedName ? "留空保留原值" : "Header 值" } value = { header . value } onChange = { ( event ) => editor . setHeaders ( draftHeaders . map ( ( item ) => item . id === header . id ? { ...item , value : event . target . value } : item ) ) } /> < IconButton label = "移除 Header" onClick = { ( ) => editor . removeHeader ( header . id ) } > < IconTrash aria-hidden = "true" size = { 16 } stroke = { 1.7 } /> </ IconButton > </ WebuiInline > ) } < WebuiButton variant = "outline-secondary" size = "compact" onClick = { ( ) => editor . addHeader ( ) } > < IconPlus aria-hidden = "true" size = { 16 } stroke = { 1.7 } /> 添加 Header</ WebuiButton > </ WebuiStack >
355+ < WebuiStack gap = "small" className = "webui-provider-form-headers" > < div className = "text-[13px] font-medium leading-5 text-text_default_primary" > 自定义 Headers</ div > { draftHeaders . map ( ( header ) => < WebuiInline key = { header . id } gap = "small" className = "webui-provider-form-header-row" > < WebuiTextInput controlSize = "dense" className = "flex-1" aria-label = "Header 名称" placeholder = "Header 名称" value = { header . name } onChange = { ( event ) => editor . setHeaders ( draftHeaders . map ( ( item ) => item . id === header . id ? { ...item , name : event . target . value } : item ) ) } /> < WebuiTextInput controlSize = "dense" className = "flex-1" aria-label = "Header 值" placeholder = { header . persistedName ? "留空保留原值" : "Header 值" } value = { header . value } onChange = { ( event ) => editor . setHeaders ( draftHeaders . map ( ( item ) => item . id === header . id ? { ...item , value : event . target . value } : item ) ) } /> < IconButton label = "移除 Header" onClick = { ( ) => editor . removeHeader ( header . id ) } > < IconTrash aria-hidden = "true" size = { 16 } stroke = { 1.7 } /> </ IconButton > </ WebuiInline > ) } < WebuiButton variant = "outline-secondary" size = "compact" className = "w-fit self-start" onClick = { ( ) => editor . addHeader ( ) } > < IconPlus aria-hidden = "true" size = { 16 } stroke = { 1.7 } /> 添加 Header</ WebuiButton > </ WebuiStack >
350356 { formError ? < FeedbackError message = { formError } className = "webui-provider-form-error" /> : null }
351357 </ WebuiStack >
352358 </ form >
0 commit comments