1- import React , { useState , useMemo , useCallback } from "react" ;
2- import { X , ExternalLink , Wand2 , Eye , EyeOff } from "lucide-react" ;
1+ import React , { useState , useMemo , useCallback , useEffect } from "react" ;
2+ import { X , ExternalLink , Wand2 , Eye , EyeOff , Database } from "lucide-react" ;
33import { Provider , AppType } from "@/lib/api/switch" ;
44import { getConfig } from "@/hooks/useTauri" ;
55import { cn } from "@/lib/utils" ;
6+ import {
7+ providerPoolApi ,
8+ CredentialDisplay ,
9+ PoolProviderType ,
10+ } from "@/lib/api/providerPool" ;
611
712interface ProviderFormProps {
813 appType : AppType ;
@@ -20,6 +25,7 @@ type ProviderCategory =
2025 | "aggregator"
2126 | "third_party"
2227 | "proxy"
28+ | "credential_pool"
2329 | "custom" ;
2430
2531// 预设供应商接口
@@ -137,6 +143,13 @@ const presets: Record<AppType, ProviderPreset[]> = {
137143 iconColor : "#3b82f6" ,
138144 defaultBaseUrl : "http://127.0.0.1:3001" ,
139145 } ,
146+ // 从凭证池导入
147+ {
148+ id : "credential_pool" ,
149+ name : "从凭证池导入" ,
150+ category : "credential_pool" ,
151+ iconColor : "#22c55e" ,
152+ } ,
140153 // 自定义
141154 {
142155 id : "custom" ,
@@ -181,6 +194,13 @@ model = "gpt-4o"
181194 api_base_url : "http://127.0.0.1:3001/v1" ,
182195 } ,
183196 } ,
197+ // 从凭证池导入
198+ {
199+ id : "credential_pool" ,
200+ name : "从凭证池导入" ,
201+ category : "credential_pool" ,
202+ iconColor : "#22c55e" ,
203+ } ,
184204 // 自定义
185205 {
186206 id : "custom" ,
@@ -211,6 +231,13 @@ model = "gpt-4o"
211231 GEMINI_MODEL : "gemini-2.0-flash" ,
212232 } ,
213233 } ,
234+ // 从凭证池导入
235+ {
236+ id : "credential_pool" ,
237+ name : "从凭证池导入" ,
238+ category : "credential_pool" ,
239+ iconColor : "#22c55e" ,
240+ } ,
214241 // 自定义
215242 {
216243 id : "custom" ,
@@ -228,6 +255,7 @@ const categoryLabels: Record<ProviderCategory, string> = {
228255 aggregator : "聚合服务" ,
229256 third_party : "第三方" ,
230257 proxy : "本地代理" ,
258+ credential_pool : "从凭证池导入" ,
231259 custom : "自定义" ,
232260} ;
233261
@@ -450,6 +478,53 @@ export function ProviderForm({
450478 const [ error , setError ] = useState < string | null > ( null ) ;
451479 const [ showApiKey , setShowApiKey ] = useState ( false ) ;
452480
481+ // 凭证池相关状态
482+ const [ poolCredentials , setPoolCredentials ] = useState < CredentialDisplay [ ] > (
483+ [ ] ,
484+ ) ;
485+ const [ selectedCredentialUuid , setSelectedCredentialUuid ] = useState <
486+ string | null
487+ > ( null ) ;
488+ const [ loadingCredentials , setLoadingCredentials ] = useState ( false ) ;
489+
490+ // 加载凭证池数据
491+ const loadPoolCredentials = useCallback ( async ( ) => {
492+ setLoadingCredentials ( true ) ;
493+ try {
494+ const overview = await providerPoolApi . getOverview ( ) ;
495+ // 根据 appType 筛选相关的凭证类型
496+ const relevantTypes : PoolProviderType [ ] =
497+ appType === "claude"
498+ ? [ "claude" , "kiro" , "antigravity" ]
499+ : appType === "codex"
500+ ? [ "openai" , "codex" ]
501+ : appType === "gemini"
502+ ? [ "gemini" ]
503+ : [ ] ;
504+
505+ const credentials : CredentialDisplay [ ] = [ ] ;
506+ for ( const pool of overview ) {
507+ if ( relevantTypes . includes ( pool . provider_type as PoolProviderType ) ) {
508+ credentials . push (
509+ ...pool . credentials . filter ( ( c ) => c . is_healthy && ! c . is_disabled ) ,
510+ ) ;
511+ }
512+ }
513+ setPoolCredentials ( credentials ) ;
514+ } catch ( e ) {
515+ console . error ( "Failed to load pool credentials:" , e ) ;
516+ } finally {
517+ setLoadingCredentials ( false ) ;
518+ }
519+ } , [ appType ] ) ;
520+
521+ // 当选择"从凭证池导入"时加载凭证
522+ useEffect ( ( ) => {
523+ if ( selectedPresetId === "credential_pool" ) {
524+ loadPoolCredentials ( ) ;
525+ }
526+ } , [ selectedPresetId , loadPoolCredentials ] ) ;
527+
453528 // 当前选中的预设
454529 const selectedPreset = useMemo ( ( ) => {
455530 return appPresets . find ( ( p ) => p . id === selectedPresetId ) ;
@@ -548,6 +623,55 @@ export function ProviderForm({
548623 setGeminiEnv ( defaultGeminiEnv ) ;
549624 }
550625 }
626+
627+ // 凭证池预设:重置选中的凭证
628+ if ( preset . id === "credential_pool" ) {
629+ setSelectedCredentialUuid ( null ) ;
630+ }
631+ }
632+ } ;
633+
634+ // 处理凭证选择
635+ const handleCredentialSelect = async ( credential : CredentialDisplay ) => {
636+ setSelectedCredentialUuid ( credential . uuid ) ;
637+ setName ( credential . name || `${ credential . provider_type } 凭证` ) ;
638+ setIconColor ( "#22c55e" ) ;
639+
640+ // 根据凭证类型和 appType 设置配置
641+ // 使用 ProxyCast 代理,凭证池中的凭证通过本地代理访问
642+ try {
643+ const config = await getConfig ( ) ;
644+ const proxyApiKey = config . server . api_key || "" ;
645+ const proxyHost = config . server . host || "127.0.0.1" ;
646+ const proxyPort = config . server . port || 3001 ;
647+ const proxyBaseUrl = `http://${ proxyHost } :${ proxyPort } ` ;
648+
649+ if ( appType === "claude" ) {
650+ setApiKey ( proxyApiKey ) ;
651+ setBaseUrl ( proxyBaseUrl ) ;
652+ setJsonManuallyEdited ( false ) ;
653+ } else if ( appType === "codex" ) {
654+ setCodexAuth (
655+ JSON . stringify (
656+ {
657+ api_key : proxyApiKey ,
658+ api_base_url : `${ proxyBaseUrl } /v1` ,
659+ } ,
660+ null ,
661+ 2 ,
662+ ) ,
663+ ) ;
664+ } else if ( appType === "gemini" ) {
665+ setGeminiEnv (
666+ `GEMINI_API_KEY=${ proxyApiKey } \nGOOGLE_GEMINI_BASE_URL=${ proxyBaseUrl } \nGEMINI_MODEL=gemini-2.0-flash` ,
667+ ) ;
668+ }
669+
670+ setNotes (
671+ `使用凭证池: ${ credential . provider_type } - ${ credential . uuid . slice ( 0 , 8 ) } ` ,
672+ ) ;
673+ } catch ( e ) {
674+ console . error ( "Failed to load config:" , e ) ;
551675 }
552676 } ;
553677
@@ -680,6 +804,65 @@ export function ProviderForm({
680804 </ div >
681805 ) }
682806
807+ { /* 凭证池选择器(当选择"从凭证池导入"时显示) */ }
808+ { ! isEditMode && selectedPresetId === "credential_pool" && (
809+ < div className = "space-y-3" >
810+ < label className = "block text-sm font-medium flex items-center gap-2" >
811+ < Database className = "h-4 w-4" />
812+ 选择凭证
813+ </ label >
814+ { loadingCredentials ? (
815+ < div className = "flex items-center justify-center py-8 text-muted-foreground" >
816+ < div className = "animate-spin rounded-full h-5 w-5 border-2 border-primary border-t-transparent mr-2" />
817+ 加载凭证中...
818+ </ div >
819+ ) : poolCredentials . length === 0 ? (
820+ < div className = "text-center py-8 text-muted-foreground border border-dashed rounded-lg" >
821+ < Database className = "h-8 w-8 mx-auto mb-2 opacity-50" />
822+ < p > 暂无可用凭证</ p >
823+ < p className = "text-xs mt-1" > 请先在凭证池中添加凭证</ p >
824+ </ div >
825+ ) : (
826+ < div className = "grid gap-2 max-h-48 overflow-y-auto" >
827+ { poolCredentials . map ( ( credential ) => (
828+ < button
829+ key = { credential . uuid }
830+ type = "button"
831+ onClick = { ( ) => handleCredentialSelect ( credential ) }
832+ className = { cn (
833+ "flex items-center gap-3 p-3 rounded-lg border text-left transition-all" ,
834+ selectedCredentialUuid === credential . uuid
835+ ? "border-primary bg-primary/10"
836+ : "border-border hover:border-muted-foreground/50 hover:bg-muted/50" ,
837+ ) }
838+ >
839+ < div
840+ className = "w-3 h-3 rounded-full flex-shrink-0"
841+ style = { {
842+ backgroundColor : credential . is_healthy
843+ ? "#22c55e"
844+ : "#ef4444" ,
845+ } }
846+ />
847+ < div className = "flex-1 min-w-0" >
848+ < p className = "font-medium truncate" >
849+ { credential . name || credential . uuid . slice ( 0 , 8 ) }
850+ </ p >
851+ < p className = "text-xs text-muted-foreground" >
852+ { credential . provider_type } ·{ " " }
853+ { credential . uuid . slice ( 0 , 8 ) } ...
854+ </ p >
855+ </ div >
856+ { selectedCredentialUuid === credential . uuid && (
857+ < span className = "text-xs text-primary" > 已选择</ span >
858+ ) }
859+ </ button >
860+ ) ) }
861+ </ div >
862+ ) }
863+ </ div >
864+ ) }
865+
683866 { /* 基础字段 */ }
684867 < div className = "space-y-4" >
685868 < div >
0 commit comments