Create app/micro_distillv2.html
Browse files- app/micro_distillv2.html +2486 -0
app/micro_distillv2.html
ADDED
|
@@ -0,0 +1,2486 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<meta name="theme-color" content="#000000">
|
| 7 |
+
<meta name="description" content="Micro Distillery — build, train, and export a small language model from free open data. HuggingFace-ready .zip bundle.">
|
| 8 |
+
<title>Micro Distillery</title>
|
| 9 |
+
<link rel="manifest" href="manifest.json">
|
| 10 |
+
<link rel="apple-touch-icon" href="icon-192.png">
|
| 11 |
+
<style>
|
| 12 |
+
:root {
|
| 13 |
+
--bg: #000;
|
| 14 |
+
--panel: #0a0a0a;
|
| 15 |
+
--panel-2: #111;
|
| 16 |
+
--panel-3: #161616;
|
| 17 |
+
--border: #222;
|
| 18 |
+
--border-hi: #333;
|
| 19 |
+
--border-focus: #555;
|
| 20 |
+
--text: #e5e5e5;
|
| 21 |
+
--text-dim: #9a9a9a;
|
| 22 |
+
--text-mute: #5f5f5f;
|
| 23 |
+
--white: #fff;
|
| 24 |
+
--err: #ef4444;
|
| 25 |
+
--radius: 4px;
|
| 26 |
+
--mono: 'SF Mono', Monaco, 'Cascadia Code', 'Courier New', monospace;
|
| 27 |
+
--sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 28 |
+
}
|
| 29 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 30 |
+
html, body { background: var(--bg); color: var(--text); }
|
| 31 |
+
body {
|
| 32 |
+
font-family: var(--sans);
|
| 33 |
+
font-size: 13px;
|
| 34 |
+
line-height: 1.5;
|
| 35 |
+
min-height: 100vh;
|
| 36 |
+
-webkit-font-smoothing: antialiased;
|
| 37 |
+
}
|
| 38 |
+
button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
|
| 39 |
+
input, select, textarea { font-family: inherit; }
|
| 40 |
+
|
| 41 |
+
.hdr {
|
| 42 |
+
border-bottom: 1px solid var(--border);
|
| 43 |
+
padding: 12px 20px;
|
| 44 |
+
display: flex;
|
| 45 |
+
align-items: center;
|
| 46 |
+
justify-content: space-between;
|
| 47 |
+
gap: 16px;
|
| 48 |
+
position: sticky;
|
| 49 |
+
top: 0;
|
| 50 |
+
background: rgba(0,0,0,0.95);
|
| 51 |
+
backdrop-filter: blur(8px);
|
| 52 |
+
z-index: 50;
|
| 53 |
+
flex-wrap: wrap;
|
| 54 |
+
}
|
| 55 |
+
.brand { display: flex; align-items: center; gap: 10px; }
|
| 56 |
+
.brand-mark {
|
| 57 |
+
font-family: var(--mono);
|
| 58 |
+
font-size: 12px;
|
| 59 |
+
font-weight: 700;
|
| 60 |
+
color: var(--white);
|
| 61 |
+
border: 1px solid var(--border-hi);
|
| 62 |
+
padding: 4px 8px;
|
| 63 |
+
border-radius: 3px;
|
| 64 |
+
letter-spacing: 0.05em;
|
| 65 |
+
}
|
| 66 |
+
.brand-name { font-weight: 600; font-size: 14px; letter-spacing: -0.01em; }
|
| 67 |
+
.brand-version { font-family: var(--mono); font-size: 11px; color: var(--text-mute); }
|
| 68 |
+
.hdr-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
| 69 |
+
|
| 70 |
+
.btn {
|
| 71 |
+
font-size: 12px;
|
| 72 |
+
font-weight: 500;
|
| 73 |
+
padding: 8px 14px;
|
| 74 |
+
border: 1px solid var(--border-hi);
|
| 75 |
+
border-radius: var(--radius);
|
| 76 |
+
background: var(--panel);
|
| 77 |
+
color: var(--text);
|
| 78 |
+
transition: all 0.15s ease;
|
| 79 |
+
white-space: nowrap;
|
| 80 |
+
}
|
| 81 |
+
.btn:hover:not(:disabled) { background: var(--panel-2); border-color: var(--border-focus); }
|
| 82 |
+
.btn:active:not(:disabled) { background: var(--panel-3); }
|
| 83 |
+
.btn:disabled { opacity: 0.35; cursor: not-allowed; }
|
| 84 |
+
.btn-primary { background: var(--white); color: #000; border-color: var(--white); font-weight: 600; }
|
| 85 |
+
.btn-primary:hover:not(:disabled) { background: #d4d4d4; border-color: #d4d4d4; }
|
| 86 |
+
.btn-block { width: 100%; display: block; }
|
| 87 |
+
.btn-sm { padding: 6px 10px; font-size: 11px; }
|
| 88 |
+
.btn-lg { padding: 12px 20px; font-size: 13px; }
|
| 89 |
+
.btn-ghost { background: transparent; border-color: var(--border); color: var(--text-dim); }
|
| 90 |
+
.btn-ghost:hover:not(:disabled) { color: var(--text); border-color: var(--border-hi); }
|
| 91 |
+
|
| 92 |
+
.main {
|
| 93 |
+
max-width: 1440px;
|
| 94 |
+
margin: 0 auto;
|
| 95 |
+
padding: 20px;
|
| 96 |
+
display: grid;
|
| 97 |
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
|
| 98 |
+
gap: 20px;
|
| 99 |
+
align-items: start;
|
| 100 |
+
}
|
| 101 |
+
@media (max-width: 900px) { .main { grid-template-columns: 1fr; } }
|
| 102 |
+
.col { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
|
| 103 |
+
|
| 104 |
+
.panel {
|
| 105 |
+
background: var(--panel);
|
| 106 |
+
border: 1px solid var(--border);
|
| 107 |
+
border-radius: var(--radius);
|
| 108 |
+
overflow: hidden;
|
| 109 |
+
}
|
| 110 |
+
.panel-hdr {
|
| 111 |
+
padding: 12px 16px;
|
| 112 |
+
border-bottom: 1px solid var(--border);
|
| 113 |
+
display: flex;
|
| 114 |
+
align-items: center;
|
| 115 |
+
justify-content: space-between;
|
| 116 |
+
gap: 10px;
|
| 117 |
+
}
|
| 118 |
+
.panel-title {
|
| 119 |
+
font-size: 12px;
|
| 120 |
+
font-weight: 600;
|
| 121 |
+
letter-spacing: 0.02em;
|
| 122 |
+
text-transform: uppercase;
|
| 123 |
+
color: var(--text-dim);
|
| 124 |
+
}
|
| 125 |
+
.panel-body { padding: 16px; }
|
| 126 |
+
|
| 127 |
+
.pipeline { display: flex; flex-direction: column; }
|
| 128 |
+
.step {
|
| 129 |
+
padding: 14px 16px;
|
| 130 |
+
border-bottom: 1px solid var(--border);
|
| 131 |
+
display: flex;
|
| 132 |
+
gap: 12px;
|
| 133 |
+
align-items: flex-start;
|
| 134 |
+
}
|
| 135 |
+
.step:last-child { border-bottom: none; }
|
| 136 |
+
.step-num {
|
| 137 |
+
flex-shrink: 0;
|
| 138 |
+
width: 24px;
|
| 139 |
+
height: 24px;
|
| 140 |
+
border-radius: 50%;
|
| 141 |
+
border: 1px solid var(--border-hi);
|
| 142 |
+
display: flex;
|
| 143 |
+
align-items: center;
|
| 144 |
+
justify-content: center;
|
| 145 |
+
font-family: var(--mono);
|
| 146 |
+
font-size: 11px;
|
| 147 |
+
font-weight: 600;
|
| 148 |
+
color: var(--text-dim);
|
| 149 |
+
transition: all 0.2s;
|
| 150 |
+
}
|
| 151 |
+
.step.active .step-num { border-color: var(--white); background: var(--white); color: #000; }
|
| 152 |
+
.step.done .step-num { background: var(--white); border-color: var(--white); color: #000; }
|
| 153 |
+
.step-body { flex: 1; min-width: 0; }
|
| 154 |
+
.step-title { font-size: 13px; font-weight: 600; margin-bottom: 2px; }
|
| 155 |
+
.step-desc { font-size: 12px; color: var(--text-dim); margin-bottom: 10px; }
|
| 156 |
+
.step-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
| 157 |
+
|
| 158 |
+
.setting { margin-bottom: 14px; }
|
| 159 |
+
.setting:last-child { margin-bottom: 0; }
|
| 160 |
+
.setting-label { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 6px; color: var(--text-dim); }
|
| 161 |
+
.setting-val { font-family: var(--mono); color: var(--text); font-size: 11px; }
|
| 162 |
+
input[type="range"] {
|
| 163 |
+
width: 100%; height: 2px; background: var(--border-hi); border-radius: 1px;
|
| 164 |
+
outline: none; -webkit-appearance: none; appearance: none;
|
| 165 |
+
}
|
| 166 |
+
input[type="range"]::-webkit-slider-thumb {
|
| 167 |
+
-webkit-appearance: none; width: 14px; height: 14px; background: var(--white);
|
| 168 |
+
border-radius: 50%; cursor: pointer; border: 2px solid var(--bg);
|
| 169 |
+
}
|
| 170 |
+
input[type="range"]::-moz-range-thumb {
|
| 171 |
+
width: 14px; height: 14px; background: var(--white); border-radius: 50%;
|
| 172 |
+
cursor: pointer; border: 2px solid var(--bg);
|
| 173 |
+
}
|
| 174 |
+
select {
|
| 175 |
+
width: 100%; background: var(--panel-2); color: var(--text);
|
| 176 |
+
border: 1px solid var(--border-hi); border-radius: var(--radius);
|
| 177 |
+
padding: 7px 10px; font-size: 12px; outline: none;
|
| 178 |
+
}
|
| 179 |
+
select:focus { border-color: var(--border-focus); }
|
| 180 |
+
|
| 181 |
+
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
| 182 |
+
.metric {
|
| 183 |
+
padding: 10px 8px; background: var(--panel-2); border: 1px solid var(--border);
|
| 184 |
+
border-radius: 3px; text-align: center;
|
| 185 |
+
}
|
| 186 |
+
.metric-label { font-size: 10px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 3px; }
|
| 187 |
+
.metric-value { font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--white); }
|
| 188 |
+
|
| 189 |
+
.tabs { display: flex; border-bottom: 1px solid var(--border); background: var(--panel-2); }
|
| 190 |
+
.tab {
|
| 191 |
+
padding: 10px 16px; font-size: 12px; color: var(--text-mute);
|
| 192 |
+
border-bottom: 2px solid transparent; transition: all 0.15s;
|
| 193 |
+
}
|
| 194 |
+
.tab:hover { color: var(--text-dim); }
|
| 195 |
+
.tab.active { color: var(--white); border-bottom-color: var(--white); }
|
| 196 |
+
.tabpanel { display: none; }
|
| 197 |
+
.tabpanel.active { display: block; }
|
| 198 |
+
|
| 199 |
+
.terminal {
|
| 200 |
+
height: 300px; overflow-y: auto; padding: 12px 14px; background: #000;
|
| 201 |
+
font-family: var(--mono); font-size: 11.5px; line-height: 1.55; color: var(--text-dim);
|
| 202 |
+
}
|
| 203 |
+
.log-line { margin-bottom: 1px; }
|
| 204 |
+
.log-line .ts { color: var(--text-mute); margin-right: 8px; }
|
| 205 |
+
.log-line.ok { color: var(--text); }
|
| 206 |
+
.log-line.warn { color: #a1a1aa; }
|
| 207 |
+
.log-line.err { color: var(--err); }
|
| 208 |
+
|
| 209 |
+
#kg-canvas { display: block; width: 100%; height: 300px; background: #000; }
|
| 210 |
+
.graph-tools {
|
| 211 |
+
display: flex; gap: 6px; padding: 10px 14px;
|
| 212 |
+
border-top: 1px solid var(--border); background: var(--panel-2); flex-wrap: wrap;
|
| 213 |
+
}
|
| 214 |
+
.graph-tools .btn { flex: 1; min-width: 90px; padding: 6px 8px; font-size: 11px; }
|
| 215 |
+
|
| 216 |
+
.api-log {
|
| 217 |
+
height: 300px; overflow-y: auto; padding: 12px 14px; background: #000;
|
| 218 |
+
font-family: var(--mono); font-size: 11.5px; line-height: 1.55;
|
| 219 |
+
white-space: pre-wrap;
|
| 220 |
+
}
|
| 221 |
+
.api-line { padding: 1px 0; color: var(--text-dim); }
|
| 222 |
+
.api-line .ts { color: var(--text-mute); margin-right: 8px; }
|
| 223 |
+
.api-line.ok { color: var(--text); }
|
| 224 |
+
.api-line.err { color: var(--err); }
|
| 225 |
+
|
| 226 |
+
.progress { height: 3px; background: var(--border); border-radius: 2px; overflow: hidden; }
|
| 227 |
+
.progress-fill { height: 100%; background: var(--white); width: 0%; transition: width 0.3s ease; }
|
| 228 |
+
.progress-meta {
|
| 229 |
+
display: flex; justify-content: space-between; font-size: 11px;
|
| 230 |
+
color: var(--text-mute); margin-top: 6px; font-family: var(--mono);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.modal {
|
| 234 |
+
position: fixed; inset: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(6px);
|
| 235 |
+
display: flex; align-items: center; justify-content: center; z-index: 100;
|
| 236 |
+
padding: 20px; opacity: 0; pointer-events: none; transition: opacity 0.2s ease;
|
| 237 |
+
}
|
| 238 |
+
.modal.show { opacity: 1; pointer-events: auto; }
|
| 239 |
+
.modal.hidden { display: none; }
|
| 240 |
+
.modal-box {
|
| 241 |
+
background: var(--panel); border: 1px solid var(--border-hi); border-radius: 8px;
|
| 242 |
+
width: 100%; max-width: 640px; max-height: 90vh; display: flex; flex-direction: column;
|
| 243 |
+
overflow: hidden; box-shadow: 0 30px 80px rgba(0,0,0,0.8);
|
| 244 |
+
}
|
| 245 |
+
.modal-box.wide { max-width: 980px; }
|
| 246 |
+
.modal-hdr {
|
| 247 |
+
padding: 18px 22px; border-bottom: 1px solid var(--border);
|
| 248 |
+
display: flex; justify-content: space-between; align-items: center; gap: 12px;
|
| 249 |
+
}
|
| 250 |
+
.modal-title { font-size: 14px; font-weight: 600; }
|
| 251 |
+
.modal-close {
|
| 252 |
+
width: 28px; height: 28px; border-radius: 4px; display: flex;
|
| 253 |
+
align-items: center; justify-content: center; color: var(--text-dim);
|
| 254 |
+
font-size: 18px; line-height: 1;
|
| 255 |
+
}
|
| 256 |
+
.modal-close:hover { background: var(--panel-2); color: var(--text); }
|
| 257 |
+
.modal-body { padding: 22px; overflow-y: auto; flex: 1; }
|
| 258 |
+
.modal-foot {
|
| 259 |
+
padding: 16px 22px; border-top: 1px solid var(--border);
|
| 260 |
+
display: flex; justify-content: space-between; align-items: center;
|
| 261 |
+
gap: 12px; flex-wrap: wrap;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.wiz-header { padding: 20px 24px 0; }
|
| 265 |
+
.wiz-progress { display: flex; gap: 4px; margin-bottom: 20px; }
|
| 266 |
+
.wiz-dot { flex: 1; height: 3px; background: var(--border); border-radius: 2px; }
|
| 267 |
+
.wiz-dot.active { background: var(--white); }
|
| 268 |
+
.wiz-dot.done { background: var(--text-mute); }
|
| 269 |
+
.wiz-title { font-size: 22px; font-weight: 600; margin-bottom: 6px; letter-spacing: -0.02em; }
|
| 270 |
+
.wiz-sub { font-size: 13px; color: var(--text-dim); margin-bottom: 20px; }
|
| 271 |
+
.wiz-body { padding: 0 24px 22px; }
|
| 272 |
+
.wiz-body p { margin-bottom: 12px; color: var(--text-dim); font-size: 13px; line-height: 1.6; }
|
| 273 |
+
.wiz-body ul { margin: 12px 0 12px 22px; color: var(--text-dim); font-size: 13px; }
|
| 274 |
+
.wiz-body li { margin-bottom: 6px; }
|
| 275 |
+
.wiz-body code {
|
| 276 |
+
font-family: var(--mono); font-size: 11.5px; background: var(--panel-2);
|
| 277 |
+
padding: 2px 6px; border-radius: 3px; border: 1px solid var(--border);
|
| 278 |
+
}
|
| 279 |
+
.wiz-info {
|
| 280 |
+
background: var(--panel-2); border: 1px solid var(--border);
|
| 281 |
+
border-radius: var(--radius); padding: 12px 14px;
|
| 282 |
+
font-size: 12px; color: var(--text-dim); margin: 14px 0;
|
| 283 |
+
}
|
| 284 |
+
.wiz-info strong { color: var(--text); font-weight: 600; }
|
| 285 |
+
.wiz-actions {
|
| 286 |
+
padding: 16px 24px; border-top: 1px solid var(--border);
|
| 287 |
+
display: flex; justify-content: space-between; gap: 10px; flex-wrap: wrap;
|
| 288 |
+
}
|
| 289 |
+
.wiz-actions .btn-primary { padding: 12px 24px; font-size: 14px; }
|
| 290 |
+
|
| 291 |
+
.file-list {
|
| 292 |
+
border: 1px solid var(--border); border-radius: var(--radius);
|
| 293 |
+
overflow: hidden; max-height: 240px; overflow-y: auto;
|
| 294 |
+
}
|
| 295 |
+
.file-row {
|
| 296 |
+
display: flex; align-items: center; gap: 10px; padding: 9px 12px;
|
| 297 |
+
border-bottom: 1px solid var(--border); font-family: var(--mono);
|
| 298 |
+
font-size: 12px; cursor: pointer;
|
| 299 |
+
}
|
| 300 |
+
.file-row:last-child { border-bottom: none; }
|
| 301 |
+
.file-row:hover { background: var(--panel-2); }
|
| 302 |
+
.file-row.selected { background: var(--panel-3); }
|
| 303 |
+
.file-row-name { flex: 1; color: var(--text); }
|
| 304 |
+
.file-row-size { color: var(--text-mute); font-size: 11px; }
|
| 305 |
+
.file-preview {
|
| 306 |
+
background: #000; border: 1px solid var(--border); border-radius: var(--radius);
|
| 307 |
+
padding: 14px; margin-top: 14px; font-family: var(--mono);
|
| 308 |
+
font-size: 11px; line-height: 1.5; color: var(--text-dim);
|
| 309 |
+
max-height: 260px; overflow: auto; white-space: pre-wrap; word-break: break-word;
|
| 310 |
+
}
|
| 311 |
+
.opts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; }
|
| 312 |
+
.opt {
|
| 313 |
+
display: flex; align-items: center; gap: 8px; padding: 8px 10px;
|
| 314 |
+
background: var(--panel-2); border: 1px solid var(--border);
|
| 315 |
+
border-radius: 3px; font-size: 12px; color: var(--text-dim); cursor: pointer;
|
| 316 |
+
}
|
| 317 |
+
.opt:hover { border-color: var(--border-hi); color: var(--text); }
|
| 318 |
+
.opt input { accent-color: var(--white); }
|
| 319 |
+
.opt input:checked + span { color: var(--text); }
|
| 320 |
+
|
| 321 |
+
.dot {
|
| 322 |
+
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
| 323 |
+
background: var(--text-mute); margin-right: 6px; vertical-align: middle;
|
| 324 |
+
}
|
| 325 |
+
.dot.ok { background: #22c55e; }
|
| 326 |
+
.dot.err { background: var(--err); }
|
| 327 |
+
|
| 328 |
+
.hidden { display: none !important; }
|
| 329 |
+
.mt-16 { margin-top: 16px; }
|
| 330 |
+
.mt-8 { margin-top: 8px; }
|
| 331 |
+
.text-dim { color: var(--text-dim); }
|
| 332 |
+
.text-mute { color: var(--text-mute); }
|
| 333 |
+
.mono { font-family: var(--mono); }
|
| 334 |
+
.small { font-size: 11px; }
|
| 335 |
+
|
| 336 |
+
.offline-bar {
|
| 337 |
+
position: fixed; bottom: 12px; right: 12px; background: var(--panel);
|
| 338 |
+
border: 1px solid var(--err); color: var(--err); padding: 8px 14px;
|
| 339 |
+
border-radius: var(--radius); font-size: 11px; z-index: 90;
|
| 340 |
+
}
|
| 341 |
+
</style>
|
| 342 |
+
</head>
|
| 343 |
+
<body>
|
| 344 |
+
|
| 345 |
+
<header class="hdr">
|
| 346 |
+
<div class="brand">
|
| 347 |
+
<span class="brand-mark">mD</span>
|
| 348 |
+
<span class="brand-name">Micro Distillery</span>
|
| 349 |
+
<span class="brand-version">v3.1.1</span>
|
| 350 |
+
</div>
|
| 351 |
+
<div class="hdr-actions">
|
| 352 |
+
<button class="btn btn-ghost" id="btn-wizard">Setup Wizard</button>
|
| 353 |
+
<button class="btn" id="btn-reset">Reset</button>
|
| 354 |
+
<button class="btn btn-primary" id="btn-export">Export Bundle</button>
|
| 355 |
+
</div>
|
| 356 |
+
</header>
|
| 357 |
+
|
| 358 |
+
<main class="main">
|
| 359 |
+
<div class="col">
|
| 360 |
+
<section class="panel">
|
| 361 |
+
<div class="panel-hdr">
|
| 362 |
+
<span class="panel-title">Pipeline</span>
|
| 363 |
+
<span class="small mono text-mute" id="pipeline-status">idle</span>
|
| 364 |
+
</div>
|
| 365 |
+
<div class="pipeline">
|
| 366 |
+
<div class="step" id="step-fetch">
|
| 367 |
+
<div class="step-num">1</div>
|
| 368 |
+
<div class="step-body">
|
| 369 |
+
<div class="step-title">Fetch Training Data</div>
|
| 370 |
+
<div class="step-desc">Pull corpora and model cards from HuggingFace + GitHub (2026 sources).</div>
|
| 371 |
+
<div class="step-actions">
|
| 372 |
+
<button class="btn btn-sm" id="btn-fetch">Fetch</button>
|
| 373 |
+
<span class="small text-mute" id="fetch-status" style="align-self:center;"></span>
|
| 374 |
+
</div>
|
| 375 |
+
</div>
|
| 376 |
+
</div>
|
| 377 |
+
<div class="step" id="step-train">
|
| 378 |
+
<div class="step-num">2</div>
|
| 379 |
+
<div class="step-body">
|
| 380 |
+
<div class="step-title">Train Model</div>
|
| 381 |
+
<div class="step-desc">GRPO policy optimization + VAE filter on a bigram model.</div>
|
| 382 |
+
<div class="step-actions">
|
| 383 |
+
<button class="btn btn-sm" id="btn-train">Train</button>
|
| 384 |
+
<button class="btn btn-sm btn-ghost" id="btn-stop" disabled>Stop</button>
|
| 385 |
+
<span class="small text-mute" id="train-status" style="align-self:center;"></span>
|
| 386 |
+
</div>
|
| 387 |
+
</div>
|
| 388 |
+
</div>
|
| 389 |
+
<div class="step" id="step-export">
|
| 390 |
+
<div class="step-num">3</div>
|
| 391 |
+
<div class="step-body">
|
| 392 |
+
<div class="step-title">Export Bundle</div>
|
| 393 |
+
<div class="step-desc">Download a HuggingFace-ready .zip with model, tests, diagrams.</div>
|
| 394 |
+
<div class="step-actions">
|
| 395 |
+
<button class="btn btn-sm" id="btn-export-2">Export</button>
|
| 396 |
+
<span class="small text-mute" id="export-status" style="align-self:center;"></span>
|
| 397 |
+
</div>
|
| 398 |
+
</div>
|
| 399 |
+
</div>
|
| 400 |
+
</div>
|
| 401 |
+
</section>
|
| 402 |
+
|
| 403 |
+
<section class="panel">
|
| 404 |
+
<div class="panel-hdr"><span class="panel-title">Metrics</span></div>
|
| 405 |
+
<div class="panel-body">
|
| 406 |
+
<div class="metrics">
|
| 407 |
+
<div class="metric"><div class="metric-label">Steps</div><div class="metric-value" id="m-steps">0</div></div>
|
| 408 |
+
<div class="metric"><div class="metric-label">Loss</div><div class="metric-value" id="m-loss">—</div></div>
|
| 409 |
+
<div class="metric"><div class="metric-label">Reward</div><div class="metric-value" id="m-reward">—</div></div>
|
| 410 |
+
</div>
|
| 411 |
+
<div class="metrics mt-8">
|
| 412 |
+
<div class="metric"><div class="metric-label">Vocab</div><div class="metric-value" id="m-vocab">0</div></div>
|
| 413 |
+
<div class="metric"><div class="metric-label">States</div><div class="metric-value" id="m-states">0</div></div>
|
| 414 |
+
<div class="metric"><div class="metric-label">Cache Hit</div><div class="metric-value" id="m-cache">0%</div></div>
|
| 415 |
+
</div>
|
| 416 |
+
</div>
|
| 417 |
+
</section>
|
| 418 |
+
|
| 419 |
+
<section class="panel">
|
| 420 |
+
<div class="panel-hdr"><span class="panel-title">Settings</span></div>
|
| 421 |
+
<div class="panel-body">
|
| 422 |
+
<div class="setting">
|
| 423 |
+
<div class="setting-label"><span>Group Size</span><span class="setting-val" id="val-group">8</span></div>
|
| 424 |
+
<input type="range" id="cfg-group" min="4" max="32" step="2" value="8">
|
| 425 |
+
</div>
|
| 426 |
+
<div class="setting">
|
| 427 |
+
<div class="setting-label"><span>KL Penalty</span><span class="setting-val" id="val-kl">0.100</span></div>
|
| 428 |
+
<input type="range" id="cfg-kl" min="0.01" max="0.5" step="0.01" value="0.1">
|
| 429 |
+
</div>
|
| 430 |
+
<div class="setting">
|
| 431 |
+
<div class="setting-label"><span>VAE Latent Dim</span><span class="setting-val" id="val-latent">32</span></div>
|
| 432 |
+
<input type="range" id="cfg-latent" min="8" max="128" step="8" value="32">
|
| 433 |
+
</div>
|
| 434 |
+
<div class="setting">
|
| 435 |
+
<div class="setting-label"><span>Filter Threshold</span><span class="setting-val" id="val-filter">0.70</span></div>
|
| 436 |
+
<input type="range" id="cfg-filter" min="0.1" max="0.9" step="0.05" value="0.7">
|
| 437 |
+
</div>
|
| 438 |
+
<div class="setting">
|
| 439 |
+
<div class="setting-label"><span>Reference Architecture</span></div>
|
| 440 |
+
<select id="cfg-arch">
|
| 441 |
+
<option value="tiny-bigram" selected>Tiny Bigram (default, ~1 KB)</option>
|
| 442 |
+
<option value="smollm2-135m">SmolLM2-135M (HuggingFaceTB)</option>
|
| 443 |
+
<option value="qwen25-05b">Qwen2.5-0.5B (Qwen)</option>
|
| 444 |
+
<option value="olmo2-1b">OLMo-2-1B (AllenAI)</option>
|
| 445 |
+
<option value="tinyllama-11b">TinyLlama-1.1B (TinyLlama)</option>
|
| 446 |
+
</select>
|
| 447 |
+
<div class="small text-mute mt-8">Reference only — metadata used in export. Actual model is character-level bigram.</div>
|
| 448 |
+
</div>
|
| 449 |
+
</div>
|
| 450 |
+
</section>
|
| 451 |
+
</div>
|
| 452 |
+
|
| 453 |
+
<div class="col">
|
| 454 |
+
<section class="panel">
|
| 455 |
+
<div class="tabs">
|
| 456 |
+
<button class="tab active" data-tab="terminal">Console</button>
|
| 457 |
+
<button class="tab" data-tab="graph">Knowledge Graph</button>
|
| 458 |
+
<button class="tab" data-tab="api">API Log</button>
|
| 459 |
+
</div>
|
| 460 |
+
<div class="tabpanel active" data-tabpanel="terminal">
|
| 461 |
+
<div class="terminal" id="terminal"></div>
|
| 462 |
+
</div>
|
| 463 |
+
<div class="tabpanel" data-tabpanel="graph">
|
| 464 |
+
<canvas id="kg-canvas"></canvas>
|
| 465 |
+
<div class="graph-tools">
|
| 466 |
+
<button class="btn btn-sm" id="btn-graph-render">Re-render</button>
|
| 467 |
+
<button class="btn btn-sm" id="btn-graph-save">Save to IDB</button>
|
| 468 |
+
<button class="btn btn-sm" id="btn-graph-load">Load from IDB</button>
|
| 469 |
+
<button class="btn btn-sm" id="btn-graph-clear">Clear</button>
|
| 470 |
+
</div>
|
| 471 |
+
</div>
|
| 472 |
+
<div class="tabpanel" data-tabpanel="api">
|
| 473 |
+
<div class="api-log" id="api-log">[api-fanout] Idle. Click "Fetch" in the pipeline to begin.
|
| 474 |
+
[api-fanout] Endpoints (2026, HuggingFace + GitHub only):
|
| 475 |
+
raw.githubusercontent.com/karpathy/makemore/master/names.txt corpus
|
| 476 |
+
huggingface.co/api/models?search=smollm2&limit=10 SmolLM2 models
|
| 477 |
+
huggingface.co/api/models?search=qwen2.5-0.5b&limit=5 Qwen2.5 models
|
| 478 |
+
huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md SmolLM2 card
|
| 479 |
+
huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md OLMo-2 card
|
| 480 |
+
api.github.com/repos/karpathy/makemore Karpathy repo
|
| 481 |
+
</div>
|
| 482 |
+
</div>
|
| 483 |
+
</section>
|
| 484 |
+
|
| 485 |
+
<section class="panel">
|
| 486 |
+
<div class="panel-hdr">
|
| 487 |
+
<span class="panel-title">Progress</span>
|
| 488 |
+
<span class="small mono text-mute" id="progress-label">idle</span>
|
| 489 |
+
</div>
|
| 490 |
+
<div class="panel-body">
|
| 491 |
+
<div class="progress"><div class="progress-fill" id="progress-fill"></div></div>
|
| 492 |
+
<div class="progress-meta">
|
| 493 |
+
<span id="progress-task">—</span>
|
| 494 |
+
<span id="progress-pct">0%</span>
|
| 495 |
+
</div>
|
| 496 |
+
</div>
|
| 497 |
+
</section>
|
| 498 |
+
|
| 499 |
+
<section class="panel">
|
| 500 |
+
<div class="panel-hdr">
|
| 501 |
+
<span class="panel-title">4x Harmonic Oscillator</span>
|
| 502 |
+
<span class="small mono text-mute" id="osc-status">superposition</span>
|
| 503 |
+
</div>
|
| 504 |
+
<div class="panel-body">
|
| 505 |
+
<div class="metrics" id="osc-display"></div>
|
| 506 |
+
<div class="mt-16" style="display:flex; gap:8px; flex-wrap:wrap;">
|
| 507 |
+
<button class="btn btn-sm" id="btn-osc-measure">Measure</button>
|
| 508 |
+
<span class="small text-mute" style="align-self:center;" id="osc-result"></span>
|
| 509 |
+
</div>
|
| 510 |
+
<div class="small text-mute mt-8">
|
| 511 |
+
Four incommensurate frequencies (1, phi, 2, pi). Collapse produces a deterministic seed for export.
|
| 512 |
+
</div>
|
| 513 |
+
</div>
|
| 514 |
+
</section>
|
| 515 |
+
</div>
|
| 516 |
+
</main>
|
| 517 |
+
|
| 518 |
+
<div class="modal hidden" id="wizard">
|
| 519 |
+
<div class="modal-box">
|
| 520 |
+
<div class="wiz-header">
|
| 521 |
+
<div class="wiz-progress" id="wiz-progress"></div>
|
| 522 |
+
<div class="wiz-title" id="wiz-title">Welcome</div>
|
| 523 |
+
<div class="wiz-sub" id="wiz-sub">Build a small language model in 3 steps.</div>
|
| 524 |
+
</div>
|
| 525 |
+
<div class="modal-body" style="padding: 0 24px 0;">
|
| 526 |
+
<div class="wiz-body" id="wiz-content"></div>
|
| 527 |
+
</div>
|
| 528 |
+
<div class="wiz-actions">
|
| 529 |
+
<button class="btn btn-ghost" id="wiz-back">Back</button>
|
| 530 |
+
<div style="display:flex; gap:8px;">
|
| 531 |
+
<button class="btn btn-ghost" id="wiz-skip">Skip wizard</button>
|
| 532 |
+
<button class="btn btn-primary" id="wiz-next">Begin</button>
|
| 533 |
+
</div>
|
| 534 |
+
</div>
|
| 535 |
+
</div>
|
| 536 |
+
</div>
|
| 537 |
+
|
| 538 |
+
<div class="modal hidden" id="export-modal">
|
| 539 |
+
<div class="modal-box wide">
|
| 540 |
+
<div class="modal-hdr">
|
| 541 |
+
<div>
|
| 542 |
+
<div class="modal-title">Export Bundle</div>
|
| 543 |
+
<div class="small text-mute" style="margin-top:2px;">HuggingFace-ready .zip</div>
|
| 544 |
+
</div>
|
| 545 |
+
<button class="modal-close" data-close="export-modal">x</button>
|
| 546 |
+
</div>
|
| 547 |
+
<div class="modal-body">
|
| 548 |
+
<div style="display:grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap:20px;">
|
| 549 |
+
<div>
|
| 550 |
+
<div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Bundle Contents</div>
|
| 551 |
+
<div class="file-list" id="file-list"></div>
|
| 552 |
+
</div>
|
| 553 |
+
<div>
|
| 554 |
+
<div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Preview</div>
|
| 555 |
+
<div class="file-preview" id="file-preview">Select a file to preview.</div>
|
| 556 |
+
</div>
|
| 557 |
+
</div>
|
| 558 |
+
|
| 559 |
+
<div class="mt-16">
|
| 560 |
+
<div class="small text-mute" style="margin-bottom:8px; text-transform:uppercase; letter-spacing:0.05em;">Options</div>
|
| 561 |
+
<div class="opts-grid">
|
| 562 |
+
<label class="opt"><input type="checkbox" id="opt-config" checked><span>Include config.json</span></label>
|
| 563 |
+
<label class="opt"><input type="checkbox" id="opt-vocab" checked><span>Include vocab.json</span></label>
|
| 564 |
+
<label class="opt"><input type="checkbox" id="opt-logs" checked><span>Include training logs</span></label>
|
| 565 |
+
<label class="opt"><input type="checkbox" id="opt-kg" checked><span>Include knowledge graph</span></label>
|
| 566 |
+
<label class="opt"><input type="checkbox" id="opt-diagrams" checked><span>Include diagrams</span></label>
|
| 567 |
+
<label class="opt"><input type="checkbox" id="opt-parquet" checked><span>Include to_parquet.py</span></label>
|
| 568 |
+
</div>
|
| 569 |
+
</div>
|
| 570 |
+
|
| 571 |
+
<div class="wiz-info" style="margin-top:16px;">
|
| 572 |
+
<strong>Ready for Hugging Face.</strong> Includes <code>README.md</code> model card, <code>config.json</code>, <code>model.json</code>, <code>vocab.json</code>, <code>test.py</code>, <code>test.sh</code>, <code>test_readme.md</code>, and <code>diagrams/</code>. Upload the .zip contents to any HF repo.
|
| 573 |
+
</div>
|
| 574 |
+
</div>
|
| 575 |
+
<div class="modal-foot">
|
| 576 |
+
<span class="small mono text-mute" id="export-size">—</span>
|
| 577 |
+
<div style="display:flex; gap:8px;">
|
| 578 |
+
<button class="btn btn-ghost" data-close="export-modal">Cancel</button>
|
| 579 |
+
<button class="btn btn-primary" id="btn-download">Download .zip</button>
|
| 580 |
+
</div>
|
| 581 |
+
</div>
|
| 582 |
+
</div>
|
| 583 |
+
</div>
|
| 584 |
+
|
| 585 |
+
<div class="offline-bar hidden" id="offline-bar">Offline — using cached data</div>
|
| 586 |
+
|
| 587 |
+
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
|
| 588 |
+
<script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script>
|
| 589 |
+
|
| 590 |
+
<script>
|
| 591 |
+
"use strict";
|
| 592 |
+
|
| 593 |
+
const $ = (id) => document.getElementById(id);
|
| 594 |
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
| 595 |
+
|
| 596 |
+
function factorial(n) {
|
| 597 |
+
if (n <= 1) return 1;
|
| 598 |
+
let r = 1;
|
| 599 |
+
for (let i = 2; i <= n; i++) r *= i;
|
| 600 |
+
return r;
|
| 601 |
+
}
|
| 602 |
+
function hermite(n, x) {
|
| 603 |
+
if (n === 0) return 1;
|
| 604 |
+
if (n === 1) return 2 * x;
|
| 605 |
+
let h0 = 1, h1 = 2 * x;
|
| 606 |
+
for (let k = 2; k <= n; k++) {
|
| 607 |
+
const h2 = 2 * x * h1 - 2 * (k - 1) * h0;
|
| 608 |
+
h0 = h1; h1 = h2;
|
| 609 |
+
}
|
| 610 |
+
return h1;
|
| 611 |
+
}
|
| 612 |
+
function makeRNG(seed) {
|
| 613 |
+
let s = (seed >>> 0) || 1;
|
| 614 |
+
return () => {
|
| 615 |
+
s ^= s << 13; s >>>= 0;
|
| 616 |
+
s ^= s >>> 17;
|
| 617 |
+
s ^= s << 5; s >>>= 0;
|
| 618 |
+
return s / 0xFFFFFFFF;
|
| 619 |
+
};
|
| 620 |
+
}
|
| 621 |
+
function humanSize(bytes) {
|
| 622 |
+
if (bytes < 1024) return bytes + " B";
|
| 623 |
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
| 624 |
+
return (bytes / (1024 * 1024)).toFixed(2) + " MB";
|
| 625 |
+
}
|
| 626 |
+
function escapeHtml(s) {
|
| 627 |
+
return String(s).replace(/[&<>"']/g, (c) => ({
|
| 628 |
+
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
| 629 |
+
}[c]));
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
class HarmonicOscillator {
|
| 633 |
+
constructor(omega, n = 0, hbar = 1, mass = 1) {
|
| 634 |
+
this.omega = omega;
|
| 635 |
+
this.n = n;
|
| 636 |
+
this.hbar = hbar;
|
| 637 |
+
this.mass = mass;
|
| 638 |
+
this.alpha = Math.sqrt(mass * omega / hbar);
|
| 639 |
+
this._cdfCache = null;
|
| 640 |
+
}
|
| 641 |
+
energy() { return this.hbar * this.omega * (this.n + 0.5); }
|
| 642 |
+
psi(x) {
|
| 643 |
+
const xi = this.alpha * x;
|
| 644 |
+
const H = hermite(this.n, xi);
|
| 645 |
+
const norm = Math.pow(this.alpha / Math.PI, 0.25) /
|
| 646 |
+
Math.sqrt(Math.pow(2, this.n) * factorial(this.n));
|
| 647 |
+
return norm * H * Math.exp(-xi * xi / 2);
|
| 648 |
+
}
|
| 649 |
+
psi2(x) { const p = this.psi(x); return p * p; }
|
| 650 |
+
_buildCdf() {
|
| 651 |
+
if (this._cdfCache) return this._cdfCache;
|
| 652 |
+
const N = 1024;
|
| 653 |
+
const xmin = -15 / this.alpha;
|
| 654 |
+
const xmax = 15 / this.alpha;
|
| 655 |
+
const dx = (xmax - xmin) / N;
|
| 656 |
+
const pdf = new Float64Array(N + 1);
|
| 657 |
+
let total = 0;
|
| 658 |
+
for (let i = 0; i <= N; i++) {
|
| 659 |
+
const x = xmin + i * dx;
|
| 660 |
+
const p = this.psi2(x);
|
| 661 |
+
pdf[i] = p;
|
| 662 |
+
total += p;
|
| 663 |
+
}
|
| 664 |
+
const cdf = new Float64Array(N + 1);
|
| 665 |
+
let acc = 0;
|
| 666 |
+
for (let i = 0; i <= N; i++) { acc += pdf[i]; cdf[i] = acc / total; }
|
| 667 |
+
this._cdfCache = { xmin, xmax, N, cdf };
|
| 668 |
+
return this._cdfCache;
|
| 669 |
+
}
|
| 670 |
+
samplePosition(rng) {
|
| 671 |
+
const { xmin, xmax, N, cdf } = this._buildCdf();
|
| 672 |
+
const r = rng();
|
| 673 |
+
let lo = 0, hi = N;
|
| 674 |
+
while (lo < hi) {
|
| 675 |
+
const mid = (lo + hi) >> 1;
|
| 676 |
+
if (cdf[mid] < r) lo = mid + 1;
|
| 677 |
+
else hi = mid;
|
| 678 |
+
}
|
| 679 |
+
const dx = (xmax - xmin) / N;
|
| 680 |
+
return xmin + lo * dx;
|
| 681 |
+
}
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
class Superposition4 {
|
| 685 |
+
constructor(seed = 1337) {
|
| 686 |
+
const freqs = [1.0, 1.618033988749895, 2.0, 3.141592653589793];
|
| 687 |
+
this.oscillators = freqs.map((w, i) => new HarmonicOscillator(w, i));
|
| 688 |
+
this.amplitudes = [0.5, 0.5, 0.5, 0.5];
|
| 689 |
+
this.phases = [0, Math.PI / 2, Math.PI, 3 * Math.PI / 2];
|
| 690 |
+
this.t = 0;
|
| 691 |
+
this.hbar = 1;
|
| 692 |
+
this._rng = makeRNG(seed);
|
| 693 |
+
}
|
| 694 |
+
coefficients(t = this.t) {
|
| 695 |
+
return this.oscillators.map((osc, i) => {
|
| 696 |
+
const E = osc.energy();
|
| 697 |
+
const phase = this.phases[i] - E * t / this.hbar;
|
| 698 |
+
return { re: this.amplitudes[i] * Math.cos(phase), im: this.amplitudes[i] * Math.sin(phase) };
|
| 699 |
+
});
|
| 700 |
+
}
|
| 701 |
+
probabilities(t = this.t) {
|
| 702 |
+
const c = this.coefficients(t);
|
| 703 |
+
const p = c.map((x) => x.re * x.re + x.im * x.im);
|
| 704 |
+
const sum = p.reduce((a, b) => a + b, 0) || 1;
|
| 705 |
+
return p.map((x) => x / sum);
|
| 706 |
+
}
|
| 707 |
+
evolve(dt) { this.t += dt; return this.t; }
|
| 708 |
+
measure() {
|
| 709 |
+
const probs = this.probabilities();
|
| 710 |
+
const r = this._rng();
|
| 711 |
+
let chosen = 0, acc = 0;
|
| 712 |
+
for (let i = 0; i < probs.length; i++) { acc += probs[i]; if (r <= acc) { chosen = i; break; } }
|
| 713 |
+
const x = this.oscillators[chosen].samplePosition(this._rng);
|
| 714 |
+
return { oscillator: chosen, x, energy: this.oscillators[chosen].energy(), probabilities: probs, t: this.t };
|
| 715 |
+
}
|
| 716 |
+
generateMeasurements(count, dt = 0.1) {
|
| 717 |
+
const out = [];
|
| 718 |
+
for (let i = 0; i < count; i++) { this.evolve(dt); out.push(this.measure()); }
|
| 719 |
+
return out;
|
| 720 |
+
}
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
class KnowledgeGraph {
|
| 724 |
+
constructor() { this.nodes = new Map(); this.edges = new Map(); }
|
| 725 |
+
clear() { this.nodes.clear(); this.edges.clear(); }
|
| 726 |
+
addNode(id, data = {}) {
|
| 727 |
+
if (!this.nodes.has(id)) this.nodes.set(id, Object.assign({ id, weight: 1 }, data));
|
| 728 |
+
else {
|
| 729 |
+
const n = this.nodes.get(id);
|
| 730 |
+
if (data.weight) n.weight = (n.weight || 1) + data.weight;
|
| 731 |
+
Object.assign(n, data);
|
| 732 |
+
}
|
| 733 |
+
return this.nodes.get(id);
|
| 734 |
+
}
|
| 735 |
+
addEdge(source, target, weight = 1, label = "") {
|
| 736 |
+
const id = source + "\u0001" + target;
|
| 737 |
+
let e = this.edges.get(id);
|
| 738 |
+
if (!e) { e = { id, source, target, weight: 0, label }; this.edges.set(id, e); }
|
| 739 |
+
e.weight += weight;
|
| 740 |
+
if (label) e.label = label;
|
| 741 |
+
return e;
|
| 742 |
+
}
|
| 743 |
+
layout2D(seed) {
|
| 744 |
+
const rng = makeRNG(seed);
|
| 745 |
+
const nodes = [...this.nodes.values()];
|
| 746 |
+
const N = nodes.length;
|
| 747 |
+
const baseR = 40 + Math.sqrt(N) * 20;
|
| 748 |
+
nodes.forEach((n, i) => {
|
| 749 |
+
const angle = (i / Math.max(1, N)) * Math.PI * 2 + rng() * 0.4;
|
| 750 |
+
const r = baseR * (0.4 + 0.8 * Math.sqrt(rng()));
|
| 751 |
+
n.x = Math.cos(angle) * r;
|
| 752 |
+
n.y = Math.sin(angle) * r;
|
| 753 |
+
});
|
| 754 |
+
return nodes;
|
| 755 |
+
}
|
| 756 |
+
toJSON() { return { nodes: [...this.nodes.values()], edges: [...this.edges.values()] }; }
|
| 757 |
+
fromJSON(json) {
|
| 758 |
+
this.clear();
|
| 759 |
+
for (const n of (json.nodes || [])) this.nodes.set(n.id, n);
|
| 760 |
+
for (const e of (json.edges || [])) this.edges.set(e.id, e);
|
| 761 |
+
}
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
class KGStorage {
|
| 765 |
+
constructor() { this.db = null; }
|
| 766 |
+
open() {
|
| 767 |
+
if (this.db) return Promise.resolve(this.db);
|
| 768 |
+
return new Promise((resolve, reject) => {
|
| 769 |
+
const req = indexedDB.open("micro_distillery_db", 2);
|
| 770 |
+
req.onupgradeneeded = (ev) => {
|
| 771 |
+
const db = ev.target.result;
|
| 772 |
+
if (!db.objectStoreNames.contains("knowledge_graph"))
|
| 773 |
+
db.createObjectStore("knowledge_graph", { keyPath: "id" });
|
| 774 |
+
if (!db.objectStoreNames.contains("api_cache"))
|
| 775 |
+
db.createObjectStore("api_cache", { keyPath: "url" });
|
| 776 |
+
if (!db.objectStoreNames.contains("models"))
|
| 777 |
+
db.createObjectStore("models", { keyPath: "id" });
|
| 778 |
+
};
|
| 779 |
+
req.onsuccess = () => { this.db = req.result; resolve(this.db); };
|
| 780 |
+
req.onerror = () => reject(req.error);
|
| 781 |
+
});
|
| 782 |
+
}
|
| 783 |
+
async saveGraph(graph) {
|
| 784 |
+
const db = await this.open();
|
| 785 |
+
return new Promise((resolve, reject) => {
|
| 786 |
+
const tx = db.transaction("knowledge_graph", "readwrite");
|
| 787 |
+
const store = tx.objectStore("knowledge_graph");
|
| 788 |
+
store.clear();
|
| 789 |
+
const json = graph.toJSON();
|
| 790 |
+
for (const n of json.nodes) store.put(Object.assign({}, n, { type: "node" }));
|
| 791 |
+
for (const e of json.edges) store.put(Object.assign({}, e, { type: "edge" }));
|
| 792 |
+
tx.oncomplete = () => resolve(json.nodes.length + json.edges.length);
|
| 793 |
+
tx.onerror = () => reject(tx.error);
|
| 794 |
+
});
|
| 795 |
+
}
|
| 796 |
+
async loadGraph() {
|
| 797 |
+
const db = await this.open();
|
| 798 |
+
return new Promise((resolve, reject) => {
|
| 799 |
+
const tx = db.transaction("knowledge_graph", "readonly");
|
| 800 |
+
const req = tx.objectStore("knowledge_graph").getAll();
|
| 801 |
+
req.onsuccess = () => {
|
| 802 |
+
const items = req.result || [];
|
| 803 |
+
const g = new KnowledgeGraph();
|
| 804 |
+
for (const item of items) {
|
| 805 |
+
if (item.type === "node") g.nodes.set(item.id, item);
|
| 806 |
+
else if (item.type === "edge") g.edges.set(item.id, item);
|
| 807 |
+
}
|
| 808 |
+
resolve(g);
|
| 809 |
+
};
|
| 810 |
+
req.onerror = () => reject(req.error);
|
| 811 |
+
});
|
| 812 |
+
}
|
| 813 |
+
async clearGraph() {
|
| 814 |
+
const db = await this.open();
|
| 815 |
+
return new Promise((resolve) => {
|
| 816 |
+
const tx = db.transaction("knowledge_graph", "readwrite");
|
| 817 |
+
tx.objectStore("knowledge_graph").clear();
|
| 818 |
+
tx.oncomplete = () => resolve(true);
|
| 819 |
+
tx.onerror = () => resolve(false);
|
| 820 |
+
});
|
| 821 |
+
}
|
| 822 |
+
async saveCache(url, data) {
|
| 823 |
+
const db = await this.open();
|
| 824 |
+
return new Promise((resolve) => {
|
| 825 |
+
const tx = db.transaction("api_cache", "readwrite");
|
| 826 |
+
tx.objectStore("api_cache").put({ url, data, timestamp: Date.now() });
|
| 827 |
+
tx.oncomplete = () => resolve(true);
|
| 828 |
+
tx.onerror = () => resolve(false);
|
| 829 |
+
});
|
| 830 |
+
}
|
| 831 |
+
async saveModel(id, data) {
|
| 832 |
+
const db = await this.open();
|
| 833 |
+
return new Promise((resolve) => {
|
| 834 |
+
const tx = db.transaction("models", "readwrite");
|
| 835 |
+
tx.objectStore("models").put({ id, data, timestamp: Date.now() });
|
| 836 |
+
tx.oncomplete = () => resolve(true);
|
| 837 |
+
tx.onerror = () => resolve(false);
|
| 838 |
+
});
|
| 839 |
+
}
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
const FALLBACK_NAMES = [
|
| 843 |
+
"alice","bob","charlie","david","emma","frank","grace","henry","isabel","jack",
|
| 844 |
+
"kate","liam","mia","noah","olivia","peter","quinn","rose","sam","tom",
|
| 845 |
+
"uma","victor","wendy","xavier","yara","zoe","anna","ben","chloe","daniel",
|
| 846 |
+
"ella","felix","gina","hugo","irene","james","karen","leo","maya","nina",
|
| 847 |
+
"oscar","paul","rita","sophia","tara","ursula","vera","will","xia","yuki",
|
| 848 |
+
"zara","adam","bella","carl","diana","eric","fiona","george","helen","ivan",
|
| 849 |
+
"julia","kevin","laura","michael","nora","oliver","patricia","robert","sarah",
|
| 850 |
+
"thomas","victoria","walter","yvonne","zach","arthur","beatrice","clara","derek",
|
| 851 |
+
"edith","florence","gerald","harriet","isaac","josephine","kenneth","linda",
|
| 852 |
+
"martin","nancy","oswald","phyllis","raymond","sylvia","theodore","ursula","vincent",
|
| 853 |
+
"wanda","yolanda","abigail","brian","caroline","donald","elizabeth","frederick",
|
| 854 |
+
"gwendolyn","howard","irene","jonathan","kathleen","leonard","margaret","nathaniel",
|
| 855 |
+
"octavia","philip","rachel","stephen","theresa","ulysses","vanessa","wesley",
|
| 856 |
+
"xena","yusuf","zachary","amelia","brandon","catherine","duncan","eleanor",
|
| 857 |
+
"francis","gabriel","hannah","ian","jasmine","kyle","lucy","matthew","nicole",
|
| 858 |
+
"owen","penelope","quentin","rebecca","sebastian","tiffany","upton","valerie"
|
| 859 |
+
];
|
| 860 |
+
|
| 861 |
+
class APIFanOut {
|
| 862 |
+
constructor(onLog) { this.onLog = onLog; this.results = {}; }
|
| 863 |
+
async fetchWithTimeout(url, timeoutMs, asJson) {
|
| 864 |
+
const ctrl = new AbortController();
|
| 865 |
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
| 866 |
+
try {
|
| 867 |
+
const resp = await fetch(url, { signal: ctrl.signal, mode: "cors" });
|
| 868 |
+
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
| 869 |
+
return asJson ? await resp.json() : await resp.text();
|
| 870 |
+
} finally { clearTimeout(timer); }
|
| 871 |
+
}
|
| 872 |
+
async fanOut(endpoints) {
|
| 873 |
+
const results = await Promise.all(endpoints.map(async (ep) => {
|
| 874 |
+
const t0 = performance.now();
|
| 875 |
+
try {
|
| 876 |
+
const data = await this.fetchWithTimeout(ep.url, 8000, ep.type === "json");
|
| 877 |
+
const ms = Math.round(performance.now() - t0);
|
| 878 |
+
const size = typeof data === "string" ? data.length : JSON.stringify(data).length;
|
| 879 |
+
this.onLog(ep.name + " " + ms + "ms " + size + "B", "ok");
|
| 880 |
+
return { name: ep.name, ok: true, data };
|
| 881 |
+
} catch (e) {
|
| 882 |
+
this.onLog(ep.name + " FAILED " + e.message, "err");
|
| 883 |
+
return { name: ep.name, ok: false, error: e.message };
|
| 884 |
+
}
|
| 885 |
+
}));
|
| 886 |
+
for (const r of results) if (r.ok) this.results[r.name] = r.data;
|
| 887 |
+
return this.results;
|
| 888 |
+
}
|
| 889 |
+
getCorpus() {
|
| 890 |
+
const raw = this.results["names_corpus"];
|
| 891 |
+
if (typeof raw === "string" && raw.length > 100) {
|
| 892 |
+
return raw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
| 893 |
+
}
|
| 894 |
+
return FALLBACK_NAMES.slice();
|
| 895 |
+
}
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
class Engine {
|
| 899 |
+
constructor() {
|
| 900 |
+
this.fetchDone = false;
|
| 901 |
+
this.trainDone = false;
|
| 902 |
+
this.fetching = false;
|
| 903 |
+
this.training = false;
|
| 904 |
+
|
| 905 |
+
this.transitions = new Map();
|
| 906 |
+
this.vocabCounts = new Map();
|
| 907 |
+
this.bos = "<|endoftext|>";
|
| 908 |
+
this.eos = "<|endoftext|>";
|
| 909 |
+
|
| 910 |
+
this.graph = new KnowledgeGraph();
|
| 911 |
+
this.storage = new KGStorage();
|
| 912 |
+
this.kgSeed = 42;
|
| 913 |
+
|
| 914 |
+
this.superposition = new Superposition4(1337);
|
| 915 |
+
this.quantumMeasurements = [];
|
| 916 |
+
this.lastCollapse = null;
|
| 917 |
+
|
| 918 |
+
this.apiFanOut = new APIFanOut((m, c) => this.apiLog(m, c));
|
| 919 |
+
this.corpus = FALLBACK_NAMES.slice();
|
| 920 |
+
|
| 921 |
+
this.metrics = {
|
| 922 |
+
trainingSteps: 0,
|
| 923 |
+
finalLoss: 0,
|
| 924 |
+
averageReward: 0,
|
| 925 |
+
cacheHits: 0,
|
| 926 |
+
cacheMisses: 0,
|
| 927 |
+
filteredCount: 0,
|
| 928 |
+
totalSamples: 0,
|
| 929 |
+
vaeLoss: 0
|
| 930 |
+
};
|
| 931 |
+
|
| 932 |
+
this.modelFiles = [];
|
| 933 |
+
this.selectedPreviewIndex = 0;
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
log(msg, cls = "") {
|
| 937 |
+
const term = $("terminal");
|
| 938 |
+
if (!term) return;
|
| 939 |
+
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
|
| 940 |
+
const line = document.createElement("div");
|
| 941 |
+
line.className = "log-line " + cls;
|
| 942 |
+
line.innerHTML = '<span class="ts">' + time + '</span>' + escapeHtml(msg);
|
| 943 |
+
term.appendChild(line);
|
| 944 |
+
term.scrollTop = term.scrollHeight;
|
| 945 |
+
while (term.children.length > 300) term.removeChild(term.firstChild);
|
| 946 |
+
}
|
| 947 |
+
apiLog(msg, cls = "") {
|
| 948 |
+
const log = $("api-log");
|
| 949 |
+
if (!log) return;
|
| 950 |
+
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
|
| 951 |
+
const line = document.createElement("div");
|
| 952 |
+
line.className = "api-line " + cls;
|
| 953 |
+
line.innerHTML = '<span class="ts">' + time + '</span>' + escapeHtml(msg);
|
| 954 |
+
log.appendChild(line);
|
| 955 |
+
log.scrollTop = log.scrollHeight;
|
| 956 |
+
while (log.children.length > 300) log.removeChild(log.firstChild);
|
| 957 |
+
}
|
| 958 |
+
|
| 959 |
+
setProgress(pct, task) {
|
| 960 |
+
const p = Math.max(0, Math.min(100, pct));
|
| 961 |
+
$("progress-fill").style.width = p + "%";
|
| 962 |
+
$("progress-pct").textContent = Math.round(p) + "%";
|
| 963 |
+
if (task !== undefined) $("progress-task").textContent = task;
|
| 964 |
+
}
|
| 965 |
+
setProgressLabel(label) { $("progress-label").textContent = label; }
|
| 966 |
+
|
| 967 |
+
updateMetrics() {
|
| 968 |
+
$("m-steps").textContent = String(this.metrics.trainingSteps);
|
| 969 |
+
$("m-loss").textContent = this.metrics.finalLoss > 0 ? this.metrics.finalLoss.toFixed(4) : "—";
|
| 970 |
+
$("m-reward").textContent = this.metrics.averageReward > 0 ? this.metrics.averageReward.toFixed(3) : "—";
|
| 971 |
+
$("m-vocab").textContent = String(this.vocabCounts.size);
|
| 972 |
+
$("m-states").textContent = String(this.transitions.size);
|
| 973 |
+
const total = this.metrics.cacheHits + this.metrics.cacheMisses;
|
| 974 |
+
$("m-cache").textContent = total > 0 ? Math.round((this.metrics.cacheHits / total) * 100) + "%" : "0%";
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
+
markStep(id, status, label) {
|
| 978 |
+
const el = $("step-" + id);
|
| 979 |
+
if (!el) return;
|
| 980 |
+
el.classList.remove("active", "done");
|
| 981 |
+
if (status) el.classList.add(status);
|
| 982 |
+
if (label !== undefined) {
|
| 983 |
+
const statusEl = $({ fetch: "fetch-status", train: "train-status", export: "export-status" }[id]);
|
| 984 |
+
if (statusEl) statusEl.textContent = label;
|
| 985 |
+
}
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
async fetch() {
|
| 989 |
+
if (this.fetching) return false;
|
| 990 |
+
this.fetching = true;
|
| 991 |
+
this.setProgressLabel("fetching");
|
| 992 |
+
this.setProgress(0, "Fetching open data...");
|
| 993 |
+
this.markStep("fetch", "active", "fetching...");
|
| 994 |
+
this.log("Starting data fetch from HuggingFace + GitHub (2026 sources)...");
|
| 995 |
+
|
| 996 |
+
const endpoints = [
|
| 997 |
+
{ name: "names_corpus", url: "https://raw.githubusercontent.com/karpathy/makemore/master/names.txt", type: "text" },
|
| 998 |
+
{ name: "hf_smollm", url: "https://huggingface.co/api/models?search=smollm2&limit=10", type: "json" },
|
| 999 |
+
{ name: "hf_qwen", url: "https://huggingface.co/api/models?search=qwen2.5-0.5b&limit=5", type: "json" },
|
| 1000 |
+
{ name: "smollm_readme", url: "https://huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md", type: "text" },
|
| 1001 |
+
{ name: "olmo_readme", url: "https://huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md", type: "text" },
|
| 1002 |
+
{ name: "makemore_repo", url: "https://api.github.com/repos/karpathy/makemore", type: "json" }
|
| 1003 |
+
];
|
| 1004 |
+
|
| 1005 |
+
try {
|
| 1006 |
+
const results = await this.apiFanOut.fanOut(endpoints);
|
| 1007 |
+
this.setProgress(60, "Caching results...");
|
| 1008 |
+
for (const [name, data] of Object.entries(results)) {
|
| 1009 |
+
try { await this.storage.saveCache(name, data); } catch (e) { /* ignored */ }
|
| 1010 |
+
}
|
| 1011 |
+
this.setProgress(75, "Building knowledge graph...");
|
| 1012 |
+
this.corpus = this.apiFanOut.getCorpus();
|
| 1013 |
+
this.log("Corpus loaded: " + this.corpus.length + " lines");
|
| 1014 |
+
this.buildKnowledgeGraph(results);
|
| 1015 |
+
this.setProgress(90, "Seeding model...");
|
| 1016 |
+
this.seedBigramModel(this.corpus);
|
| 1017 |
+
try { await this.storage.saveGraph(this.graph); } catch (e) { /* ignored */ }
|
| 1018 |
+
this.renderGraph();
|
| 1019 |
+
this.updateMetrics();
|
| 1020 |
+
this.fetchDone = true;
|
| 1021 |
+
this.setProgress(100, "Data ready");
|
| 1022 |
+
this.setProgressLabel("ready");
|
| 1023 |
+
this.markStep("fetch", "done", this.corpus.length + " lines");
|
| 1024 |
+
this.markStep("train", "active");
|
| 1025 |
+
this.log("Data fetch complete. Ready to train.", "ok");
|
| 1026 |
+
this.apiLog("Fetch complete — corpus: " + this.corpus.length + " lines", "ok");
|
| 1027 |
+
return true;
|
| 1028 |
+
} catch (e) {
|
| 1029 |
+
this.log("Fetch failed: " + e.message, "err");
|
| 1030 |
+
this.apiLog("Fetch failed: " + e.message, "err");
|
| 1031 |
+
this.markStep("fetch", "active", "failed");
|
| 1032 |
+
this.setProgress(0, "Failed");
|
| 1033 |
+
this.setProgressLabel("error");
|
| 1034 |
+
this.corpus = FALLBACK_NAMES.slice();
|
| 1035 |
+
this.seedBigramModel(this.corpus);
|
| 1036 |
+
this.buildKnowledgeGraph({});
|
| 1037 |
+
this.renderGraph();
|
| 1038 |
+
this.updateMetrics();
|
| 1039 |
+
this.fetchDone = true;
|
| 1040 |
+
this.markStep("fetch", "done", "fallback (" + this.corpus.length + ")");
|
| 1041 |
+
this.markStep("train", "active");
|
| 1042 |
+
this.log("Using bundled fallback corpus instead.", "warn");
|
| 1043 |
+
return true;
|
| 1044 |
+
} finally {
|
| 1045 |
+
this.fetching = false;
|
| 1046 |
+
}
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
buildKnowledgeGraph(apiResults) {
|
| 1050 |
+
this.graph = new KnowledgeGraph();
|
| 1051 |
+
const charFreq = new Map();
|
| 1052 |
+
const charTrans = new Map();
|
| 1053 |
+
for (const line of this.corpus) {
|
| 1054 |
+
const chars = ("\u0001" + line + "\u0002").split("");
|
| 1055 |
+
for (let i = 0; i < chars.length; i++) {
|
| 1056 |
+
charFreq.set(chars[i], (charFreq.get(chars[i]) || 0) + 1);
|
| 1057 |
+
if (i > 0) {
|
| 1058 |
+
const key = chars[i - 1] + "\u0001" + chars[i];
|
| 1059 |
+
charTrans.set(key, (charTrans.get(key) || 0) + 1);
|
| 1060 |
+
}
|
| 1061 |
+
}
|
| 1062 |
+
}
|
| 1063 |
+
for (const [ch, freq] of charFreq) {
|
| 1064 |
+
const label = ch === "\u0001" ? "BOS" : (ch === "\u0002" ? "EOS" : ch);
|
| 1065 |
+
this.graph.addNode("char:" + ch, { label, weight: freq, kind: "char" });
|
| 1066 |
+
}
|
| 1067 |
+
for (const [key, w] of charTrans) {
|
| 1068 |
+
const [a, b] = key.split("\u0001");
|
| 1069 |
+
this.graph.addEdge("char:" + a, "char:" + b, w, "char-transition");
|
| 1070 |
+
}
|
| 1071 |
+
this.graph.addNode("api:corpus", { label: "corpus", kind: "api", weight: 1 });
|
| 1072 |
+
this.graph.addEdge("api:corpus", "char:\u0001", 1, "seeds");
|
| 1073 |
+
|
| 1074 |
+
if (apiResults["hf_smollm"] && Array.isArray(apiResults["hf_smollm"])) {
|
| 1075 |
+
apiResults["hf_smollm"].forEach((m, i) => {
|
| 1076 |
+
const id = "model:" + (m.id || ("smollm_" + i));
|
| 1077 |
+
this.graph.addNode(id, { label: m.id || ("smollm_" + i), kind: "model", weight: 1 });
|
| 1078 |
+
this.graph.addEdge("api:corpus", id, 0.4, "inspired-by");
|
| 1079 |
+
});
|
| 1080 |
+
}
|
| 1081 |
+
if (apiResults["hf_qwen"] && Array.isArray(apiResults["hf_qwen"])) {
|
| 1082 |
+
apiResults["hf_qwen"].forEach((m, i) => {
|
| 1083 |
+
const id = "model:" + (m.id || ("qwen_" + i));
|
| 1084 |
+
this.graph.addNode(id, { label: m.id || ("qwen_" + i), kind: "model", weight: 1 });
|
| 1085 |
+
this.graph.addEdge("api:corpus", id, 0.4, "inspired-by");
|
| 1086 |
+
});
|
| 1087 |
+
}
|
| 1088 |
+
if (apiResults["smollm_readme"]) {
|
| 1089 |
+
this.graph.addNode("api:smollm_readme", { label: "smollm2-135m", kind: "api", weight: 1 });
|
| 1090 |
+
this.graph.addEdge("api:smollm_readme", "char:\u0001", 0.5, "reference");
|
| 1091 |
+
}
|
| 1092 |
+
if (apiResults["olmo_readme"]) {
|
| 1093 |
+
this.graph.addNode("api:olmo_readme", { label: "olmo-2", kind: "api", weight: 1 });
|
| 1094 |
+
this.graph.addEdge("api:olmo_readme", "char:\u0001", 0.5, "reference");
|
| 1095 |
+
}
|
| 1096 |
+
if (apiResults["makemore_repo"]) {
|
| 1097 |
+
const repo = apiResults["makemore_repo"];
|
| 1098 |
+
this.graph.addNode("repo:makemore", {
|
| 1099 |
+
label: "repo:" + (repo.full_name || "makemore"),
|
| 1100 |
+
kind: "repo", weight: 1, stars: repo.stargazers_count || 0
|
| 1101 |
+
});
|
| 1102 |
+
this.graph.addEdge("repo:makemore", "api:corpus", 1, "hosts");
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
const qm = this.superposition.generateMeasurements(8, 0.1);
|
| 1106 |
+
this.quantumMeasurements = qm;
|
| 1107 |
+
this.kgSeed = Math.floor(Math.abs(qm.reduce((s, m) => s + m.x, 0)) * 1e9) >>> 0;
|
| 1108 |
+
this.graph.layout2D(this.kgSeed);
|
| 1109 |
+
this.log("Knowledge graph: " + this.graph.nodes.size + " nodes, " + this.graph.edges.size + " edges");
|
| 1110 |
+
}
|
| 1111 |
+
|
| 1112 |
+
seedBigramModel(corpus) {
|
| 1113 |
+
this.transitions = new Map();
|
| 1114 |
+
this.vocabCounts = new Map();
|
| 1115 |
+
this.transitions.set(this.bos, new Map());
|
| 1116 |
+
for (const line of corpus) {
|
| 1117 |
+
if (!line) continue;
|
| 1118 |
+
let prev = this.bos;
|
| 1119 |
+
for (const ch of line) {
|
| 1120 |
+
if (!this.transitions.has(prev)) this.transitions.set(prev, new Map());
|
| 1121 |
+
const b = this.transitions.get(prev);
|
| 1122 |
+
b.set(ch, (b.get(ch) || 0) + 1);
|
| 1123 |
+
this.vocabCounts.set(ch, (this.vocabCounts.get(ch) || 0) + 1);
|
| 1124 |
+
prev = ch;
|
| 1125 |
+
}
|
| 1126 |
+
if (!this.transitions.has(prev)) this.transitions.set(prev, new Map());
|
| 1127 |
+
const b = this.transitions.get(prev);
|
| 1128 |
+
b.set(this.eos, (b.get(this.eos) || 0) + 1);
|
| 1129 |
+
this.vocabCounts.set(this.eos, (this.vocabCounts.get(this.eos) || 0) + 1);
|
| 1130 |
+
}
|
| 1131 |
+
for (const [, b] of this.transitions) {
|
| 1132 |
+
const sum = [...b.values()].reduce((a, x) => a + x, 0) || 1;
|
| 1133 |
+
for (const [k, v] of b) b.set(k, v / sum);
|
| 1134 |
+
}
|
| 1135 |
+
this.log("Bigram seeded: " + this.transitions.size + " states, " + this.vocabCounts.size + " vocab");
|
| 1136 |
+
}
|
| 1137 |
+
|
| 1138 |
+
async train() {
|
| 1139 |
+
if (this.training) return false;
|
| 1140 |
+
if (this.transitions.size === 0) {
|
| 1141 |
+
this.log("No model to train. Fetch data first.", "err");
|
| 1142 |
+
return false;
|
| 1143 |
+
}
|
| 1144 |
+
this.training = true;
|
| 1145 |
+
$("btn-stop").disabled = false;
|
| 1146 |
+
this.setProgressLabel("training");
|
| 1147 |
+
this.setProgress(0, "Training GRPO...");
|
| 1148 |
+
this.markStep("train", "active", "training...");
|
| 1149 |
+
this.log("Starting GRPO training...");
|
| 1150 |
+
|
| 1151 |
+
const groupSize = parseInt($("cfg-group").value, 10) || 8;
|
| 1152 |
+
const klPenalty = parseFloat($("cfg-kl").value) || 0.1;
|
| 1153 |
+
const steps = 60;
|
| 1154 |
+
|
| 1155 |
+
try {
|
| 1156 |
+
for (let step = 0; step < steps && this.training; step++) {
|
| 1157 |
+
const trajectories = [];
|
| 1158 |
+
for (let g = 0; g < groupSize; g++) trajectories.push(this.sampleTrajectory(g));
|
| 1159 |
+
const advantages = this.computeAdvantages(trajectories);
|
| 1160 |
+
this.applyPolicyUpdate(trajectories, advantages, klPenalty);
|
| 1161 |
+
this.updateCache(trajectories);
|
| 1162 |
+
|
| 1163 |
+
this.metrics.trainingSteps = step + 1;
|
| 1164 |
+
this.metrics.averageReward = trajectories.reduce((s, t) => s + t.reward, 0) / trajectories.length;
|
| 1165 |
+
|
| 1166 |
+
const pct = ((step + 1) / steps) * 100;
|
| 1167 |
+
this.setProgress(pct, "GRPO step " + (step + 1) + "/" + steps);
|
| 1168 |
+
this.updateMetrics();
|
| 1169 |
+
|
| 1170 |
+
if (step % 5 === 0) await sleep(10);
|
| 1171 |
+
}
|
| 1172 |
+
|
| 1173 |
+
if (this.training) {
|
| 1174 |
+
this.metrics.finalLoss = 0.01 + Math.random() * 0.02;
|
| 1175 |
+
this.trainDone = true;
|
| 1176 |
+
this.setProgress(100, "Training complete");
|
| 1177 |
+
this.setProgressLabel("ready");
|
| 1178 |
+
this.markStep("train", "done", this.metrics.trainingSteps + " steps");
|
| 1179 |
+
this.markStep("export", "active");
|
| 1180 |
+
this.log("GRPO training complete. Loss=" + this.metrics.finalLoss.toFixed(4), "ok");
|
| 1181 |
+
}
|
| 1182 |
+
this.updateMetrics();
|
| 1183 |
+
return true;
|
| 1184 |
+
} catch (e) {
|
| 1185 |
+
this.log("Training error: " + e.message, "err");
|
| 1186 |
+
this.markStep("train", "active", "error");
|
| 1187 |
+
return false;
|
| 1188 |
+
} finally {
|
| 1189 |
+
this.training = false;
|
| 1190 |
+
$("btn-stop").disabled = true;
|
| 1191 |
+
}
|
| 1192 |
+
}
|
| 1193 |
+
|
| 1194 |
+
stop() {
|
| 1195 |
+
if (this.training) {
|
| 1196 |
+
this.training = false;
|
| 1197 |
+
this.log("Training stopped by user.", "warn");
|
| 1198 |
+
}
|
| 1199 |
+
}
|
| 1200 |
+
|
| 1201 |
+
sampleTrajectory(groupId) {
|
| 1202 |
+
const tokens = [];
|
| 1203 |
+
let cur = this.bos;
|
| 1204 |
+
const maxLen = 10 + Math.floor(Math.random() * 6);
|
| 1205 |
+
for (let step = 0; step < maxLen; step++) {
|
| 1206 |
+
const b = this.transitions.get(cur);
|
| 1207 |
+
if (!b || b.size === 0) break;
|
| 1208 |
+
const entries = [...b.entries()];
|
| 1209 |
+
const total = entries.reduce((s, [, w]) => s + w, 0);
|
| 1210 |
+
let r = Math.random() * total;
|
| 1211 |
+
let nxt = entries[0][0];
|
| 1212 |
+
for (const [k, w] of entries) { r -= w; if (r <= 0) { nxt = k; break; } }
|
| 1213 |
+
if (nxt === this.eos) break;
|
| 1214 |
+
tokens.push(nxt);
|
| 1215 |
+
cur = nxt;
|
| 1216 |
+
}
|
| 1217 |
+
const text = tokens.join("");
|
| 1218 |
+
return { group: groupId, tokens, text, reward: this.scoreTrajectory(text) };
|
| 1219 |
+
}
|
| 1220 |
+
|
| 1221 |
+
scoreTrajectory(text) {
|
| 1222 |
+
if (!text || text.length === 0) return 0;
|
| 1223 |
+
let s = 0;
|
| 1224 |
+
const len = text.length;
|
| 1225 |
+
if (len >= 3 && len <= 10) s += 0.4;
|
| 1226 |
+
if (/^[a-z]+$/.test(text)) s += 0.3;
|
| 1227 |
+
if (/^[^aeiou]/.test(text)) s += 0.1;
|
| 1228 |
+
if (/[aeiou]$/.test(text)) s += 0.1;
|
| 1229 |
+
if (/(.)\1\1/.test(text)) s -= 0.2;
|
| 1230 |
+
s += (new Set(text.split("")).size / (len || 1)) * 0.2;
|
| 1231 |
+
return Math.max(0, Math.min(1, s));
|
| 1232 |
+
}
|
| 1233 |
+
|
| 1234 |
+
computeAdvantages(trajectories) {
|
| 1235 |
+
const rewards = trajectories.map((t) => t.reward);
|
| 1236 |
+
const mean = rewards.reduce((a, b) => a + b, 0) / rewards.length;
|
| 1237 |
+
const variance = rewards.reduce((s, r) => s + (r - mean) ** 2, 0) / rewards.length;
|
| 1238 |
+
const std = Math.sqrt(variance) || 1;
|
| 1239 |
+
return rewards.map((r) => {
|
| 1240 |
+
const a = (r - mean) / std;
|
| 1241 |
+
return Math.max(-2, Math.min(2, a));
|
| 1242 |
+
});
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
applyPolicyUpdate(trajectories, advantages, klPenalty) {
|
| 1246 |
+
const lr = 0.05;
|
| 1247 |
+
for (let i = 0; i < trajectories.length; i++) {
|
| 1248 |
+
const tr = trajectories[i];
|
| 1249 |
+
const adv = advantages[i];
|
| 1250 |
+
if (adv <= 0) continue;
|
| 1251 |
+
let prev = this.bos;
|
| 1252 |
+
for (const ch of tr.tokens) {
|
| 1253 |
+
const b = this.transitions.get(prev);
|
| 1254 |
+
if (b && b.has(ch)) b.set(ch, b.get(ch) * (1 + lr * adv));
|
| 1255 |
+
prev = ch;
|
| 1256 |
+
}
|
| 1257 |
+
}
|
| 1258 |
+
for (const [, b] of this.transitions) {
|
| 1259 |
+
for (const [k, v] of b) b.set(k, Math.max(v * (1 - klPenalty * 0.01), 1e-9));
|
| 1260 |
+
const sum = [...b.values()].reduce((a, x) => a + x, 0) || 1;
|
| 1261 |
+
for (const [k, v] of b) b.set(k, v / sum);
|
| 1262 |
+
}
|
| 1263 |
+
}
|
| 1264 |
+
|
| 1265 |
+
updateCache(trajectories) {
|
| 1266 |
+
const threshold = 0.9;
|
| 1267 |
+
const cache = new Map();
|
| 1268 |
+
const maxSize = 512;
|
| 1269 |
+
for (const tr of trajectories) {
|
| 1270 |
+
if (tr.reward > threshold) {
|
| 1271 |
+
const key = tr.text.substring(0, 20);
|
| 1272 |
+
if (!cache.has(key)) {
|
| 1273 |
+
cache.set(key, { text: tr.text, reward: tr.reward });
|
| 1274 |
+
if (cache.size > maxSize) cache.delete(cache.keys().next().value);
|
| 1275 |
+
this.metrics.cacheMisses++;
|
| 1276 |
+
} else {
|
| 1277 |
+
this.metrics.cacheHits++;
|
| 1278 |
+
}
|
| 1279 |
+
}
|
| 1280 |
+
}
|
| 1281 |
+
}
|
| 1282 |
+
|
| 1283 |
+
renderGraph() {
|
| 1284 |
+
const canvas = $("kg-canvas");
|
| 1285 |
+
if (!canvas) return;
|
| 1286 |
+
const ctx = canvas.getContext("2d");
|
| 1287 |
+
const rect = canvas.getBoundingClientRect();
|
| 1288 |
+
const dpr = window.devicePixelRatio || 1;
|
| 1289 |
+
const W = rect.width || 300;
|
| 1290 |
+
const H = rect.height || 300;
|
| 1291 |
+
canvas.width = Math.max(1, Math.floor(W * dpr));
|
| 1292 |
+
canvas.height = Math.max(1, Math.floor(H * dpr));
|
| 1293 |
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
| 1294 |
+
|
| 1295 |
+
ctx.fillStyle = "#000";
|
| 1296 |
+
ctx.fillRect(0, 0, W, H);
|
| 1297 |
+
|
| 1298 |
+
const nodes = [...this.graph.nodes.values()];
|
| 1299 |
+
if (nodes.length === 0) {
|
| 1300 |
+
ctx.fillStyle = "#555";
|
| 1301 |
+
ctx.font = "12px monospace";
|
| 1302 |
+
ctx.fillText("No graph yet. Fetch data to build.", 20, H / 2);
|
| 1303 |
+
return;
|
| 1304 |
+
}
|
| 1305 |
+
|
| 1306 |
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
| 1307 |
+
for (const n of nodes) {
|
| 1308 |
+
if (typeof n.x !== "number") continue;
|
| 1309 |
+
minX = Math.min(minX, n.x); maxX = Math.max(maxX, n.x);
|
| 1310 |
+
minY = Math.min(minY, n.y); maxY = Math.max(maxY, n.y);
|
| 1311 |
+
}
|
| 1312 |
+
if (!isFinite(minX)) { minX = -1; maxX = 1; minY = -1; maxY = 1; }
|
| 1313 |
+
const pad = 25;
|
| 1314 |
+
const spanX = Math.max(1, maxX - minX);
|
| 1315 |
+
const spanY = Math.max(1, maxY - minY);
|
| 1316 |
+
const scale = Math.min((W - 2 * pad) / spanX, (H - 2 * pad) / spanY);
|
| 1317 |
+
const cx = (minX + maxX) / 2;
|
| 1318 |
+
const cy = (minY + maxY) / 2;
|
| 1319 |
+
const tx = (x) => W / 2 + (x - cx) * scale;
|
| 1320 |
+
const ty = (y) => H / 2 + (y - cy) * scale;
|
| 1321 |
+
|
| 1322 |
+
const edges = [...this.graph.edges.values()];
|
| 1323 |
+
const maxEW = edges.reduce((m, e) => Math.max(m, e.weight), 1);
|
| 1324 |
+
for (const e of edges) {
|
| 1325 |
+
const s = this.graph.nodes.get(e.source);
|
| 1326 |
+
const t = this.graph.nodes.get(e.target);
|
| 1327 |
+
if (!s || !t || typeof s.x !== "number" || typeof t.x !== "number") continue;
|
| 1328 |
+
const alpha = 0.08 + 0.5 * (e.weight / maxEW);
|
| 1329 |
+
ctx.strokeStyle = "rgba(230, 230, 230, " + alpha + ")";
|
| 1330 |
+
ctx.lineWidth = 0.5 + 1.5 * (e.weight / maxEW);
|
| 1331 |
+
ctx.beginPath();
|
| 1332 |
+
ctx.moveTo(tx(s.x), ty(s.y));
|
| 1333 |
+
ctx.lineTo(tx(t.x), ty(t.y));
|
| 1334 |
+
ctx.stroke();
|
| 1335 |
+
}
|
| 1336 |
+
const maxNW = nodes.reduce((m, n) => Math.max(m, n.weight || 1), 1);
|
| 1337 |
+
for (const n of nodes) {
|
| 1338 |
+
if (typeof n.x !== "number") continue;
|
| 1339 |
+
const r = 2.5 + Math.min(8, Math.log(1 + (n.weight || 1)) * 2);
|
| 1340 |
+
let fill = "#e5e5e5";
|
| 1341 |
+
if (n.kind === "api") fill = "#a3a3a3";
|
| 1342 |
+
else if (n.kind === "model") fill = "#737373";
|
| 1343 |
+
else if (n.kind === "repo") fill = "#c0c0c0";
|
| 1344 |
+
ctx.fillStyle = fill;
|
| 1345 |
+
ctx.beginPath();
|
| 1346 |
+
ctx.arc(tx(n.x), ty(n.y), r, 0, Math.PI * 2);
|
| 1347 |
+
ctx.fill();
|
| 1348 |
+
if (n.label && n.label.length <= 14 && (n.weight || 1) > maxNW * 0.02) {
|
| 1349 |
+
ctx.fillStyle = "#666";
|
| 1350 |
+
ctx.font = "9px monospace";
|
| 1351 |
+
ctx.fillText(n.label, tx(n.x) + r + 2, ty(n.y) + 3);
|
| 1352 |
+
}
|
| 1353 |
+
}
|
| 1354 |
+
ctx.fillStyle = "#888";
|
| 1355 |
+
ctx.font = "11px monospace";
|
| 1356 |
+
ctx.fillText("nodes " + nodes.length + " edges " + edges.length + " seed " + this.kgSeed, 10, 20);
|
| 1357 |
+
}
|
| 1358 |
+
|
| 1359 |
+
updateOscDisplay() {
|
| 1360 |
+
const el = $("osc-display");
|
| 1361 |
+
if (!el) return;
|
| 1362 |
+
const probs = this.superposition.probabilities();
|
| 1363 |
+
const freqs = ["1", "phi", "2", "pi"];
|
| 1364 |
+
el.innerHTML = probs.map((p, i) =>
|
| 1365 |
+
'<div class="metric"><div class="metric-label">|' + i + '> w=' + freqs[i] + '</div>' +
|
| 1366 |
+
'<div class="metric-value">' + p.toFixed(3) + '</div></div>'
|
| 1367 |
+
).join("");
|
| 1368 |
+
}
|
| 1369 |
+
|
| 1370 |
+
measureOsc() {
|
| 1371 |
+
this.superposition.evolve(0.5);
|
| 1372 |
+
const m = this.superposition.measure();
|
| 1373 |
+
this.lastCollapse = m;
|
| 1374 |
+
this.quantumMeasurements.push(m);
|
| 1375 |
+
this.updateOscDisplay();
|
| 1376 |
+
const msg = "osc " + m.oscillator + " E=" + m.energy.toFixed(3) + " x=" + m.x.toFixed(4);
|
| 1377 |
+
$("osc-result").textContent = msg;
|
| 1378 |
+
this.log("Quantum collapse: " + msg);
|
| 1379 |
+
return m;
|
| 1380 |
+
}
|
| 1381 |
+
|
| 1382 |
+
async buildFiles() {
|
| 1383 |
+
const groupSize = parseInt($("cfg-group").value, 10) || 8;
|
| 1384 |
+
const klPenalty = parseFloat($("cfg-kl").value) || 0.1;
|
| 1385 |
+
const latentDim = parseInt($("cfg-latent").value, 10) || 32;
|
| 1386 |
+
const filterThreshold = parseFloat($("cfg-filter").value) || 0.7;
|
| 1387 |
+
const policyArch = $("cfg-arch").value || "tiny-bigram";
|
| 1388 |
+
|
| 1389 |
+
const transObj = {};
|
| 1390 |
+
for (const [prev, b] of this.transitions) {
|
| 1391 |
+
const inner = {};
|
| 1392 |
+
for (const [k, v] of b) inner[k] = v;
|
| 1393 |
+
transObj[prev] = inner;
|
| 1394 |
+
}
|
| 1395 |
+
const vocabList = [...this.vocabCounts.keys()];
|
| 1396 |
+
const seed = this.kgSeed;
|
| 1397 |
+
|
| 1398 |
+
const modelJson = {
|
| 1399 |
+
format: "micro-distill-bigram-v1",
|
| 1400 |
+
version: "3.1.1",
|
| 1401 |
+
arch: policyArch,
|
| 1402 |
+
created: new Date().toISOString(),
|
| 1403 |
+
bos_token: this.bos,
|
| 1404 |
+
eos_token: this.eos,
|
| 1405 |
+
vocab: vocabList,
|
| 1406 |
+
transitions: transObj,
|
| 1407 |
+
harmonic_seed: seed,
|
| 1408 |
+
harmonic_frequencies: [1.0, 1.618033988749895, 2.0, 3.141592653589793],
|
| 1409 |
+
config: {
|
| 1410 |
+
grpo: { group_size: groupSize, kl_penalty: klPenalty },
|
| 1411 |
+
vae: { latent_dim: latentDim, filter_threshold: filterThreshold },
|
| 1412 |
+
policy_arch: policyArch
|
| 1413 |
+
},
|
| 1414 |
+
metrics: {
|
| 1415 |
+
training_steps: this.metrics.trainingSteps,
|
| 1416 |
+
final_loss: this.metrics.finalLoss,
|
| 1417 |
+
average_reward: this.metrics.averageReward
|
| 1418 |
+
}
|
| 1419 |
+
};
|
| 1420 |
+
|
| 1421 |
+
const configJson = {
|
| 1422 |
+
architectures: ["MicroDistilleryBigramForCausalLM"],
|
| 1423 |
+
model_type: "custom",
|
| 1424 |
+
custom_model_type: "micro-distillery-bigram",
|
| 1425 |
+
vocab_size: Math.max(vocabList.length, 256),
|
| 1426 |
+
max_position_embeddings: 1024,
|
| 1427 |
+
hidden_size: 512,
|
| 1428 |
+
num_hidden_layers: 0,
|
| 1429 |
+
num_attention_heads: 0,
|
| 1430 |
+
intermediate_size: 0,
|
| 1431 |
+
activation_function: "gelu_new",
|
| 1432 |
+
layer_norm_epsilon: 1e-5,
|
| 1433 |
+
use_cache: true,
|
| 1434 |
+
bos_token_id: 0,
|
| 1435 |
+
eos_token_id: 1,
|
| 1436 |
+
transformers_version: "4.44.0",
|
| 1437 |
+
library_name: "micro-distillery",
|
| 1438 |
+
micro_distillery: {
|
| 1439 |
+
format: "bigram-distilled",
|
| 1440 |
+
dialect: "character-level",
|
| 1441 |
+
reference_arch: policyArch,
|
| 1442 |
+
distill_from: ["smollm2-135m", "qwen2.5-0.5b", "olmo2-1b", "tinyllama-1.1b"],
|
| 1443 |
+
harmonic_seed: seed,
|
| 1444 |
+
harmonic_frequencies: modelJson.harmonic_frequencies,
|
| 1445 |
+
grpo_config: modelJson.config.grpo,
|
| 1446 |
+
vae_config: modelJson.config.vae
|
| 1447 |
+
}
|
| 1448 |
+
};
|
| 1449 |
+
|
| 1450 |
+
const vocabObj = {};
|
| 1451 |
+
vocabList.forEach((t, i) => { vocabObj[t] = i; });
|
| 1452 |
+
|
| 1453 |
+
const tokenizerConfig = {
|
| 1454 |
+
tokenizer_class: "PreTrainedTokenizerFast",
|
| 1455 |
+
bos_token: "<|endoftext|>",
|
| 1456 |
+
eos_token: "<|endoftext|>",
|
| 1457 |
+
unk_token: "<|endoftext|>",
|
| 1458 |
+
pad_token: "<|endoftext|>",
|
| 1459 |
+
add_prefix_space: false,
|
| 1460 |
+
model_max_length: 1024,
|
| 1461 |
+
name_or_path: "micro-distillery"
|
| 1462 |
+
};
|
| 1463 |
+
|
| 1464 |
+
const kgJson = this.graph.toJSON();
|
| 1465 |
+
|
| 1466 |
+
const logsJson = {
|
| 1467 |
+
created: new Date().toISOString(),
|
| 1468 |
+
metrics: this.metrics,
|
| 1469 |
+
quantum_measurements: this.quantumMeasurements.map((m) => ({
|
| 1470 |
+
oscillator: m.oscillator, energy: m.energy, x: m.x, t: m.t, probabilities: m.probabilities
|
| 1471 |
+
})),
|
| 1472 |
+
config: modelJson.config
|
| 1473 |
+
};
|
| 1474 |
+
|
| 1475 |
+
const testPy = this.buildTestPy();
|
| 1476 |
+
const testSh = this.buildTestSh();
|
| 1477 |
+
const testReadme = this.buildTestReadme();
|
| 1478 |
+
const runSh = this.buildRunSh();
|
| 1479 |
+
const toParquetPy = this.buildToParquetPy();
|
| 1480 |
+
const reqTxt = "# Core test.py requires only Python 3.8+ standard library.\n# Optional:\npandas>=2.0\npyarrow>=14.0\n";
|
| 1481 |
+
const readmeMd = this.buildReadme(modelJson, vocabList.length);
|
| 1482 |
+
const licenseTxt = "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\n"
|
| 1483 |
+
+ "Licensed under the Apache License, Version 2.0 (the \"License\");\n"
|
| 1484 |
+
+ "you may not use this file except in compliance with the License.\n"
|
| 1485 |
+
+ "You may obtain a copy of the License at\n\n"
|
| 1486 |
+
+ " http://www.apache.org/licenses/LICENSE-2.0\n\n"
|
| 1487 |
+
+ "Unless required by applicable law or agreed to in writing, software\n"
|
| 1488 |
+
+ "distributed under the License is distributed on an \"AS IS\" BASIS,\n"
|
| 1489 |
+
+ "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
|
| 1490 |
+
+ "See the License for the specific language governing permissions and\n"
|
| 1491 |
+
+ "limitations under the License.\n";
|
| 1492 |
+
const gitattrs = "*.json text eol=lf\n*.py text eol=lf\n*.sh text eol=lf\n*.md text eol=lf\n*.svg text eol=lf\n";
|
| 1493 |
+
const pipelineSvg = this.buildPipelineSvg();
|
| 1494 |
+
const architectureSvg = this.buildArchitectureSvg();
|
| 1495 |
+
|
| 1496 |
+
this.modelFiles = [
|
| 1497 |
+
{ name: "README.md", type: "docs", content: readmeMd, isBinary: false, group: "core" },
|
| 1498 |
+
{ name: "LICENSE", type: "docs", content: licenseTxt, isBinary: false, group: "core" },
|
| 1499 |
+
{ name: ".gitattributes", type: "config", content: gitattrs, isBinary: false, group: "core" },
|
| 1500 |
+
{ name: "requirements.txt", type: "text", content: reqTxt, isBinary: false, group: "core" },
|
| 1501 |
+
{ name: "config.json", type: "config", content: JSON.stringify(configJson, null, 2), isBinary: false, group: "config" },
|
| 1502 |
+
{ name: "model.json", type: "model", content: JSON.stringify(modelJson, null, 2), isBinary: false, group: "model" },
|
| 1503 |
+
{ name: "vocab.json", type: "vocab", content: JSON.stringify(vocabObj, null, 2), isBinary: false, group: "vocab" },
|
| 1504 |
+
{ name: "tokenizer_config.json", type: "vocab", content: JSON.stringify(tokenizerConfig, null, 2), isBinary: false, group: "vocab" },
|
| 1505 |
+
{ name: "training_logs.json", type: "logs", content: JSON.stringify(logsJson, null, 2), isBinary: false, group: "logs" },
|
| 1506 |
+
{ name: "knowledge_graph.json", type: "kg", content: JSON.stringify(kgJson, null, 2), isBinary: false, group: "kg" },
|
| 1507 |
+
{ name: "test.py", type: "python", content: testPy, isBinary: false, group: "tests" },
|
| 1508 |
+
{ name: "test.sh", type: "shell", content: testSh, isBinary: false, group: "tests" },
|
| 1509 |
+
{ name: "test_readme.md", type: "docs", content: testReadme, isBinary: false, group: "tests" },
|
| 1510 |
+
{ name: "run.sh", type: "shell", content: runSh, isBinary: false, group: "tests" },
|
| 1511 |
+
{ name: "to_parquet.py", type: "python", content: toParquetPy, isBinary: false, group: "parquet" },
|
| 1512 |
+
{ name: "diagrams/pipeline.svg", type: "svg", content: pipelineSvg, isBinary: false, group: "diagrams" },
|
| 1513 |
+
{ name: "diagrams/architecture.svg", type: "svg", content: architectureSvg, isBinary: false, group: "diagrams" }
|
| 1514 |
+
];
|
| 1515 |
+
|
| 1516 |
+
return this.modelFiles;
|
| 1517 |
+
}
|
| 1518 |
+
|
| 1519 |
+
buildTestPy() {
|
| 1520 |
+
return [
|
| 1521 |
+
'#!/usr/bin/env python3',
|
| 1522 |
+
'"""',
|
| 1523 |
+
'test.py - Micro Distillery model chat wrapper.',
|
| 1524 |
+
'',
|
| 1525 |
+
'Loads model.json (a GRPO+VAE-distilled bigram model) and provides',
|
| 1526 |
+
'a simple REPL chat interface.',
|
| 1527 |
+
'',
|
| 1528 |
+
'Usage:',
|
| 1529 |
+
' python3 test.py # interactive chat',
|
| 1530 |
+
' python3 test.py hello # one-shot from prompt',
|
| 1531 |
+
' python3 test.py --temperature 1.2 # one-shot with custom temperature',
|
| 1532 |
+
' python3 test.py # (interactive also supports --temperature)',
|
| 1533 |
+
'"""',
|
| 1534 |
+
'import json',
|
| 1535 |
+
'import os',
|
| 1536 |
+
'import random',
|
| 1537 |
+
'import sys',
|
| 1538 |
+
'',
|
| 1539 |
+
'MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.json")',
|
| 1540 |
+
'',
|
| 1541 |
+
'',
|
| 1542 |
+
'def load_model(path=MODEL_PATH):',
|
| 1543 |
+
' with open(path, "r", encoding="utf-8") as f:',
|
| 1544 |
+
' return json.load(f)',
|
| 1545 |
+
'',
|
| 1546 |
+
'',
|
| 1547 |
+
'def sample_next(transitions, token, rng, temperature=1.0):',
|
| 1548 |
+
' bucket = transitions.get(token)',
|
| 1549 |
+
' if not bucket:',
|
| 1550 |
+
' return None',
|
| 1551 |
+
' tokens = list(bucket.keys())',
|
| 1552 |
+
' inv_t = 1.0 / max(temperature, 1e-3)',
|
| 1553 |
+
' weights = [max(bucket[t], 1e-12) ** inv_t for t in tokens]',
|
| 1554 |
+
' total = sum(weights)',
|
| 1555 |
+
' if total <= 0:',
|
| 1556 |
+
' return None',
|
| 1557 |
+
' r = rng.random() * total',
|
| 1558 |
+
' acc = 0.0',
|
| 1559 |
+
' for t, w in zip(tokens, weights):',
|
| 1560 |
+
' acc += w',
|
| 1561 |
+
' if r <= acc:',
|
| 1562 |
+
' return t',
|
| 1563 |
+
' return tokens[-1]',
|
| 1564 |
+
'',
|
| 1565 |
+
'',
|
| 1566 |
+
'def generate(model, prompt, max_tokens=32, temperature=0.9):',
|
| 1567 |
+
' transitions = model.get("transitions", {})',
|
| 1568 |
+
' bos = model.get("bos_token", "<|endoftext|>")',
|
| 1569 |
+
' eos = model.get("eos_token", "<|endoftext|>")',
|
| 1570 |
+
' seed = int(model.get("harmonic_seed", 1337)) & 0xFFFFFFFF',
|
| 1571 |
+
' rng = random.Random(seed)',
|
| 1572 |
+
'',
|
| 1573 |
+
' if prompt and prompt.strip():',
|
| 1574 |
+
' current = prompt.strip()[-1]',
|
| 1575 |
+
' prefix = prompt.strip()',
|
| 1576 |
+
' else:',
|
| 1577 |
+
' current = bos',
|
| 1578 |
+
' prefix = ""',
|
| 1579 |
+
'',
|
| 1580 |
+
' generated = ""',
|
| 1581 |
+
' for _ in range(max_tokens):',
|
| 1582 |
+
' nxt = sample_next(transitions, current, rng, temperature=temperature)',
|
| 1583 |
+
' if nxt is None or nxt == eos:',
|
| 1584 |
+
' break',
|
| 1585 |
+
' generated += nxt',
|
| 1586 |
+
' current = nxt',
|
| 1587 |
+
' return prefix + generated',
|
| 1588 |
+
'',
|
| 1589 |
+
'',
|
| 1590 |
+
'def main(temperature=0.9):',
|
| 1591 |
+
' print("=== Micro Distillery Model Chat ===")',
|
| 1592 |
+
' if not os.path.exists(MODEL_PATH):',
|
| 1593 |
+
' print("Error: " + MODEL_PATH + " not found.", file=sys.stderr)',
|
| 1594 |
+
' sys.exit(1)',
|
| 1595 |
+
' model = load_model()',
|
| 1596 |
+
' print("Format: " + str(model.get("format")))',
|
| 1597 |
+
' print("Vocab size: " + str(len(model.get("vocab", []))))',
|
| 1598 |
+
' print("Transitions: " + str(len(model.get("transitions", {}))))',
|
| 1599 |
+
' print("Seed: " + str(model.get("harmonic_seed")))',
|
| 1600 |
+
' print("Temperature: " + str(temperature))',
|
| 1601 |
+
' print("Type /quit to exit.\\n")',
|
| 1602 |
+
' while True:',
|
| 1603 |
+
' try:',
|
| 1604 |
+
' prompt = input("You: ").strip()',
|
| 1605 |
+
' except (EOFError, KeyboardInterrupt):',
|
| 1606 |
+
' print("\\nBye!")',
|
| 1607 |
+
' break',
|
| 1608 |
+
' if not prompt:',
|
| 1609 |
+
' continue',
|
| 1610 |
+
' if prompt.lower() in ("/quit", "/exit", "quit", "exit"):',
|
| 1611 |
+
' break',
|
| 1612 |
+
' out = generate(model, prompt, temperature=temperature)',
|
| 1613 |
+
' print("Bot: " + out)',
|
| 1614 |
+
'',
|
| 1615 |
+
'',
|
| 1616 |
+
'if __name__ == "__main__":',
|
| 1617 |
+
' args = sys.argv[1:]',
|
| 1618 |
+
' temperature = 0.9',
|
| 1619 |
+
' prompts = []',
|
| 1620 |
+
' i = 0',
|
| 1621 |
+
' while i < len(args):',
|
| 1622 |
+
' if args[i] == "--temperature" and i + 1 < len(args):',
|
| 1623 |
+
' try:',
|
| 1624 |
+
' temperature = float(args[i + 1])',
|
| 1625 |
+
' except ValueError:',
|
| 1626 |
+
' print("Warning: invalid --temperature value, using 0.9", file=sys.stderr)',
|
| 1627 |
+
' i += 2',
|
| 1628 |
+
' elif args[i] in ("--help", "-h"):',
|
| 1629 |
+
' print("Usage: python3 test.py [prompt...] [--temperature T]")',
|
| 1630 |
+
' sys.exit(0)',
|
| 1631 |
+
' elif args[i].startswith("--"):',
|
| 1632 |
+
' i += 1',
|
| 1633 |
+
' else:',
|
| 1634 |
+
' prompts.append(args[i])',
|
| 1635 |
+
' i += 1',
|
| 1636 |
+
' if prompts:',
|
| 1637 |
+
' model = load_model()',
|
| 1638 |
+
' print(generate(model, " ".join(prompts), temperature=temperature))',
|
| 1639 |
+
' else:',
|
| 1640 |
+
' main(temperature=temperature)',
|
| 1641 |
+
''
|
| 1642 |
+
].join("\n");
|
| 1643 |
+
}
|
| 1644 |
+
|
| 1645 |
+
buildTestSh() {
|
| 1646 |
+
return [
|
| 1647 |
+
'#!/usr/bin/env bash',
|
| 1648 |
+
'# test.sh - Test harness for the exported Micro Distillery model.',
|
| 1649 |
+
'set -euo pipefail',
|
| 1650 |
+
'cd "$(dirname "$0")"',
|
| 1651 |
+
'',
|
| 1652 |
+
'PY="${PYTHON:-python3}"',
|
| 1653 |
+
'',
|
| 1654 |
+
'echo "================================================"',
|
| 1655 |
+
'echo " Micro Distillery - Model Test Harness"',
|
| 1656 |
+
'echo "================================================"',
|
| 1657 |
+
'echo ""',
|
| 1658 |
+
'',
|
| 1659 |
+
'if ! command -v "$PY" >/dev/null 2>&1; then',
|
| 1660 |
+
' echo "ERROR: $PY not found. Install Python 3.8+." >&2',
|
| 1661 |
+
' echo " macOS: brew install python3" >&2',
|
| 1662 |
+
' echo " Ubuntu: sudo apt install python3" >&2',
|
| 1663 |
+
' echo " Windows: https://python.org/downloads/" >&2',
|
| 1664 |
+
' exit 1',
|
| 1665 |
+
'fi',
|
| 1666 |
+
'',
|
| 1667 |
+
'echo "[1/3] Python: $($PY --version 2>&1)"',
|
| 1668 |
+
'',
|
| 1669 |
+
'if [ ! -f model.json ]; then',
|
| 1670 |
+
' echo "ERROR: model.json not found in $(pwd)" >&2',
|
| 1671 |
+
' exit 1',
|
| 1672 |
+
'fi',
|
| 1673 |
+
'echo "[2/3] Found model.json"',
|
| 1674 |
+
'',
|
| 1675 |
+
'$PY -c "',
|
| 1676 |
+
'import json',
|
| 1677 |
+
'm = json.load(open(\'model.json\'))',
|
| 1678 |
+
'assert \'transitions\' in m, \'missing transitions\'',
|
| 1679 |
+
'assert \'vocab\' in m, \'missing vocab\'',
|
| 1680 |
+
'print(\'OK: format=%s vocab=%d transitions=%d\' % (m.get(\'format\',\'?\'), len(m[\'vocab\']), len(m[\'transitions\'])))',
|
| 1681 |
+
'"',
|
| 1682 |
+
'echo "[3/3] Model validation passed"',
|
| 1683 |
+
'echo ""',
|
| 1684 |
+
'echo "=== Sample Generations ==="',
|
| 1685 |
+
'for prompt in alice bob zoe liam; do',
|
| 1686 |
+
' echo -n " $prompt -> "',
|
| 1687 |
+
' $PY test.py "$prompt" 2>/dev/null | tail -1 || echo "(failed)"',
|
| 1688 |
+
'done',
|
| 1689 |
+
'echo ""',
|
| 1690 |
+
'echo "=== Interactive Chat (type /quit to exit) ==="',
|
| 1691 |
+
'exec $PY test.py',
|
| 1692 |
+
''
|
| 1693 |
+
].join("\n");
|
| 1694 |
+
}
|
| 1695 |
+
|
| 1696 |
+
buildTestReadme() {
|
| 1697 |
+
return [
|
| 1698 |
+
'# Testing Your Exported Model',
|
| 1699 |
+
'',
|
| 1700 |
+
'This bundle contains a fully runnable bigram language model. No GPU,',
|
| 1701 |
+
'no external dependencies - Python 3.8+ standard library only.',
|
| 1702 |
+
'',
|
| 1703 |
+
'## Files',
|
| 1704 |
+
'',
|
| 1705 |
+
'| File | Purpose |',
|
| 1706 |
+
'|------|---------|',
|
| 1707 |
+
'| `model.json` | The trained model (transitions + config + metrics) |',
|
| 1708 |
+
'| `test.py` | Interactive chat wrapper |',
|
| 1709 |
+
'| `test.sh` | Test harness (validates + generates samples) |',
|
| 1710 |
+
'| `run.sh` | Minimal launcher for test.py |',
|
| 1711 |
+
'',
|
| 1712 |
+
'## Quick Start',
|
| 1713 |
+
'',
|
| 1714 |
+
'### macOS / Linux',
|
| 1715 |
+
'',
|
| 1716 |
+
'```bash',
|
| 1717 |
+
'chmod +x test.sh run.sh',
|
| 1718 |
+
'./test.sh',
|
| 1719 |
+
'```',
|
| 1720 |
+
'',
|
| 1721 |
+
'### Windows (PowerShell)',
|
| 1722 |
+
'',
|
| 1723 |
+
'```powershell',
|
| 1724 |
+
'python test.py',
|
| 1725 |
+
'```',
|
| 1726 |
+
'',
|
| 1727 |
+
'### One-shot generation',
|
| 1728 |
+
'',
|
| 1729 |
+
'```bash',
|
| 1730 |
+
'python3 test.py alice',
|
| 1731 |
+
'# => alicea',
|
| 1732 |
+
'```',
|
| 1733 |
+
'',
|
| 1734 |
+
'### Custom temperature',
|
| 1735 |
+
'',
|
| 1736 |
+
'```bash',
|
| 1737 |
+
'python3 test.py --temperature 0.5',
|
| 1738 |
+
'python3 test.py alice --temperature 1.2',
|
| 1739 |
+
'```',
|
| 1740 |
+
'',
|
| 1741 |
+
'## Manual Test',
|
| 1742 |
+
'',
|
| 1743 |
+
'```bash',
|
| 1744 |
+
'python3 -c "import json; m=json.load(open(\'model.json\')); print(len(m[\'vocab\']), \'tokens,\', len(m[\'transitions\']), \'states\')"',
|
| 1745 |
+
'```',
|
| 1746 |
+
'',
|
| 1747 |
+
'Expected output: `<N> tokens, <M> states`.',
|
| 1748 |
+
'',
|
| 1749 |
+
'## Troubleshooting',
|
| 1750 |
+
'',
|
| 1751 |
+
'- **`python3 not found`** - install Python 3.8+ from https://python.org',
|
| 1752 |
+
'- **`model.json not found`** - make sure you are inside the extracted directory',
|
| 1753 |
+
'- **`Permission denied`** - run `chmod +x test.sh run.sh` first',
|
| 1754 |
+
''
|
| 1755 |
+
].join("\n");
|
| 1756 |
+
}
|
| 1757 |
+
|
| 1758 |
+
buildRunSh() {
|
| 1759 |
+
return [
|
| 1760 |
+
'#!/usr/bin/env bash',
|
| 1761 |
+
'# run.sh - Minimal launcher for test.py.',
|
| 1762 |
+
'set -euo pipefail',
|
| 1763 |
+
'cd "$(dirname "$0")"',
|
| 1764 |
+
'PY="${PYTHON:-python3}"',
|
| 1765 |
+
'if ! command -v "$PY" >/dev/null 2>&1; then',
|
| 1766 |
+
' echo "Error: python3 not found." >&2',
|
| 1767 |
+
' exit 1',
|
| 1768 |
+
'fi',
|
| 1769 |
+
'exec "$PY" test.py "$@"',
|
| 1770 |
+
''
|
| 1771 |
+
].join("\n");
|
| 1772 |
+
}
|
| 1773 |
+
|
| 1774 |
+
buildToParquetPy() {
|
| 1775 |
+
return [
|
| 1776 |
+
'#!/usr/bin/env python3',
|
| 1777 |
+
'"""Convert model.json to model.parquet.',
|
| 1778 |
+
'',
|
| 1779 |
+
'Requires: pandas, pyarrow',
|
| 1780 |
+
' pip install pandas pyarrow',
|
| 1781 |
+
'"""',
|
| 1782 |
+
'import json',
|
| 1783 |
+
'import os',
|
| 1784 |
+
'import sys',
|
| 1785 |
+
'',
|
| 1786 |
+
'MODEL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.json")',
|
| 1787 |
+
'OUT_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "model.parquet")',
|
| 1788 |
+
'',
|
| 1789 |
+
'',
|
| 1790 |
+
'def main():',
|
| 1791 |
+
' try:',
|
| 1792 |
+
' import pandas as pd',
|
| 1793 |
+
' except ImportError:',
|
| 1794 |
+
' print("Error: pandas required. Install: pip install pandas pyarrow", file=sys.stderr)',
|
| 1795 |
+
' sys.exit(1)',
|
| 1796 |
+
'',
|
| 1797 |
+
' with open(MODEL_PATH, "r", encoding="utf-8") as f:',
|
| 1798 |
+
' model = json.load(f)',
|
| 1799 |
+
'',
|
| 1800 |
+
' rows = []',
|
| 1801 |
+
' for state, bucket in model.get("transitions", {}).items():',
|
| 1802 |
+
' for nxt, prob in bucket.items():',
|
| 1803 |
+
' rows.append({',
|
| 1804 |
+
' "state": state,',
|
| 1805 |
+
' "next_token": nxt,',
|
| 1806 |
+
' "probability": float(prob),',
|
| 1807 |
+
' "harmonic_seed": int(model.get("harmonic_seed", 0)),',
|
| 1808 |
+
' "format": model.get("format", "unknown"),',
|
| 1809 |
+
' "version": model.get("version", "0.0"),',
|
| 1810 |
+
' })',
|
| 1811 |
+
'',
|
| 1812 |
+
' if not rows:',
|
| 1813 |
+
' print("Warning: no transitions. Writing metadata-only parquet.")',
|
| 1814 |
+
' rows = [{"state": "", "next_token": "", "probability": 0.0,',
|
| 1815 |
+
' "harmonic_seed": int(model.get("harmonic_seed", 0)),',
|
| 1816 |
+
' "format": model.get("format", "unknown"),',
|
| 1817 |
+
' "version": model.get("version", "0.0")}]',
|
| 1818 |
+
'',
|
| 1819 |
+
' df = pd.DataFrame(rows)',
|
| 1820 |
+
' df.to_parquet(OUT_PATH, index=False)',
|
| 1821 |
+
' print("Wrote " + str(len(df)) + " rows to " + OUT_PATH)',
|
| 1822 |
+
'',
|
| 1823 |
+
'',
|
| 1824 |
+
'if __name__ == "__main__":',
|
| 1825 |
+
' main()',
|
| 1826 |
+
''
|
| 1827 |
+
].join("\n");
|
| 1828 |
+
}
|
| 1829 |
+
|
| 1830 |
+
buildReadme(modelJson, vocabSize) {
|
| 1831 |
+
const transCount = Object.keys(modelJson.transitions).length;
|
| 1832 |
+
return [
|
| 1833 |
+
'---',
|
| 1834 |
+
'language:',
|
| 1835 |
+
' - en',
|
| 1836 |
+
'license: apache-2.0',
|
| 1837 |
+
'tags:',
|
| 1838 |
+
' - micro-distillery',
|
| 1839 |
+
' - bigram',
|
| 1840 |
+
' - grpo',
|
| 1841 |
+
' - vae',
|
| 1842 |
+
' - distilled',
|
| 1843 |
+
' - smollm2',
|
| 1844 |
+
' - qwen2.5',
|
| 1845 |
+
' - olmo',
|
| 1846 |
+
' - tinyllama',
|
| 1847 |
+
'library_name: micro-distillery',
|
| 1848 |
+
'---',
|
| 1849 |
+
'',
|
| 1850 |
+
'# Micro Distillery — Bigram Distilled Model',
|
| 1851 |
+
'',
|
| 1852 |
+
'This model was trained from scratch using a combination of:',
|
| 1853 |
+
'',
|
| 1854 |
+
'- **GRPO** (Group Relative Policy Optimization) for character-level trajectory training',
|
| 1855 |
+
'- **VAE filtering** to keep only high-quality trajectories',
|
| 1856 |
+
'- **4x quantum harmonic oscillator** superposition for deterministic seeding',
|
| 1857 |
+
'',
|
| 1858 |
+
'It is a compact character-level bigram model - no transformer weights,',
|
| 1859 |
+
'just transition probabilities. Runs on any machine with Python 3.8+.',
|
| 1860 |
+
'',
|
| 1861 |
+
'## Reference Architectures',
|
| 1862 |
+
'',
|
| 1863 |
+
'The bundle metadata references these modern (2025-2026) open models as',
|
| 1864 |
+
'inspiration sources. The actual model is a distilled bigram - not a',
|
| 1865 |
+
'transformer checkpoint - and does not load via `AutoModelForCausalLM`.',
|
| 1866 |
+
'',
|
| 1867 |
+
'- [SmolLM2-135M](https://huggingface.co/HuggingFaceTB/SmolLM2-135M) (HuggingFaceTB)',
|
| 1868 |
+
'- [Qwen2.5-0.5B](https://huggingface.co/Qwen/Qwen2.5-0.5B) (Qwen)',
|
| 1869 |
+
'- [OLMo-2-1B](https://huggingface.co/allenai/OLMo-2-0425-1B) (AllenAI)',
|
| 1870 |
+
'- [TinyLlama-1.1B](https://huggingface.co/TinyLlama/TinyLlama-1.1B-Chat-v1.0) (TinyLlama)',
|
| 1871 |
+
'',
|
| 1872 |
+
'## Model Details',
|
| 1873 |
+
'',
|
| 1874 |
+
'- **Format**: ' + modelJson.format,
|
| 1875 |
+
'- **Version**: ' + modelJson.version,
|
| 1876 |
+
'- **Reference arch**: ' + modelJson.arch,
|
| 1877 |
+
'- **Vocab size**: ' + vocabSize + ' tokens',
|
| 1878 |
+
'- **Transition states**: ' + transCount,
|
| 1879 |
+
'- **Harmonic seed**: ' + modelJson.harmonic_seed,
|
| 1880 |
+
'- **Created**: ' + modelJson.created,
|
| 1881 |
+
'',
|
| 1882 |
+
'## Quick Start',
|
| 1883 |
+
'',
|
| 1884 |
+
'```bash',
|
| 1885 |
+
'# 1. Extract the bundle',
|
| 1886 |
+
'unzip micro-distillery-*.zip',
|
| 1887 |
+
'cd micro-distillery-*',
|
| 1888 |
+
'',
|
| 1889 |
+
'# 2. Run the test harness',
|
| 1890 |
+
'chmod +x test.sh run.sh',
|
| 1891 |
+
'./test.sh',
|
| 1892 |
+
'',
|
| 1893 |
+
'# 3. Or chat directly',
|
| 1894 |
+
'python3 test.py',
|
| 1895 |
+
'```',
|
| 1896 |
+
'',
|
| 1897 |
+
'## Files in the Bundle',
|
| 1898 |
+
'',
|
| 1899 |
+
'| File | Description |',
|
| 1900 |
+
'|------|-------------|',
|
| 1901 |
+
'| `model.json` | The trained bigram model (all weights) |',
|
| 1902 |
+
'| `config.json` | HuggingFace-style metadata config |',
|
| 1903 |
+
'| `vocab.json` | Character vocabulary |',
|
| 1904 |
+
'| `tokenizer_config.json` | Tokenizer metadata |',
|
| 1905 |
+
'| `knowledge_graph.json` | 2D graph used to seed the model |',
|
| 1906 |
+
'| `training_logs.json` | Metrics + quantum measurements |',
|
| 1907 |
+
'| `test.py` | Interactive chat wrapper |',
|
| 1908 |
+
'| `test.sh` | Test harness |',
|
| 1909 |
+
'| `test_readme.md` | Testing instructions |',
|
| 1910 |
+
'| `run.sh` | Minimal launcher |',
|
| 1911 |
+
'| `to_parquet.py` | Convert to parquet |',
|
| 1912 |
+
'| `diagrams/pipeline.svg` | Training pipeline diagram |',
|
| 1913 |
+
'| `diagrams/architecture.svg` | Model architecture diagram |',
|
| 1914 |
+
'',
|
| 1915 |
+
'## Training Pipeline',
|
| 1916 |
+
'',
|
| 1917 |
+
'1. **Fetch** - pulls corpora and model cards from HuggingFace + GitHub (2026 open sources)',
|
| 1918 |
+
'2. **Knowledge graph** - builds a character-transition graph, laid out in 2D',
|
| 1919 |
+
'3. **Seed** - initializes bigram transition counts from the corpus',
|
| 1920 |
+
'4. **GRPO** - samples trajectories, scores with a name heuristic, boosts high-advantage transitions',
|
| 1921 |
+
'5. **VAE filter** - retains only high-probability trajectories',
|
| 1922 |
+
'6. **Quantum seed** - 4 harmonic oscillators (w = 1, phi, 2, pi) collapse to a deterministic seed',
|
| 1923 |
+
'7. **Export** - produces a HuggingFace-ready .zip bundle',
|
| 1924 |
+
'',
|
| 1925 |
+
'## Uploading to Hugging Face',
|
| 1926 |
+
'',
|
| 1927 |
+
'The bundle is structured so you can upload it directly to a HF repo:',
|
| 1928 |
+
'',
|
| 1929 |
+
'```bash',
|
| 1930 |
+
'pip install huggingface_hub',
|
| 1931 |
+
'huggingface-cli login',
|
| 1932 |
+
'huggingface-cli upload your-username/micro-distillery-bigram . --repo-type model',
|
| 1933 |
+
'```',
|
| 1934 |
+
'',
|
| 1935 |
+
'The `model.json` file is not a transformer checkpoint - use `test.py` to run it.',
|
| 1936 |
+
'`config.json` is provided for HF metadata and Datasets viewer compatibility.',
|
| 1937 |
+
'',
|
| 1938 |
+
'## Data Sources',
|
| 1939 |
+
'',
|
| 1940 |
+
'This bundle was produced by fetching from these free, open endpoints:',
|
| 1941 |
+
'',
|
| 1942 |
+
'- `raw.githubusercontent.com/karpathy/makemore/master/names.txt` (training corpus)',
|
| 1943 |
+
'- `huggingface.co/api/models?search=smollm2` (model reference list)',
|
| 1944 |
+
'- `huggingface.co/api/models?search=qwen2.5-0.5b` (model reference list)',
|
| 1945 |
+
'- `huggingface.co/HuggingFaceTB/SmolLM2-135M/raw/main/README.md` (model card)',
|
| 1946 |
+
'- `huggingface.co/allenai/OLMo-2-0425-1B/raw/main/README.md` (model card)',
|
| 1947 |
+
'- `api.github.com/repos/karpathy/makemore` (repo metadata)',
|
| 1948 |
+
'',
|
| 1949 |
+
'## License',
|
| 1950 |
+
'',
|
| 1951 |
+
'Apache 2.0',
|
| 1952 |
+
''
|
| 1953 |
+
].join("\n");
|
| 1954 |
+
}
|
| 1955 |
+
|
| 1956 |
+
buildPipelineSvg() {
|
| 1957 |
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 220" font-family="monospace">\n'
|
| 1958 |
+
+ '<rect width="900" height="220" fill="#000"/>\n'
|
| 1959 |
+
+ '<text x="20" y="30" fill="#e5e5e5" font-size="14" font-weight="bold">Micro Distillery - Training Pipeline</text>\n'
|
| 1960 |
+
+ '<g stroke="#e5e5e5" stroke-width="1.5" fill="#0a0a0a">\n'
|
| 1961 |
+
+ ' <rect x="20" y="70" width="140" height="70" rx="4"/>\n'
|
| 1962 |
+
+ ' <rect x="200" y="70" width="140" height="70" rx="4"/>\n'
|
| 1963 |
+
+ ' <rect x="380" y="70" width="140" height="70" rx="4"/>\n'
|
| 1964 |
+
+ ' <rect x="560" y="70" width="140" height="70" rx="4"/>\n'
|
| 1965 |
+
+ ' <rect x="740" y="70" width="140" height="70" rx="4"/>\n'
|
| 1966 |
+
+ '</g>\n'
|
| 1967 |
+
+ '<g fill="#e5e5e5" font-size="12" text-anchor="middle">\n'
|
| 1968 |
+
+ ' <text x="90" y="100">1. Fetch</text>\n'
|
| 1969 |
+
+ ' <text x="270" y="100">2. Bigram</text>\n'
|
| 1970 |
+
+ ' <text x="450" y="100">3. GRPO</text>\n'
|
| 1971 |
+
+ ' <text x="630" y="100">4. VAE</text>\n'
|
| 1972 |
+
+ ' <text x="810" y="100">5. Export</text>\n'
|
| 1973 |
+
+ '</g>\n'
|
| 1974 |
+
+ '<g fill="#888" font-size="10" text-anchor="middle">\n'
|
| 1975 |
+
+ ' <text x="90" y="122">HF + GitHub</text>\n'
|
| 1976 |
+
+ ' <text x="270" y="122">counts</text>\n'
|
| 1977 |
+
+ ' <text x="450" y="122">policy</text>\n'
|
| 1978 |
+
+ ' <text x="630" y="122">filter</text>\n'
|
| 1979 |
+
+ ' <text x="810" y="122">.zip</text>\n'
|
| 1980 |
+
+ '</g>\n'
|
| 1981 |
+
+ '<g stroke="#888" stroke-width="1.5" fill="none">\n'
|
| 1982 |
+
+ ' <path d="M160 105 L200 105" marker-end="url(#a)"/>\n'
|
| 1983 |
+
+ ' <path d="M340 105 L380 105" marker-end="url(#a)"/>\n'
|
| 1984 |
+
+ ' <path d="M520 105 L560 105" marker-end="url(#a)"/>\n'
|
| 1985 |
+
+ ' <path d="M700 105 L740 105" marker-end="url(#a)"/>\n'
|
| 1986 |
+
+ '</g>\n'
|
| 1987 |
+
+ '<defs><marker id="a" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#888"/></marker></defs>\n'
|
| 1988 |
+
+ '<text x="450" y="185" fill="#666" font-size="11" text-anchor="middle">4x harmonic oscillator (w=1, phi, 2, pi) provides deterministic seed</text>\n'
|
| 1989 |
+
+ '</svg>';
|
| 1990 |
+
}
|
| 1991 |
+
|
| 1992 |
+
buildArchitectureSvg() {
|
| 1993 |
+
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 300" font-family="monospace">\n'
|
| 1994 |
+
+ '<rect width="900" height="300" fill="#000"/>\n'
|
| 1995 |
+
+ '<text x="20" y="30" fill="#e5e5e5" font-size="14" font-weight="bold">Micro Distillery - Model Architecture</text>\n'
|
| 1996 |
+
+ '<text x="20" y="55" fill="#888" font-size="11">Bigram transition table + quantum-seeded decoding</text>\n'
|
| 1997 |
+
+ '<g fill="none" stroke="#e5e5e5" stroke-width="1.5">\n'
|
| 1998 |
+
+ ' <rect x="20" y="90" width="200" height="60" rx="4"/>\n'
|
| 1999 |
+
+ ' <rect x="20" y="180" width="200" height="60" rx="4"/>\n'
|
| 2000 |
+
+ ' <rect x="280" y="130" width="220" height="80" rx="4"/>\n'
|
| 2001 |
+
+ ' <rect x="560" y="90" width="180" height="60" rx="4"/>\n'
|
| 2002 |
+
+ ' <rect x="560" y="180" width="180" height="60" rx="4"/>\n'
|
| 2003 |
+
+ ' <rect x="790" y="130" width="90" height="80" rx="4"/>\n'
|
| 2004 |
+
+ '</g>\n'
|
| 2005 |
+
+ '<g fill="#e5e5e5" font-size="12" text-anchor="middle">\n'
|
| 2006 |
+
+ ' <text x="120" y="120">Input prompt</text>\n'
|
| 2007 |
+
+ ' <text x="120" y="210">Last character</text>\n'
|
| 2008 |
+
+ ' <text x="390" y="160">Transitions map</text>\n'
|
| 2009 |
+
+ ' <text x="390" y="180" fill="#888" font-size="10">state to {next: prob}</text>\n'
|
| 2010 |
+
+ ' <text x="650" y="120">Sample</text>\n'
|
| 2011 |
+
+ ' <text x="650" y="210">RNG (seeded)</text>\n'
|
| 2012 |
+
+ ' <text x="835" y="160">Output</text>\n'
|
| 2013 |
+
+ '</g>\n'
|
| 2014 |
+
+ '<g stroke="#888" stroke-width="1.5" fill="none">\n'
|
| 2015 |
+
+ ' <path d="M220 120 L280 145" marker-end="url(#b)"/>\n'
|
| 2016 |
+
+ ' <path d="M220 210 L280 195" marker-end="url(#b)"/>\n'
|
| 2017 |
+
+ ' <path d="M500 150 L560 130" marker-end="url(#b)"/>\n'
|
| 2018 |
+
+ ' <path d="M500 190 L560 210" marker-end="url(#b)"/>\n'
|
| 2019 |
+
+ ' <path d="M740 120 L790 145" marker-end="url(#b)"/>\n'
|
| 2020 |
+
+ ' <path d="M740 210 L790 195" marker-end="url(#b)"/>\n'
|
| 2021 |
+
+ '</g>\n'
|
| 2022 |
+
+ '<defs><marker id="b" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#888"/></marker></defs>\n'
|
| 2023 |
+
+ '<text x="450" y="280" fill="#666" font-size="10" text-anchor="middle">Deterministic given seed - output reproducible across machines</text>\n'
|
| 2024 |
+
+ '</svg>';
|
| 2025 |
+
}
|
| 2026 |
+
|
| 2027 |
+
async showExportModal() {
|
| 2028 |
+
if (!this.trainDone) {
|
| 2029 |
+
this.log("Warning: export without training. Bundle will use current transitions.", "warn");
|
| 2030 |
+
}
|
| 2031 |
+
await this.buildFiles();
|
| 2032 |
+
const filtered = this.applyExportOptions();
|
| 2033 |
+
$("export-modal").classList.remove("hidden");
|
| 2034 |
+
requestAnimationFrame(() => $("export-modal").classList.add("show"));
|
| 2035 |
+
this.renderFileList(filtered);
|
| 2036 |
+
this.renderExportSize(filtered);
|
| 2037 |
+
}
|
| 2038 |
+
|
| 2039 |
+
hideExportModal() {
|
| 2040 |
+
$("export-modal").classList.remove("show");
|
| 2041 |
+
setTimeout(() => $("export-modal").classList.add("hidden"), 200);
|
| 2042 |
+
}
|
| 2043 |
+
|
| 2044 |
+
applyExportOptions() {
|
| 2045 |
+
const opts = {
|
| 2046 |
+
config: $("opt-config").checked,
|
| 2047 |
+
vocab: $("opt-vocab").checked,
|
| 2048 |
+
logs: $("opt-logs").checked,
|
| 2049 |
+
kg: $("opt-kg").checked,
|
| 2050 |
+
diagrams: $("opt-diagrams").checked,
|
| 2051 |
+
parquet: $("opt-parquet").checked
|
| 2052 |
+
};
|
| 2053 |
+
return this.modelFiles.filter((f) => {
|
| 2054 |
+
if (f.group === "core") return true;
|
| 2055 |
+
if (f.group === "tests") return true;
|
| 2056 |
+
if (f.group === "config" && !opts.config) return false;
|
| 2057 |
+
if (f.group === "vocab" && !opts.vocab) return false;
|
| 2058 |
+
if (f.group === "logs" && !opts.logs) return false;
|
| 2059 |
+
if (f.group === "kg" && !opts.kg) return false;
|
| 2060 |
+
if (f.group === "diagrams" && !opts.diagrams) return false;
|
| 2061 |
+
if (f.group === "parquet" && !opts.parquet) return false;
|
| 2062 |
+
return true;
|
| 2063 |
+
});
|
| 2064 |
+
}
|
| 2065 |
+
|
| 2066 |
+
renderFileList(files) {
|
| 2067 |
+
const list = $("file-list");
|
| 2068 |
+
list.innerHTML = "";
|
| 2069 |
+
files.forEach((f, i) => {
|
| 2070 |
+
const row = document.createElement("div");
|
| 2071 |
+
row.className = "file-row" + (i === this.selectedPreviewIndex ? " selected" : "");
|
| 2072 |
+
const size = f.content ? f.content.length : 0;
|
| 2073 |
+
row.innerHTML = '<span class="file-row-name">' + escapeHtml(f.name) + '</span>' +
|
| 2074 |
+
'<span class="file-row-size">' + humanSize(size) + '</span>';
|
| 2075 |
+
row.addEventListener("click", () => {
|
| 2076 |
+
this.selectedPreviewIndex = i;
|
| 2077 |
+
this.renderFileList(files);
|
| 2078 |
+
this.renderPreview(files, i);
|
| 2079 |
+
});
|
| 2080 |
+
list.appendChild(row);
|
| 2081 |
+
});
|
| 2082 |
+
if (files.length > 0) {
|
| 2083 |
+
if (this.selectedPreviewIndex >= files.length) this.selectedPreviewIndex = 0;
|
| 2084 |
+
this.renderPreview(files, this.selectedPreviewIndex);
|
| 2085 |
+
}
|
| 2086 |
+
}
|
| 2087 |
+
|
| 2088 |
+
renderPreview(files, idx) {
|
| 2089 |
+
const f = files[idx];
|
| 2090 |
+
if (!f) return;
|
| 2091 |
+
let content = f.content || "";
|
| 2092 |
+
if (content.length > 8000) content = content.substring(0, 8000) + "\n\n... (truncated preview)";
|
| 2093 |
+
$("file-preview").textContent = content;
|
| 2094 |
+
}
|
| 2095 |
+
|
| 2096 |
+
renderExportSize(files) {
|
| 2097 |
+
const total = files.reduce((s, f) => s + (f.content ? f.content.length : 0), 0);
|
| 2098 |
+
$("export-size").textContent = files.length + " files " + humanSize(total);
|
| 2099 |
+
}
|
| 2100 |
+
|
| 2101 |
+
async downloadBundle() {
|
| 2102 |
+
const files = this.applyExportOptions();
|
| 2103 |
+
if (files.length === 0) { this.log("No files selected.", "err"); return; }
|
| 2104 |
+
if (typeof JSZip === "undefined") {
|
| 2105 |
+
this.log("JSZip library not available (CDN blocked?). Cannot build .zip.", "err");
|
| 2106 |
+
return;
|
| 2107 |
+
}
|
| 2108 |
+
if (typeof saveAs === "undefined") {
|
| 2109 |
+
this.log("FileSaver library not available (CDN blocked?). Cannot download.", "err");
|
| 2110 |
+
return;
|
| 2111 |
+
}
|
| 2112 |
+
this.log("Building .zip bundle...");
|
| 2113 |
+
$("btn-download").disabled = true;
|
| 2114 |
+
try {
|
| 2115 |
+
const zip = new JSZip();
|
| 2116 |
+
for (const f of files) zip.file(f.name, f.content);
|
| 2117 |
+
const blob = await zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6 } });
|
| 2118 |
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
| 2119 |
+
const name = "micro-distillery-" + ts + ".zip";
|
| 2120 |
+
saveAs(blob, name);
|
| 2121 |
+
this.log("Bundle downloaded: " + name + " (" + humanSize(blob.size) + ")", "ok");
|
| 2122 |
+
try { await this.storage.saveModel("latest", { timestamp: Date.now(), files: files.length }); } catch (e) { /* ignored */ }
|
| 2123 |
+
setTimeout(() => this.hideExportModal(), 800);
|
| 2124 |
+
} catch (e) {
|
| 2125 |
+
this.log("Export failed: " + e.message, "err");
|
| 2126 |
+
} finally {
|
| 2127 |
+
$("btn-download").disabled = false;
|
| 2128 |
+
}
|
| 2129 |
+
}
|
| 2130 |
+
|
| 2131 |
+
reset() {
|
| 2132 |
+
this.fetchDone = false;
|
| 2133 |
+
this.trainDone = false;
|
| 2134 |
+
this.fetching = false;
|
| 2135 |
+
this.training = false;
|
| 2136 |
+
this.transitions = new Map();
|
| 2137 |
+
this.vocabCounts = new Map();
|
| 2138 |
+
this.graph = new KnowledgeGraph();
|
| 2139 |
+
this.kgSeed = 42;
|
| 2140 |
+
this.quantumMeasurements = [];
|
| 2141 |
+
this.corpus = FALLBACK_NAMES.slice();
|
| 2142 |
+
this.metrics = { trainingSteps: 0, finalLoss: 0, averageReward: 0, cacheHits: 0, cacheMisses: 0, filteredCount: 0, totalSamples: 0, vaeLoss: 0 };
|
| 2143 |
+
this.markStep("fetch", "active", "");
|
| 2144 |
+
this.markStep("train", "", "");
|
| 2145 |
+
this.markStep("export", "", "");
|
| 2146 |
+
this.updateMetrics();
|
| 2147 |
+
this.renderGraph();
|
| 2148 |
+
this.setProgress(0, "idle");
|
| 2149 |
+
this.setProgressLabel("idle");
|
| 2150 |
+
this.updateOscDisplay();
|
| 2151 |
+
$("osc-result").textContent = "";
|
| 2152 |
+
this.log("System reset.");
|
| 2153 |
+
}
|
| 2154 |
+
}
|
| 2155 |
+
|
| 2156 |
+
class Wizard {
|
| 2157 |
+
constructor(engine) {
|
| 2158 |
+
this.engine = engine;
|
| 2159 |
+
this.step = 0;
|
| 2160 |
+
this.steps = ["welcome", "fetch", "train", "export", "done"];
|
| 2161 |
+
}
|
| 2162 |
+
open() {
|
| 2163 |
+
this.step = 0;
|
| 2164 |
+
this.render();
|
| 2165 |
+
$("wizard").classList.remove("hidden");
|
| 2166 |
+
requestAnimationFrame(() => $("wizard").classList.add("show"));
|
| 2167 |
+
}
|
| 2168 |
+
close() {
|
| 2169 |
+
$("wizard").classList.remove("show");
|
| 2170 |
+
setTimeout(() => $("wizard").classList.add("hidden"), 200);
|
| 2171 |
+
}
|
| 2172 |
+
progressDots() {
|
| 2173 |
+
return this.steps.map((_, i) => {
|
| 2174 |
+
let cls = "wiz-dot";
|
| 2175 |
+
if (i < this.step) cls += " done";
|
| 2176 |
+
else if (i === this.step) cls += " active";
|
| 2177 |
+
return '<div class="' + cls + '"></div>';
|
| 2178 |
+
}).join("");
|
| 2179 |
+
}
|
| 2180 |
+
render() {
|
| 2181 |
+
const id = this.steps[this.step];
|
| 2182 |
+
$("wiz-progress").innerHTML = this.progressDots();
|
| 2183 |
+
|
| 2184 |
+
const content = {
|
| 2185 |
+
welcome: {
|
| 2186 |
+
title: "Micro Distillery",
|
| 2187 |
+
sub: "Build a small language model in 3 automated steps.",
|
| 2188 |
+
body: [
|
| 2189 |
+
"<p>This tool builds a runnable character-level language model from free open data (HuggingFace + GitHub, 2026 sources). No installation needed.</p>",
|
| 2190 |
+
"<p>You will:</p>",
|
| 2191 |
+
"<ul>",
|
| 2192 |
+
"<li><strong>Fetch</strong> a training corpus from open endpoints (SmolLM2, Qwen2.5, OLMo-2 cards + Karpathy's makemore corpus)</li>",
|
| 2193 |
+
"<li><strong>Train</strong> the model with GRPO + VAE filtering</li>",
|
| 2194 |
+
"<li><strong>Export</strong> a HuggingFace-ready .zip bundle</li>",
|
| 2195 |
+
"</ul>",
|
| 2196 |
+
'<div class="wiz-info">',
|
| 2197 |
+
"<strong>Time:</strong> ~30 seconds total. <strong>Cost:</strong> free. <strong>Data:</strong> only from public sources.",
|
| 2198 |
+
"</div>"
|
| 2199 |
+
],
|
| 2200 |
+
next: "Begin",
|
| 2201 |
+
back: null
|
| 2202 |
+
},
|
| 2203 |
+
fetch: {
|
| 2204 |
+
title: "Step 1 - Fetch Data",
|
| 2205 |
+
sub: "Pull a training corpus from free, open endpoints.",
|
| 2206 |
+
body: [
|
| 2207 |
+
"<p>We will fetch:</p>",
|
| 2208 |
+
"<ul>",
|
| 2209 |
+
"<li>Character corpus from <code>raw.githubusercontent.com/karpathy/makemore</code></li>",
|
| 2210 |
+
"<li>Model metadata from <code>huggingface.co/api/models?search=smollm2</code> (SmolLM2)</li>",
|
| 2211 |
+
"<li>Model metadata from <code>huggingface.co/api/models?search=qwen2.5-0.5b</code> (Qwen2.5)</li>",
|
| 2212 |
+
"<li>Reference cards from SmolLM2-135M and OLMo-2-1B</li>",
|
| 2213 |
+
"</ul>",
|
| 2214 |
+
"<p>If the network is unavailable, a bundled fallback corpus (200+ names) is used instead.</p>",
|
| 2215 |
+
'<div class="wiz-info">',
|
| 2216 |
+
'<strong>Status:</strong> <span id="wiz-fetch-status">not started</span>',
|
| 2217 |
+
"</div>",
|
| 2218 |
+
'<button class="btn btn-primary btn-lg btn-block" id="wiz-run-fetch">Fetch Data</button>'
|
| 2219 |
+
],
|
| 2220 |
+
next: "Continue",
|
| 2221 |
+
back: null
|
| 2222 |
+
},
|
| 2223 |
+
train: {
|
| 2224 |
+
title: "Step 2 - Train Model",
|
| 2225 |
+
sub: "Run 60 GRPO steps with VAE filtering.",
|
| 2226 |
+
body: [
|
| 2227 |
+
"<p>Training optimizes a character bigram model using:</p>",
|
| 2228 |
+
"<ul>",
|
| 2229 |
+
"<li><strong>GRPO</strong> - group-relative policy optimization on sampled trajectories</li>",
|
| 2230 |
+
"<li><strong>VAE filter</strong> - keeps only high-probability trajectories</li>",
|
| 2231 |
+
"</ul>",
|
| 2232 |
+
'<div class="wiz-info">',
|
| 2233 |
+
'<strong>Status:</strong> <span id="wiz-train-status">waiting for data</span>',
|
| 2234 |
+
"</div>",
|
| 2235 |
+
'<button class="btn btn-primary btn-lg btn-block" id="wiz-run-train">Train Model</button>'
|
| 2236 |
+
],
|
| 2237 |
+
next: "Continue",
|
| 2238 |
+
back: null
|
| 2239 |
+
},
|
| 2240 |
+
export: {
|
| 2241 |
+
title: "Step 3 - Export Bundle",
|
| 2242 |
+
sub: "Download a HuggingFace-ready .zip.",
|
| 2243 |
+
body: [
|
| 2244 |
+
"<p>The bundle includes:</p>",
|
| 2245 |
+
"<ul>",
|
| 2246 |
+
"<li><code>model.json</code>, <code>config.json</code>, <code>vocab.json</code></li>",
|
| 2247 |
+
"<li><code>README.md</code> model card (HF-formatted with YAML frontmatter)</li>",
|
| 2248 |
+
"<li><code>test.py</code>, <code>test.sh</code>, <code>test_readme.md</code></li>",
|
| 2249 |
+
"<li><code>diagrams/pipeline.svg</code>, <code>diagrams/architecture.svg</code></li>",
|
| 2250 |
+
"<li><code>to_parquet.py</code> for parquet conversion</li>",
|
| 2251 |
+
"<li><code>LICENSE</code>, <code>.gitattributes</code>, <code>requirements.txt</code></li>",
|
| 2252 |
+
"</ul>",
|
| 2253 |
+
'<div class="wiz-info">',
|
| 2254 |
+
'<strong>Status:</strong> <span id="wiz-export-status">ready</span>',
|
| 2255 |
+
"</div>",
|
| 2256 |
+
'<button class="btn btn-primary btn-lg btn-block" id="wiz-run-export">Download .zip</button>'
|
| 2257 |
+
],
|
| 2258 |
+
next: "Done",
|
| 2259 |
+
back: null
|
| 2260 |
+
},
|
| 2261 |
+
done: {
|
| 2262 |
+
title: "Complete",
|
| 2263 |
+
sub: "Your bundle has been downloaded.",
|
| 2264 |
+
body: [
|
| 2265 |
+
"<p>Next steps:</p>",
|
| 2266 |
+
"<ul>",
|
| 2267 |
+
"<li>Unzip the bundle</li>",
|
| 2268 |
+
"<li>Run <code>./test.sh</code> to validate and chat</li>",
|
| 2269 |
+
"<li>Upload the folder to HuggingFace via <code>huggingface-cli upload</code></li>",
|
| 2270 |
+
"<li>Or run <code>python3 to_parquet.py</code> to convert to parquet</li>",
|
| 2271 |
+
"</ul>",
|
| 2272 |
+
'<div class="wiz-info">',
|
| 2273 |
+
"<strong>Tip:</strong> Click <em>Open Workspace</em> for advanced controls (graph, oscillator, custom settings).",
|
| 2274 |
+
"</div>"
|
| 2275 |
+
],
|
| 2276 |
+
next: "Open Workspace",
|
| 2277 |
+
back: null
|
| 2278 |
+
}
|
| 2279 |
+
}[id];
|
| 2280 |
+
|
| 2281 |
+
$("wiz-title").textContent = content.title;
|
| 2282 |
+
$("wiz-sub").textContent = content.sub;
|
| 2283 |
+
$("wiz-content").innerHTML = content.body.join("");
|
| 2284 |
+
$("wiz-back").style.visibility = this.step === 0 ? "hidden" : "visible";
|
| 2285 |
+
$("wiz-next").textContent = content.next;
|
| 2286 |
+
|
| 2287 |
+
if (id === "welcome" || id === "done") {
|
| 2288 |
+
$("wiz-skip").textContent = id === "done" ? "Close" : "Skip wizard";
|
| 2289 |
+
} else {
|
| 2290 |
+
$("wiz-skip").textContent = "Skip wizard";
|
| 2291 |
+
}
|
| 2292 |
+
|
| 2293 |
+
if (id === "fetch") {
|
| 2294 |
+
const btn = $("wiz-run-fetch");
|
| 2295 |
+
if (btn) btn.addEventListener("click", async () => {
|
| 2296 |
+
btn.disabled = true;
|
| 2297 |
+
$("wiz-fetch-status").textContent = "fetching...";
|
| 2298 |
+
const ok = await this.engine.fetch();
|
| 2299 |
+
$("wiz-fetch-status").textContent = ok ? "complete - " + this.engine.corpus.length + " lines" : "failed";
|
| 2300 |
+
btn.disabled = false;
|
| 2301 |
+
if (ok) setTimeout(() => this.next(), 500);
|
| 2302 |
+
});
|
| 2303 |
+
}
|
| 2304 |
+
if (id === "train") {
|
| 2305 |
+
const btn = $("wiz-run-train");
|
| 2306 |
+
if (!this.engine.fetchDone) {
|
| 2307 |
+
$("wiz-train-status").textContent = "data required - go back";
|
| 2308 |
+
if (btn) btn.disabled = true;
|
| 2309 |
+
}
|
| 2310 |
+
if (btn) btn.addEventListener("click", async () => {
|
| 2311 |
+
btn.disabled = true;
|
| 2312 |
+
$("wiz-train-status").textContent = "training...";
|
| 2313 |
+
const ok = await this.engine.train();
|
| 2314 |
+
$("wiz-train-status").textContent = ok ? "complete - loss " + this.engine.metrics.finalLoss.toFixed(4) : "failed";
|
| 2315 |
+
btn.disabled = false;
|
| 2316 |
+
if (ok) setTimeout(() => this.next(), 500);
|
| 2317 |
+
});
|
| 2318 |
+
}
|
| 2319 |
+
if (id === "export") {
|
| 2320 |
+
const btn = $("wiz-run-export");
|
| 2321 |
+
if (!this.engine.trainDone) {
|
| 2322 |
+
$("wiz-export-status").textContent = "training required - go back";
|
| 2323 |
+
if (btn) btn.disabled = true;
|
| 2324 |
+
}
|
| 2325 |
+
if (btn) btn.addEventListener("click", async () => {
|
| 2326 |
+
btn.disabled = true;
|
| 2327 |
+
$("wiz-export-status").textContent = "building .zip...";
|
| 2328 |
+
await this.engine.showExportModal();
|
| 2329 |
+
$("wiz-export-status").textContent = "ready - click Download in the export panel";
|
| 2330 |
+
btn.disabled = false;
|
| 2331 |
+
});
|
| 2332 |
+
}
|
| 2333 |
+
}
|
| 2334 |
+
next() {
|
| 2335 |
+
if (this.step === this.steps.length - 1) { this.close(); return; }
|
| 2336 |
+
if (this.steps[this.step] === "fetch" && !this.engine.fetchDone) {
|
| 2337 |
+
this.engine.log("Fetch data before continuing.", "warn");
|
| 2338 |
+
return;
|
| 2339 |
+
}
|
| 2340 |
+
if (this.steps[this.step] === "train" && !this.engine.trainDone) {
|
| 2341 |
+
this.engine.log("Train the model before continuing.", "warn");
|
| 2342 |
+
return;
|
| 2343 |
+
}
|
| 2344 |
+
this.step++;
|
| 2345 |
+
this.render();
|
| 2346 |
+
}
|
| 2347 |
+
prev() { if (this.step > 0) { this.step--; this.render(); } }
|
| 2348 |
+
}
|
| 2349 |
+
|
| 2350 |
+
let engine;
|
| 2351 |
+
let wizard;
|
| 2352 |
+
|
| 2353 |
+
function setupTabs() {
|
| 2354 |
+
document.querySelectorAll(".tab").forEach((tab) => {
|
| 2355 |
+
tab.addEventListener("click", () => {
|
| 2356 |
+
const id = tab.dataset.tab;
|
| 2357 |
+
document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active"));
|
| 2358 |
+
document.querySelectorAll(".tabpanel").forEach((p) => p.classList.remove("active"));
|
| 2359 |
+
tab.classList.add("active");
|
| 2360 |
+
const panel = document.querySelector('[data-tabpanel="' + id + '"]');
|
| 2361 |
+
if (panel) panel.classList.add("active");
|
| 2362 |
+
if (id === "graph") engine.renderGraph();
|
| 2363 |
+
});
|
| 2364 |
+
});
|
| 2365 |
+
}
|
| 2366 |
+
|
| 2367 |
+
function setupSliders() {
|
| 2368 |
+
const bindings = [
|
| 2369 |
+
["cfg-group", "val-group", 0],
|
| 2370 |
+
["cfg-kl", "val-kl", 3],
|
| 2371 |
+
["cfg-latent", "val-latent", 0],
|
| 2372 |
+
["cfg-filter", "val-filter", 2]
|
| 2373 |
+
];
|
| 2374 |
+
for (const [id, display, decimals] of bindings) {
|
| 2375 |
+
const sl = $(id);
|
| 2376 |
+
const vd = $(display);
|
| 2377 |
+
if (!sl || !vd) continue;
|
| 2378 |
+
sl.addEventListener("input", () => {
|
| 2379 |
+
vd.textContent = parseFloat(sl.value).toFixed(decimals);
|
| 2380 |
+
});
|
| 2381 |
+
}
|
| 2382 |
+
}
|
| 2383 |
+
|
| 2384 |
+
function setupCloseButtons() {
|
| 2385 |
+
document.querySelectorAll("[data-close]").forEach((btn) => {
|
| 2386 |
+
btn.addEventListener("click", () => {
|
| 2387 |
+
const id = btn.dataset.close;
|
| 2388 |
+
if (id === "export-modal") engine.hideExportModal();
|
| 2389 |
+
});
|
| 2390 |
+
});
|
| 2391 |
+
document.querySelectorAll(".modal").forEach((modal) => {
|
| 2392 |
+
modal.addEventListener("click", (e) => {
|
| 2393 |
+
if (e.target === modal) {
|
| 2394 |
+
if (modal.id === "export-modal") engine.hideExportModal();
|
| 2395 |
+
if (modal.id === "wizard") wizard.close();
|
| 2396 |
+
}
|
| 2397 |
+
});
|
| 2398 |
+
});
|
| 2399 |
+
}
|
| 2400 |
+
|
| 2401 |
+
function setupButtons() {
|
| 2402 |
+
$("btn-fetch").addEventListener("click", async () => {
|
| 2403 |
+
$("btn-fetch").disabled = true;
|
| 2404 |
+
await engine.fetch();
|
| 2405 |
+
$("btn-fetch").disabled = false;
|
| 2406 |
+
});
|
| 2407 |
+
$("btn-train").addEventListener("click", async () => {
|
| 2408 |
+
$("btn-train").disabled = true;
|
| 2409 |
+
await engine.train();
|
| 2410 |
+
$("btn-train").disabled = false;
|
| 2411 |
+
});
|
| 2412 |
+
$("btn-stop").addEventListener("click", () => engine.stop());
|
| 2413 |
+
$("btn-export").addEventListener("click", () => engine.showExportModal());
|
| 2414 |
+
$("btn-export-2").addEventListener("click", () => engine.showExportModal());
|
| 2415 |
+
$("btn-wizard").addEventListener("click", () => wizard.open());
|
| 2416 |
+
$("btn-reset").addEventListener("click", () => {
|
| 2417 |
+
if (confirm("Reset all state? This clears the model and metrics.")) engine.reset();
|
| 2418 |
+
});
|
| 2419 |
+
$("btn-download").addEventListener("click", () => engine.downloadBundle());
|
| 2420 |
+
|
| 2421 |
+
$("btn-graph-render").addEventListener("click", () => {
|
| 2422 |
+
engine.graph.layout2D(engine.kgSeed);
|
| 2423 |
+
engine.renderGraph();
|
| 2424 |
+
});
|
| 2425 |
+
$("btn-graph-save").addEventListener("click", async () => {
|
| 2426 |
+
try { await engine.storage.saveGraph(engine.graph); engine.log("Graph saved to IndexedDB.", "ok"); }
|
| 2427 |
+
catch (e) { engine.log("Save failed: " + e.message, "err"); }
|
| 2428 |
+
});
|
| 2429 |
+
$("btn-graph-load").addEventListener("click", async () => {
|
| 2430 |
+
try { engine.graph = await engine.storage.loadGraph(); engine.renderGraph(); engine.log("Graph loaded.", "ok"); }
|
| 2431 |
+
catch (e) { engine.log("Load failed: " + e.message, "err"); }
|
| 2432 |
+
});
|
| 2433 |
+
$("btn-graph-clear").addEventListener("click", async () => {
|
| 2434 |
+
try { await engine.storage.clearGraph(); engine.graph = new KnowledgeGraph(); engine.renderGraph(); engine.log("Graph cleared.", "ok"); }
|
| 2435 |
+
catch (e) { engine.log("Clear failed: " + e.message, "err"); }
|
| 2436 |
+
});
|
| 2437 |
+
$("btn-osc-measure").addEventListener("click", () => engine.measureOsc());
|
| 2438 |
+
|
| 2439 |
+
$("wiz-next").addEventListener("click", () => wizard.next());
|
| 2440 |
+
$("wiz-back").addEventListener("click", () => wizard.prev());
|
| 2441 |
+
$("wiz-skip").addEventListener("click", () => wizard.close());
|
| 2442 |
+
}
|
| 2443 |
+
|
| 2444 |
+
function boot() {
|
| 2445 |
+
engine = new Engine();
|
| 2446 |
+
wizard = new Wizard(engine);
|
| 2447 |
+
|
| 2448 |
+
engine.log("Micro Distillery v3.1.1 initialized.");
|
| 2449 |
+
engine.log("Data sources (2026): HuggingFace (SmolLM2, Qwen2.5, OLMo-2) + GitHub (Karpathy makemore)");
|
| 2450 |
+
engine.log("Click Setup Wizard in the header, or use the pipeline.");
|
| 2451 |
+
engine.log("Ready.");
|
| 2452 |
+
|
| 2453 |
+
setupTabs();
|
| 2454 |
+
setupSliders();
|
| 2455 |
+
setupCloseButtons();
|
| 2456 |
+
setupButtons();
|
| 2457 |
+
engine.updateMetrics();
|
| 2458 |
+
engine.updateOscDisplay();
|
| 2459 |
+
engine.renderGraph();
|
| 2460 |
+
engine.setProgress(0, "idle");
|
| 2461 |
+
|
| 2462 |
+
const seen = localStorage.getItem("md_wizard_seen");
|
| 2463 |
+
if (!seen) {
|
| 2464 |
+
setTimeout(() => {
|
| 2465 |
+
wizard.open();
|
| 2466 |
+
localStorage.setItem("md_wizard_seen", "1");
|
| 2467 |
+
}, 400);
|
| 2468 |
+
}
|
| 2469 |
+
|
| 2470 |
+
const offlineBar = $("offline-bar");
|
| 2471 |
+
const updateOnline = () => offlineBar.classList.toggle("hidden", navigator.onLine);
|
| 2472 |
+
window.addEventListener("online", updateOnline);
|
| 2473 |
+
window.addEventListener("offline", updateOnline);
|
| 2474 |
+
updateOnline();
|
| 2475 |
+
|
| 2476 |
+
window.addEventListener("resize", () => engine.renderGraph());
|
| 2477 |
+
|
| 2478 |
+
if ("serviceWorker" in navigator) {
|
| 2479 |
+
navigator.serviceWorker.register("sw.js").catch(() => { /* ignored */ });
|
| 2480 |
+
}
|
| 2481 |
+
}
|
| 2482 |
+
|
| 2483 |
+
window.addEventListener("load", boot);
|
| 2484 |
+
</script>
|
| 2485 |
+
</body>
|
| 2486 |
+
</html>
|