@@ -46,6 +46,8 @@ interface BatchClassifyDialogProps {
4646 uncategorizedRepos : Repository [ ] ;
4747 existingLists : { id : string ; name : string } [ ] ;
4848 onComplete : ( ) => void ;
49+ requestInterval ?: number ;
50+ concurrency ?: number ;
4951}
5052
5153export function BatchClassifyDialog ( {
@@ -54,6 +56,8 @@ export function BatchClassifyDialog({
5456 uncategorizedRepos,
5557 existingLists,
5658 onComplete,
59+ requestInterval = 1000 ,
60+ concurrency = 3 ,
5761} : BatchClassifyDialogProps ) {
5862 const [ phase , setPhase ] = useState < "confirm" | "processing" | "review" | "done" > ( "confirm" ) ;
5963 const [ progress , setProgress ] = useState ( 0 ) ;
@@ -124,32 +128,46 @@ export function BatchClassifyDialog({
124128 }
125129 } ;
126130
127- // 批量处理(串行 ,带间隔)
131+ // 批量处理(并行 ,带间隔)
128132 const startProcessing = async ( ) => {
129133 setPhase ( "processing" ) ;
130134 setProgress ( 0 ) ;
131135 setResults ( [ ] ) ;
132136 abortRef . current = false ;
133137
134138 const allResults : ClassifyResult [ ] = [ ] ;
135- const requestInterval = 2500 ; // 每个请求间隔 2.5 秒
139+ const queue = [ ...uncategorizedRepos ] ;
140+ let activeCount = 0 ;
141+ let completedCount = 0 ;
136142
137- for ( let i = 0 ; i < uncategorizedRepos . length ; i ++ ) {
138- if ( abortRef . current ) break ;
143+ const processNext = async ( ) : Promise < void > => {
144+ if ( abortRef . current || queue . length === 0 ) return ;
139145
140- const repo = uncategorizedRepos [ i ] ;
141- setCurrentRepo ( repo . fullName ) ;
146+ const repo = queue . shift ( ) ! ;
147+ activeCount ++ ;
148+ setCurrentRepo ( `${ repo . fullName } (+${ activeCount - 1 } 并行)` ) ;
142149
143150 const result = await classifyRepo ( repo ) ;
144151 allResults . push ( result ) ;
152+ completedCount ++ ;
153+ activeCount -- ;
154+
145155 setResults ( [ ...allResults ] ) ;
146- setProgress ( Math . round ( ( allResults . length / totalRepos ) * 100 ) ) ;
156+ setProgress ( Math . round ( ( completedCount / totalRepos ) * 100 ) ) ;
147157
148- // 请求间隔(最后一个不需要等待)
149- if ( i < uncategorizedRepos . length - 1 && ! abortRef . current ) {
158+ // 间隔后处理下一个
159+ if ( queue . length > 0 && ! abortRef . current ) {
150160 await delay ( requestInterval ) ;
161+ await processNext ( ) ;
151162 }
152- }
163+ } ;
164+
165+ // 启动并行任务
166+ const workers = Array ( Math . min ( concurrency , queue . length ) )
167+ . fill ( null )
168+ . map ( ( ) => processNext ( ) ) ;
169+
170+ await Promise . all ( workers ) ;
153171
154172 // 处理完成,分析结果
155173 processResults ( allResults ) ;
@@ -308,7 +326,7 @@ export function BatchClassifyDialog({
308326 < li > • 仅处理未分类的仓库</ li >
309327 < li > • 匹配现有 List 的会自动归类</ li >
310328 < li > • 建议新 List 的会让您确认后创建</ li >
311- < li > • 预计耗时:约 { Math . ceil ( totalRepos * 3 / 60 ) } 分钟(避免 API 限流 )</ li >
329+ < li > • 预计耗时:约 { Math . ceil ( totalRepos / concurrency * ( requestInterval / 1000 + 2 ) / 60 ) } 分钟({ concurrency } 并发 )</ li >
312330 </ ul >
313331 </ div >
314332 < div className = "flex justify-end gap-2" >
@@ -358,23 +376,25 @@ export function BatchClassifyDialog({
358376 < Checkbox
359377 checked = { suggestion . selected }
360378 onCheckedChange = { ( ) => toggleListSelection ( index ) }
379+ className = "mt-1 shrink-0"
361380 />
362- < div className = "flex-1 min-w-0" >
363- < div className = "flex items-center gap-2" >
364- < FolderPlus className = "h-4 w-4 text-primary" />
381+ < div className = "flex-1 min-w-0 overflow-hidden " >
382+ < div className = "flex items-center gap-2 flex-wrap " >
383+ < FolderPlus className = "h-4 w-4 text-primary shrink-0 " />
365384 < span className = "font-medium" > { suggestion . name } </ span >
366385 < span className = "text-xs text-muted-foreground" >
367386 ({ suggestion . repos . length } 个仓库)
368387 </ span >
369388 </ div >
370- < p className = "text-xs text-muted-foreground mt-1 truncate" >
371- { suggestion . repos . map ( ( r ) => r . name ) . join ( ", " ) }
389+ < p className = "text-xs text-muted-foreground mt-1 line-clamp-2" >
390+ { suggestion . repos . slice ( 0 , 5 ) . map ( ( r ) => r . name ) . join ( ", " ) }
391+ { suggestion . repos . length > 5 && ` 等 ${ suggestion . repos . length } 个` }
372392 </ p >
373393 </ div >
374394 </ div >
375395 ) ) }
376396 </ ScrollArea >
377- < div className = "flex justify-end gap-2" >
397+ < div className = "flex justify-end gap-2 pt-2 " >
378398 < Button variant = "outline" onClick = { ( ) => setPhase ( "done" ) } >
379399 跳过
380400 </ Button >
0 commit comments