2026-07-10 16:46:23 +08:00
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
2026-07-13 15:28:17 +08:00
import ts from 'typescript'
2026-07-10 16:46:23 +08:00
const root = resolve ( fileURLToPath ( new URL ( '..' , import . meta . url ) ) )
const source = readFileSync ( resolve ( root , 'src/views/fine-tune/FineTuneCreateView.vue' ) , 'utf8' )
2026-07-11 11:00:39 +08:00
const modelDialogSource = readFileSync ( resolve ( root , 'src/components/ModelSelectDialog.vue' ) , 'utf8' )
2026-07-13 15:28:17 +08:00
const formModelSource = readFileSync ( resolve ( root , 'src/views/fine-tune/fineTuneFormModel.ts' ) , 'utf8' )
2026-07-10 16:46:23 +08:00
function assert ( condition , message ) {
if ( ! condition ) {
throw new Error ( message )
}
}
assert ( source . includes ( ':rows="4"' ) , '任务描述 textarea should be taller than the original 2 rows' )
assert ( source . includes ( 'gpu-index' ) , 'GPU cards should use a restrained index label' )
assert ( source . includes ( 'GPU-{{ idx }}' ) , 'GPU cards should show the GPU number in a compact format' )
assert ( source . includes ( 'gpu-usage-bar' ) , 'GPU cards should visualize usage with an enterprise-style progress bar' )
assert ( source . includes ( 'is-busy' ) , 'GPU cards should have a distinct busy state for usage over 80%' )
assert ( source . includes ( 'gpu.gpu_percent > 80' ) , 'GPU busy state should be driven by usage over 80%' )
assert ( source . includes ( 'modelDialogVisible' ) , 'Model selection should open a dialog instead of a plain select' )
2026-07-11 11:00:39 +08:00
assert ( ! modelDialogSource . includes ( 'width="78vw"' ) , 'Model dialog should not use the previous oversized 78vw width' )
assert ( modelDialogSource . includes ( 'width="860px"' ) , 'Model dialog should use a compact enterprise modal width' )
assert ( modelDialogSource . includes ( 'model-series-list' ) , 'Model dialog should include a model series list column' )
assert ( modelDialogSource . includes ( 'model-version-list' ) , 'Model dialog should include a snapshot/version list column' )
assert ( modelDialogSource . includes ( 'handleConfirm' ) , 'Model dialog should confirm the selected model before updating the form' )
2026-07-13 15:28:17 +08:00
assert ( formModelSource . includes ( 'auto_merge: false' ) , 'Auto merge should default to disabled' )
2026-07-11 11:00:39 +08:00
assert ( source . includes ( 'v-if="form.train_type === \'SFT\'"' ) , 'Merge model settings should only be visible for SFT' )
assert ( source . includes ( 'content-position="left">合并模型' ) , 'SFT form should include a merge model section below data configuration' )
assert ( source . includes ( 'v-model="form.auto_merge"' ) , 'Merge model section should provide an auto merge selector' )
assert ( source . includes ( 'label="自动合并权重并保存"' ) , 'Auto merge selector should use a clear visible label' )
2026-07-13 15:28:17 +08:00
assert ( formModelSource . includes ( "auto_merge: form.train_type === 'SFT' && form.auto_merge" ) , 'Auto merge should be normalized by the shared payload builder' )
assert ( source . includes ( 'const payload = buildFineTunePayload(form, selectedGpus.value)' ) , 'Create and start should share one normalized payload' )
assert ( source . includes ( 'startFineTune({ ...payload, task_id: taskId })' ) , 'Start request should reuse the normalized payload' )
assert ( source . includes ( 'Object.assign(form, DEFAULT_TRAINING_PARAMS)' ) , 'Reset should reuse the canonical defaults' )
assert ( ! source . includes ( 'const taskData = {' ) , 'The duplicated create payload should be removed' )
assert ( ! source . includes ( 'const createRes: any' ) , 'Create response should use the API return type' )
assert ( ! source . includes ( '(check as any).exists' ) , 'Name-check response should use its API return type' )
assert ( source . includes ( '任务名校验失败' ) , 'Name-check failures should be visible and block submission' )
const runnableSource = ts . transpileModule (
formModelSource . replace ( "import type { FineTuneStartPayload, FineTuneTask } from '@/types'" , '' ) ,
{ compilerOptions : { module : ts . ModuleKind . ESNext , target : ts . ScriptTarget . ES2022 } } ,
) . outputText
const model = await import ( ` data:text/javascript;base64, ${ Buffer . from ( runnableSource ) . toString ( 'base64' ) } ` )
const defaults = model . createDefaultFineTuneForm ( )
const customized = { ... defaults , train _type : 'DPO' , train _method : 'full' , auto _merge : true , quantization _bit : 4 }
const payload = model . buildFineTunePayload ( customized , [ 0 , 2 ] )
assert ( payload . auto _merge === false , 'Non-SFT tasks must never enable auto merge' )
assert ( payload . quantization _bit === 0 , 'Full fine-tuning must never send QLoRA quantization' )
assert ( payload . gpus . join ( ',' ) === '0,2' , 'Selected GPUs should be preserved in the shared payload' )
const resetDefaults = { ... model . DEFAULT _TRAINING _PARAMS }
assert ( resetDefaults . lora _alpha === defaults . lora _alpha , 'Reset and initial defaults must share LoRA values' )
2026-07-10 16:46:23 +08:00
console . log ( 'fine-tune create UI regression checks passed' )