1import type { FieldPrediction, FieldType, MLFieldFeatures } from '../shared/ml';234const CLASSES: FieldType[] = ['password', 'email', 'username', 'totp', 'none'];567export const ML_FEATURE_KEYS: readonly (keyof MLFieldFeatures)[] = [8 9 'type_text', 'type_email', 'type_password', 'type_tel', 'type_number',10 'type_search', 'type_url', 'type_other',11 12 'auto_username', 'auto_email', 'auto_current_password', 'auto_new_password',13 'auto_one_time_code', 'auto_off', 'auto_other',14 15 'name_has_user', 'name_has_login', 'name_has_email', 'name_has_pass', 'name_length',16 17 'id_has_user', 'id_has_login', 'id_has_email', 'id_has_pass', 'id_length',18 19 'placeholder_has_user', 'placeholder_has_email', 'placeholder_has_pass', 'placeholder_length',20 21 'aria_label_has_user', 'aria_label_has_email', 'aria_label_has_pass', 'aria_label_length',22 23 'parent_is_form', 'parent_is_div', 'parent_is_section',24 25 'sibling_count', 'has_password_sibling', 'has_email_sibling',26 27 'form_has_submit', 'form_action_has_login',28 29 'is_required', 'has_placeholder', 'has_aria_label',30 31 'inputmode_numeric',32 33];34353637const B: number[] = [38 -1.5, 39 -1.5, 40 -2.0, 41 -3.0, 42 0.0, 43];44454647const W: number[][] = [48 49 [50 51 0.0, -1.0, 8.0, -0.5, -0.5, -2.0, -2.0, -1.0,52 53 -1.0, -1.0, 8.0, 7.0, -1.0, 0.5, 0.0,54 55 -0.5, 0.0, -0.5, 5.0, 0.0,56 57 -0.5, 0.0, -0.5, 5.0, 0.0,58 59 -0.5, -0.5, 4.0, 0.0,60 61 -0.5, -0.5, 4.0, 0.0,62 63 0.3, 0.0, 0.0,64 65 0.2, -0.5, 0.5,66 67 0.5, 0.5,68 69 0.3, 0.2, 0.2,70 71 -0.5,72 ],73 74 [75 76 0.3, 6.0, -2.0, -0.5, -1.0, -2.0, -1.0, -1.0,77 78 1.0, 7.0, -2.0, -2.0, -2.0, 0.0, 0.0,79 80 0.0, 0.5, 4.5, -1.0, 0.0,81 82 0.0, 0.5, 4.5, -1.0, 0.0,83 84 0.0, 4.0, -1.0, 0.0,85 86 0.0, 4.0, -1.0, 0.0,87 88 0.3, 0.0, 0.0,89 90 0.2, 0.5, -0.3,91 92 0.5, 0.5,93 94 0.3, 0.2, 0.2,95 96 -1.0,97 ],98 99 [100 101 1.5, 0.5, -2.0, -0.5, -1.0, -2.0, -1.0, -1.0,102 103 7.0, 1.0, -2.0, -2.0, -2.0, 0.0, 0.0,104 105 4.0, 3.5, 0.5, -1.0, 0.0,106 107 4.0, 3.5, 0.5, -1.0, 0.0,108 109 3.5, 0.5, -0.5, 0.0,110 111 3.5, 0.5, -0.5, 0.0,112 113 0.3, 0.0, 0.0,114 115 0.2, 2.0, 0.5,116 117 0.5, 1.0,118 119 0.3, 0.2, 0.2,120 121 -1.0,122 ],123 124 [125 126 0.0, -1.0, -2.0, 2.0, 3.0, -2.0, -2.0, -1.0,127 128 -2.0, -2.0, -2.0, -2.0, 9.0, 0.5, 0.0,129 130 -0.5, -0.5, -0.5, -0.5, 0.0,131 132 -0.5, -0.5, -0.5, -0.5, 0.0,133 134 -0.5, -0.5, -0.5, 0.0,135 136 -0.5, -0.5, -0.5, 0.0,137 138 0.2, 0.0, 0.0,139 140 0.2, -0.3, -0.3,141 142 0.3, 0.0,143 144 0.2, 0.1, 0.1,145 146 4.0,147 ],148 149 [150 151 0.5, -2.0, -3.0, 1.0, 1.5, 4.0, 4.0, 2.0,152 153 -2.0, -2.0, -3.0, -3.0, -3.0, 0.5, 0.2,154 155 -1.0, -1.0, -1.0, -1.0, 0.1,156 157 -1.0, -1.0, -1.0, -1.0, 0.1,158 159 -0.5, -0.5, -0.5, 0.0,160 161 -0.5, -0.5, -0.5, 0.0,162 163 -0.5, 0.0, 0.0,164 165 -0.3, -1.0, -0.5,166 167 -0.5, -1.0,168 169 -0.3, 0.0, 0.0,170 171 1.0,172 ],173];174175function softmax(logits: number[]): number[] {176 const max = Math.max(...logits);177 const exps = logits.map(x => Math.exp(x - max));178 const sum = exps.reduce((a, b) => a + b, 0);179 return exps.map(x => x / sum);180}181182function predict(features: MLFieldFeatures): FieldPrediction {183 const vec = ML_FEATURE_KEYS.map(k => features[k]);184 const logits = W.map((row, ci) =>185 B[ci] + row.reduce((s, w, fi) => s + w * vec[fi], 0)186 );187 const probs = softmax(logits);188 const maxIdx = probs.indexOf(Math.max(...probs));189 return {190 type: CLASSES[maxIdx],191 confidence: probs[maxIdx],192 probabilities: Object.fromEntries(193 CLASSES.map((t, i) => [t, probs[i]])194 ) as Record<FieldType, number>,195 };196}197198export function isValidMLFeatureBatch(value: unknown, maxFields: number): value is MLFieldFeatures[] {199 return Array.isArray(value) && value.length <= maxFields && value.every((item) =>200 typeof item === "object" && item !== null && ML_FEATURE_KEYS.every((key) =>201 typeof (item as Record<string, unknown>)[key] === "number" &&202 Number.isFinite((item as Record<string, unknown>)[key])203 )204 );205}206207export function predictBatch(featuresArray: MLFieldFeatures[]): FieldPrediction[] {208 return featuresArray.map(predict);209}