@@ -14,6 +14,7 @@ import { ping } from 'free-coding-models/src/core/ping.js'
1414import { benchmarkModel } from 'free-coding-models/src/core/benchmark.js'
1515import { loadAllApiKeys } from './api-keys.js'
1616import { parseSweScore } from './model-ranker.js'
17+ import chalk from 'chalk'
1718
1819/**
1920 * @typedef {object } ScannedModel
@@ -81,15 +82,42 @@ export async function directScan(options = {}) {
8182 const totalPings = sortedCandidates . length
8283 let completedPings = 0
8384
84- if ( options . onProgress ) {
85- options . onProgress ( `📡 Pinging models: 0% (0/${ totalPings } )` )
85+ const spinnerFrames = [ '⠋' , '⠙' , '⠹' , '⠸' , '⠼' , '⠴' , '⠦' , '⠧' , '⠇' , '⠏' ]
86+ let spinnerIndex = 0
87+ let currentAction = 'Probing'
88+ let currentTargets = [ ]
89+ let pct = 0
90+ let completed = 0
91+ let total = totalPings
92+
93+ const updateProgress = ( ) => {
94+ if ( ! options . onProgress ) return
95+ const spinner = chalk . bold . magenta ( spinnerFrames [ spinnerIndex ] )
96+ const actionStr = chalk . bold . yellow ( `${ currentAction } :` )
97+ const targetStr = currentTargets . length > 0
98+ ? chalk . cyan ( currentTargets . slice ( - 2 ) . join ( ', ' ) )
99+ : chalk . gray ( '...' )
100+ const pctStr = chalk . bold . cyan ( `${ pct } %` )
101+ const counterStr = chalk . gray ( `(${ completed } /${ total } )` )
102+
103+ options . onProgress ( `${ spinner } ${ actionStr } ${ targetStr } — ${ pctStr } ${ counterStr } ` )
86104 }
87105
106+ const intervalId = setInterval ( ( ) => {
107+ spinnerIndex = ( spinnerIndex + 1 ) % spinnerFrames . length
108+ updateProgress ( )
109+ } , 80 )
110+
88111 // 📖 Step 3: Ping candidate models in parallel (15s timeout inside ping.js)
89112 const pingPromises = sortedCandidates . map ( async ( candidate ) => {
90- const { modelId, providerKey, sourceInfo } = candidate
113+ const { modelId, providerKey, sourceInfo, label } = candidate
91114 const apiKey = keys . get ( providerKey ) || null
92115 const url = sourceInfo . url
116+ const providerName = sourceInfo . name || providerKey
117+ const targetDesc = `${ label } [${ providerName } ]`
118+
119+ currentTargets . push ( targetDesc )
120+ updateProgress ( )
93121
94122 try {
95123 const res = await ping ( apiKey , modelId , providerKey , url )
@@ -105,7 +133,7 @@ export async function directScan(options = {}) {
105133 return {
106134 ...candidate ,
107135 apiKey,
108- providerName : sourceInfo . name || providerKey ,
136+ providerName,
109137 providerUrl : url ,
110138 status,
111139 latencyMs : typeof res . ms === 'number' ? res . ms : null ,
@@ -117,7 +145,7 @@ export async function directScan(options = {}) {
117145 return {
118146 ...candidate ,
119147 apiKey,
120- providerName : sourceInfo . name || providerKey ,
148+ providerName,
121149 providerUrl : url ,
122150 status : 'down' ,
123151 latencyMs : null ,
@@ -127,10 +155,10 @@ export async function directScan(options = {}) {
127155 }
128156 } finally {
129157 completedPings ++
130- const pct = Math . round ( ( completedPings / totalPings ) * 100 )
131- if ( options . onProgress ) {
132- options . onProgress ( `📡 Pinging models: ${ pct } % ( ${ completedPings } / ${ totalPings } )` )
133- }
158+ pct = Math . round ( ( completedPings / totalPings ) * 100 )
159+ completed = completedPings
160+ currentTargets = currentTargets . filter ( t => t !== targetDesc )
161+ updateProgress ( )
134162 }
135163 } )
136164
@@ -147,24 +175,31 @@ export async function directScan(options = {}) {
147175 const usableAlive = aliveModels . filter ( m => m . status === 'up' )
148176
149177 if ( usableAlive . length === 0 ) {
178+ clearInterval ( intervalId )
150179 return aliveModels // 📖 Return what we have (mostly timeouts/auth_errors)
151180 }
152181
153182 // 📖 Step 4: Run AI Latency + TPS Benchmark on top 5 alive candidates
154- // 📖 Sort them again by SWE-bench to benchmark the smartest ones
155183 const benchmarkCandidates = usableAlive
156184 . sort ( ( a , b ) => parseSweScore ( b . sweScore ) - parseSweScore ( a . sweScore ) )
157185 . slice ( 0 , 5 )
158186
187+ currentAction = 'Benchmarking'
188+ completed = 0
189+ pct = 0
190+ total = benchmarkCandidates . length
191+ currentTargets = [ ]
192+ updateProgress ( )
193+
159194 const totalBenchmarks = benchmarkCandidates . length
160195 let completedBenchmarks = 0
161196
162- if ( options . onProgress && totalBenchmarks > 0 ) {
163- options . onProgress ( `⚡ AI latency bench: 0% (0/${ totalBenchmarks } )` )
164- }
165-
166197 const benchmarkPromises = benchmarkCandidates . map ( async ( model ) => {
167- const { modelId, providerKey, providerUrl, apiKey } = model
198+ const { modelId, providerKey, providerUrl, apiKey, label, providerName } = model
199+ const targetDesc = `${ label } [${ providerName } ]`
200+ currentTargets . push ( targetDesc )
201+ updateProgress ( )
202+
168203 try {
169204 // 📖 Limit benchmark retries to 1 with a short delay for speed
170205 const res = await benchmarkModel ( {
@@ -187,15 +222,17 @@ export async function directScan(options = {}) {
187222 // 📖 Catch benchmark errors gracefully
188223 } finally {
189224 completedBenchmarks ++
190- const pct = Math . round ( ( completedBenchmarks / totalBenchmarks ) * 100 )
191- if ( options . onProgress ) {
192- options . onProgress ( `⚡ AI latency bench: ${ pct } % ( ${ completedBenchmarks } / ${ totalBenchmarks } )` )
193- }
225+ pct = Math . round ( ( completedBenchmarks / totalBenchmarks ) * 100 )
226+ completed = completedBenchmarks
227+ currentTargets = currentTargets . filter ( t => t !== targetDesc )
228+ updateProgress ( )
194229 }
195230 return { modelId, tps : null , totalMs : null }
196231 } )
197232
198233 const benchmarkResults = await Promise . allSettled ( benchmarkPromises )
234+ clearInterval ( intervalId )
235+
199236 const benchMap = new Map ( )
200237
201238 for ( const res of benchmarkResults ) {
0 commit comments