vectorplasticity commited on
Commit
14f2bdc
·
verified ·
1 Parent(s): f676ff7

Simplify dashboard - fix column mapping, job tracking, and form submission

Browse files
Files changed (1) hide show
  1. app/templates/dashboard.html +280 -545
app/templates/dashboard.html CHANGED
@@ -310,6 +310,20 @@
310
  background: rgba(255,255,255,0.3);
311
  color: white;
312
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  </style>
314
  </head>
315
  <body>
@@ -410,14 +424,17 @@
410
  <div class="row g-4">
411
  <div class="col-lg-8">
412
  <div class="card">
413
- <div class="card-header">
414
- <i class="bi bi-clock-history me-2"></i>Recent Jobs
 
 
 
415
  </div>
416
  <div class="card-body">
417
- <div id="recent-jobs-list">
418
  <div class="text-center text-muted py-4">
419
  <i class="bi bi-inbox fs-1 d-block mb-2"></i>
420
- No jobs yet. Start a new training!
421
  </div>
422
  </div>
423
  </div>
@@ -504,11 +521,6 @@
504
  <div class="step-connector" id="connector-4-5"></div>
505
  <div class="step" id="step-5">
506
  <div class="step-number">5</div>
507
- <span class="step-label">Prompt</span>
508
- </div>
509
- <div class="step-connector" id="connector-5-6"></div>
510
- <div class="step" id="step-6">
511
- <div class="step-number">6</div>
512
  <span class="step-label">Train</span>
513
  </div>
514
  </div>
@@ -619,407 +631,114 @@
619
  <div id="dataset-recommendations" class="mt-3"></div>
620
  </div>
621
 
622
- <!-- Step 4: Dataset Configuration -->
623
  <div class="mb-4" id="form-step-4" style="display: none;">
624
- <h5 class="mb-3"><i class="bi bi-4-circle me-2"></i>Configure Dataset</h5>
625
- <div id="dataset-config-container">
626
  <div class="text-center text-muted py-4">
627
  <i class="bi bi-database fs-1 d-block mb-2"></i>
628
- Select a dataset to configure splits and columns
629
  </div>
630
  </div>
631
  </div>
632
 
633
- <!-- Step 5: Prompt Template Configuration -->
634
  <div class="mb-4" id="form-step-5" style="display: none;">
635
- <h5 class="mb-3"><i class="bi bi-5-circle me-2"></i>Configure Prompt Template</h5>
636
- <div id="prompt-config-container">
637
- <div class="mb-4">
638
- <label class="form-label fw-bold">Choose a Template Preset</label>
639
- <div class="row g-2">
640
- <div class="col-md-3 col-6">
641
- <div class="card task-card" onclick="selectPromptPreset('none')" data-preset="none">
642
- <div class="card-body py-2 text-center">
643
- <small class="fw-bold">None</small><br>
644
- <small class="text-muted">Raw text</small>
645
- </div>
646
- </div>
647
- </div>
648
- <div class="col-md-3 col-6">
649
- <div class="card task-card" onclick="selectPromptPreset('alpaca')" data-preset="alpaca">
650
- <div class="card-body py-2 text-center">
651
- <small class="fw-bold">Alpaca</small><br>
652
- <small class="text-muted">Instruction</small>
653
- </div>
654
- </div>
655
- </div>
656
- <div class="col-md-3 col-6">
657
- <div class="card task-card" onclick="selectPromptPreset('chatml')" data-preset="chatml">
658
- <div class="card-body py-2 text-center">
659
- <small class="fw-bold">ChatML</small><br>
660
- <small class="text-muted">Chat format</small>
661
- </div>
662
- </div>
663
- </div>
664
- <div class="col-md-3 col-6">
665
- <div class="card task-card" onclick="selectPromptPreset('llama3')" data-preset="llama3">
666
- <div class="card-body py-2 text-center">
667
- <small class="fw-bold">Llama 3</small><br>
668
- <small class="text-muted">Llama format</small>
669
- </div>
670
- </div>
671
- </div>
672
- <div class="col-md-3 col-6">
673
- <div class="card task-card" onclick="selectPromptPreset('mistral')" data-preset="mistral">
674
- <div class="card-body py-2 text-center">
675
- <small class="fw-bold">Mistral</small><br>
676
- <small class="text-muted">Mistral format</small>
677
- </div>
678
- </div>
679
- </div>
680
- <div class="col-md-3 col-6">
681
- <div class="card task-card" onclick="selectPromptPreset('reasoning')" data-preset="reasoning">
682
- <div class="card-body py-2 text-center">
683
- <small class="fw-bold">Reasoning</small><br>
684
- <small class="text-muted">Chain-of-Thought</small>
685
- </div>
686
- </div>
687
- </div>
688
- <div class="col-md-3 col-6">
689
- <div class="card task-card" onclick="selectPromptPreset('vicuna')" data-preset="vicuna">
690
- <div class="card-body py-2 text-center">
691
- <small class="fw-bold">Vicuna</small><br>
692
- <small class="text-muted">Vicuna format</small>
693
- </div>
694
- </div>
695
- </div>
696
- <div class="col-md-3 col-6">
697
- <div class="card task-card" onclick="selectPromptPreset('phi3')" data-preset="phi3">
698
- <div class="card-body py-2 text-center">
699
- <small class="fw-bold">Phi-3</small><br>
700
- <small class="text-muted">Phi format</small>
701
- </div>
702
- </div>
703
- </div>
704
- </div>
705
  </div>
706
-
707
- <!-- Custom Template Sections (abbreviated for space, same as before) -->
708
- <div class="accordion" id="prompt-accordion">
709
- <!-- System Section -->
710
- <div class="prompt-section">
711
- <div class="prompt-section-header" onclick="togglePromptSection('system')">
712
- <div>
713
- <div class="form-check form-switch d-inline-block me-2">
714
- <input class="form-check-input" type="checkbox" id="system-enabled" onchange="updatePromptPreview()">
715
- </div>
716
- <strong><i class="bi bi-gear me-2"></i>System Message</strong>
717
- </div>
718
- <i class="bi bi-chevron-down"></i>
719
- </div>
720
- <div class="prompt-section-body" id="system-body">
721
- <div class="mb-2">
722
- <label class="form-label">System Template</label>
723
- <textarea class="form-control" id="system-template" rows="2" placeholder="You are a helpful AI assistant." oninput="updatePromptPreview()"></textarea>
724
- </div>
725
- <div class="mb-2">
726
- <label class="form-label">Available Columns (click to insert)</label>
727
- <div id="system-columns"></div>
728
- </div>
729
- <div class="row">
730
- <div class="col-md-6">
731
- <label class="form-label">Prefix</label>
732
- <input type="text" class="form-control" id="system-prefix" placeholder="" oninput="updatePromptPreview()">
733
- </div>
734
- <div class="col-md-6">
735
- <label class="form-label">Suffix</label>
736
- <input type="text" class="form-control" id="system-suffix" value="\n\n" oninput="updatePromptPreview()">
737
- </div>
738
- </div>
739
- </div>
740
- </div>
741
-
742
- <!-- Context Section -->
743
- <div class="prompt-section">
744
- <div class="prompt-section-header" onclick="togglePromptSection('context')">
745
- <div>
746
- <div class="form-check form-switch d-inline-block me-2">
747
- <input class="form-check-input" type="checkbox" id="context-enabled" onchange="updatePromptPreview()">
748
- </div>
749
- <strong><i class="bi bi-file-text me-2"></i>Context/Passages</strong>
750
- </div>
751
- <i class="bi bi-chevron-down"></i>
752
- </div>
753
- <div class="prompt-section-body" id="context-body">
754
- <div class="mb-2">
755
- <label class="form-label">Context Template</label>
756
- <textarea class="form-control" id="context-template" rows="2" placeholder="{context}" oninput="updatePromptPreview()"></textarea>
757
- </div>
758
- <div class="mb-2">
759
- <label class="form-label">Available Columns</label>
760
- <div id="context-columns"></div>
761
- </div>
762
- <div class="row">
763
- <div class="col-md-6">
764
- <label class="form-label">Prefix</label>
765
- <input type="text" class="form-control" id="context-prefix" placeholder="Context:\n" oninput="updatePromptPreview()">
766
- </div>
767
- <div class="col-md-6">
768
- <label class="form-label">Suffix</label>
769
- <input type="text" class="form-control" id="context-suffix" value="\n\n" oninput="updatePromptPreview()">
770
- </div>
771
- </div>
772
- </div>
773
- </div>
774
-
775
- <!-- User Section -->
776
- <div class="prompt-section">
777
- <div class="prompt-section-header" onclick="togglePromptSection('user')">
778
- <div>
779
- <div class="form-check form-switch d-inline-block me-2">
780
- <input class="form-check-input" type="checkbox" id="user-enabled" checked onchange="updatePromptPreview()">
781
- </div>
782
- <strong><i class="bi bi-person me-2"></i>User Message</strong>
783
- </div>
784
- <i class="bi bi-chevron-down"></i>
785
- </div>
786
- <div class="prompt-section-body show" id="user-body">
787
- <div class="mb-2">
788
- <label class="form-label">User Template</label>
789
- <textarea class="form-control" id="user-template" rows="2" placeholder="{question}" oninput="updatePromptPreview()"></textarea>
790
- </div>
791
- <div class="mb-2">
792
- <label class="form-label">Available Columns</label>
793
- <div id="user-columns"></div>
794
- </div>
795
- <div class="row">
796
- <div class="col-md-6">
797
- <label class="form-label">Prefix</label>
798
- <input type="text" class="form-control" id="user-prefix" placeholder="" oninput="updatePromptPreview()">
799
- </div>
800
- <div class="col-md-6">
801
- <label class="form-label">Suffix</label>
802
- <input type="text" class="form-control" id="user-suffix" value="" oninput="updatePromptPreview()">
803
- </div>
804
- </div>
805
- </div>
806
- </div>
807
-
808
- <!-- Reasoning Section -->
809
- <div class="prompt-section">
810
- <div class="prompt-section-header" onclick="togglePromptSection('reasoning')">
811
- <div>
812
- <div class="form-check form-switch d-inline-block me-2">
813
- <input class="form-check-input" type="checkbox" id="reasoning-enabled" onchange="updatePromptPreview()">
814
- </div>
815
- <strong><i class="bi bi-brain me-2"></i>Reasoning/Chain-of-Thought</strong>
816
- </div>
817
- <i class="bi bi-chevron-down"></i>
818
- </div>
819
- <div class="prompt-section-body" id="reasoning-body">
820
- <div class="mb-2">
821
- <label class="form-label">Reasoning Template</label>
822
- <textarea class="form-control" id="reasoning-template" rows="2" placeholder="{reasoning}" oninput="updatePromptPreview()"></textarea>
823
- </div>
824
- <div class="mb-2">
825
- <label class="form-label">Available Columns</label>
826
- <div id="reasoning-columns"></div>
827
- </div>
828
- <div class="row">
829
- <div class="col-md-6">
830
- <label class="form-label">Prefix</label>
831
- <input type="text" class="form-control" id="reasoning-prefix" placeholder="Reasoning:\n" oninput="updatePromptPreview()">
832
- </div>
833
- <div class="col-md-6">
834
- <label class="form-label">Suffix</label>
835
- <input type="text" class="form-control" id="reasoning-suffix" value="\n\n" oninput="updatePromptPreview()">
836
- </div>
837
- </div>
838
- </div>
839
- </div>
840
-
841
- <!-- Assistant Section -->
842
- <div class="prompt-section">
843
- <div class="prompt-section-header" onclick="togglePromptSection('assistant')">
844
- <div>
845
- <div class="form-check form-switch d-inline-block me-2">
846
- <input class="form-check-input" type="checkbox" id="assistant-enabled" checked onchange="updatePromptPreview()">
847
- </div>
848
- <strong><i class="bi bi-robot me-2"></i>Assistant Response (Target)</strong>
849
- </div>
850
- <i class="bi bi-chevron-down"></i>
851
- </div>
852
- <div class="prompt-section-body show" id="assistant-body">
853
- <div class="mb-2">
854
- <label class="form-label">Assistant Template</label>
855
- <textarea class="form-control" id="assistant-template" rows="2" placeholder="{response}" oninput="updatePromptPreview()"></textarea>
856
- </div>
857
- <div class="mb-2">
858
- <label class="form-label">Available Columns</label>
859
- <div id="assistant-columns"></div>
860
- </div>
861
- <div class="row">
862
- <div class="col-md-6">
863
- <label class="form-label">Prefix</label>
864
- <input type="text" class="form-control" id="assistant-prefix" placeholder="" oninput="updatePromptPreview()">
865
- </div>
866
- <div class="col-md-6">
867
- <label class="form-label">Suffix</label>
868
- <input type="text" class="form-control" id="assistant-suffix" value="" oninput="updatePromptPreview()">
869
- </div>
870
- </div>
871
- </div>
872
- </div>
873
  </div>
874
-
875
- <!-- Template Preview -->
876
- <div class="mt-4">
877
- <h6><i class="bi bi-eye me-2"></i>Template Preview</h6>
878
- <div class="template-preview" id="template-preview">
879
- <span class="text-muted">Configure your prompt template above to see a preview...</span>
 
880
  </div>
881
  </div>
 
 
 
 
882
  </div>
883
- </div>
884
 
885
- <!-- Step 6: Training Configuration (abbreviated for space) -->
886
- <div class="mb-4" id="form-step-6" style="display: none;">
887
- <h5 class="mb-3"><i class="bi bi-6-circle me-2"></i>Training Configuration</h5>
888
- <div class="accordion" id="config-accordion">
889
- <div class="accordion-item">
890
- <h2 class="accordion-header">
891
- <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#basic-settings">
892
- <i class="bi bi-sliders me-2"></i>Basic Settings
893
- </button>
894
- </h2>
895
- <div id="basic-settings" class="accordion-collapse collapse show">
896
- <div class="accordion-body">
897
- <div class="row g-3">
898
- <div class="col-md-4">
899
- <label class="form-label">Job Name</label>
900
- <input type="text" class="form-control" id="job_name" name="job_name" placeholder="my-training-job">
901
- </div>
902
- <div class="col-md-4">
903
- <label class="form-label">Epochs</label>
904
- <input type="number" class="form-control" id="epochs" name="epochs" value="3" min="1" max="100">
905
- </div>
906
- <div class="col-md-4">
907
- <label class="form-label">Batch Size</label>
908
- <input type="number" class="form-control" id="batch_size" name="batch_size" value="1" min="1" max="64">
909
- </div>
910
- <div class="col-md-4">
911
- <label class="form-label">Learning Rate</label>
912
- <input type="text" class="form-control" id="learning_rate" name="learning_rate" value="5e-5">
913
- </div>
914
- <div class="col-md-4">
915
- <label class="form-label">Max Sequence Length</label>
916
- <input type="range" class="form-range" id="max_length" name="max_length" min="128" max="4096" value="512" oninput="updateRangeValue(this)">
917
- <div class="d-flex justify-content-between">
918
- <span>128</span>
919
- <span class="range-value" id="max_length-value">512</span>
920
- <span>4096</span>
921
- </div>
922
- </div>
923
- <div class="col-md-4">
924
- <label class="form-label">Warmup Steps</label>
925
- <input type="number" class="form-control" id="warmup_steps" name="warmup_steps" value="100" min="0">
926
- </div>
927
- </div>
928
- </div>
929
- </div>
930
  </div>
931
-
932
- <div class="accordion-item">
933
- <h2 class="accordion-header">
934
- <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#peft-settings">
935
- <i class="bi bi-tuning me-2"></i>PEFT/LoRA Settings
936
- </button>
937
- </h2>
938
- <div id="peft-settings" class="accordion-collapse collapse">
939
- <div class="accordion-body">
940
- <div class="form-check form-switch mb-3">
941
- <input class="form-check-input" type="checkbox" id="use_peft" name="use_peft" checked>
942
- <label class="form-check-label" for="use_peft">Enable PEFT/LoRA (Recommended)</label>
 
 
943
  </div>
944
- <div id="peft-options">
945
- <div class="row g-3">
946
- <div class="col-md-3">
947
- <label class="form-label">Method</label>
948
- <select class="form-select" id="peft_method" name="peft_method">
949
- <option value="lora">LoRA</option>
950
- <option value="adalora">AdaLoRA</option>
951
- <option value="ia3">IA3</option>
952
- <option value="prefix_tuning">Prefix Tuning</option>
953
- </select>
954
- </div>
955
- <div class="col-md-3">
956
- <label class="form-label">LoRA Rank (r)</label>
957
- <input type="number" class="form-control" id="lora_r" name="lora_r" value="16" min="1" max="256">
958
- </div>
959
- <div class="col-md-3">
960
- <label class="form-label">LoRA Alpha</label>
961
- <input type="number" class="form-control" id="lora_alpha" name="lora_alpha" value="32" min="1">
962
- </div>
963
- <div class="col-md-3">
964
- <label class="form-label">LoRA Dropout</label>
965
- <input type="number" class="form-control" id="lora_dropout" name="lora_dropout" value="0.05" min="0" max="1" step="0.01">
966
- </div>
967
- </div>
968
- <div class="mt-3">
969
- <label class="form-label">Target Modules</label>
970
- <div class="row g-2">
971
- <div class="col-md-3">
972
- <div class="form-check">
973
- <input class="form-check-input" type="checkbox" id="mod-q_proj" checked>
974
- <label class="form-check-label" for="mod-q_proj">q_proj</label>
975
- </div>
976
- </div>
977
- <div class="col-md-3">
978
- <div class="form-check">
979
- <input class="form-check-input" type="checkbox" id="mod-v_proj" checked>
980
- <label class="form-check-label" for="mod-v_proj">v_proj</label>
981
- </div>
982
- </div>
983
- <div class="col-md-3">
984
- <div class="form-check">
985
- <input class="form-check-input" type="checkbox" id="mod-k_proj">
986
- <label class="form-check-label" for="mod-k_proj">k_proj</label>
987
- </div>
988
- </div>
989
- <div class="col-md-3">
990
- <div class="form-check">
991
- <input class="form-check-input" type="checkbox" id="mod-o_proj">
992
- <label class="form-check-label" for="mod-o_proj">o_proj</label>
993
- </div>
994
- </div>
995
- </div>
996
- </div>
997
  </div>
998
  </div>
999
  </div>
1000
  </div>
1001
  </div>
1002
- </div>
1003
 
1004
- <!-- Output Settings -->
1005
- <div class="mb-4" id="output-settings">
1006
- <h5 class="mb-3"><i class="bi bi-cloud-upload me-2"></i>Output Settings</h5>
1007
- <div class="row g-3">
1008
- <div class="col-md-6">
1009
- <label class="form-label">Output Model Name</label>
1010
- <input type="text" class="form-control" id="output_name" name="output_name" placeholder="my-finetuned-model">
1011
  </div>
1012
- <div class="col-md-6">
1013
- <div class="form-check form-switch mt-4">
1014
- <input class="form-check-input" type="checkbox" id="push_to_hub" name="push_to_hub">
1015
- <label class="form-check-label">Push to HuggingFace Hub after training</label>
 
 
 
 
 
 
 
 
1016
  </div>
1017
  </div>
1018
  </div>
1019
  </div>
1020
 
1021
  <div class="text-center">
1022
- <button type="submit" class="btn btn-primary btn-lg">
1023
  <i class="bi bi-rocket-takeoff me-2"></i>Start Training
1024
  </button>
1025
  </div>
@@ -1033,7 +752,7 @@
1033
  <div class="card">
1034
  <div class="card-header d-flex justify-content-between align-items-center">
1035
  <span><i class="bi bi-list-task me-2"></i>Training Jobs</span>
1036
- <button class="btn btn-sm btn-outline-light" onclick="refreshJobs()">
1037
  <i class="bi bi-arrow-clockwise me-1"></i>Refresh
1038
  </button>
1039
  </div>
@@ -1130,6 +849,8 @@
1130
  let datasetSplits = [];
1131
  let datasetInfo = null;
1132
  let columnMapping = {};
 
 
1133
 
1134
  // Check authentication on load
1135
  async function checkAuth() {
@@ -1165,7 +886,7 @@
1165
  }
1166
 
1167
  function updateStepIndicator() {
1168
- for (let i = 1; i <= 6; i++) {
1169
  const stepEl = document.getElementById('step-' + i);
1170
  if (!stepEl) continue;
1171
  stepEl.classList.remove('active', 'completed');
@@ -1177,25 +898,20 @@
1177
  else if (i === 3 && selectedModel) stepEl.classList.add('active');
1178
  else if (i === 4 && Object.keys(columnMapping).length > 0) stepEl.classList.add('completed');
1179
  else if (i === 4 && selectedDataset) stepEl.classList.add('active');
1180
- else if (i === 5 && selectedTask) stepEl.classList.add('active');
1181
- else if (i === 6) stepEl.classList.add('active');
1182
  }
1183
  }
1184
 
1185
  async function loadDashboard() {
1186
  try {
1187
- const [statsRes, systemRes, jobsRes] = await Promise.all([
1188
- fetch(API_BASE + '/jobs/statistics'),
1189
  fetch(API_BASE + '/system/resources'),
1190
- fetch(API_BASE + '/jobs/?per_page=5')
1191
  ]);
1192
- const stats = await statsRes.json();
1193
  const system = await systemRes.json();
1194
- const jobs = await jobsRes.json();
1195
- document.getElementById('stat-total-jobs').textContent = stats.total_jobs || 0;
1196
- document.getElementById('stat-active-jobs').textContent = stats.active_jobs || 0;
1197
- document.getElementById('stat-completed-jobs').textContent = stats.completed_jobs || 0;
1198
- document.getElementById('stat-success-rate').textContent = (stats.success_rate || 0).toFixed(1) + '%';
1199
  document.getElementById('cpu-usage').textContent = (system.cpu?.percent || 0).toFixed(1) + '%';
1200
  document.getElementById('cpu-bar').style.width = (system.cpu?.percent || 0) + '%';
1201
  document.getElementById('memory-usage').textContent = (system.memory?.percent || 0).toFixed(1) + '%';
@@ -1205,29 +921,74 @@
1205
  document.getElementById('gpu-status').textContent = system.gpu?.available ? 'Available' : 'Not Available';
1206
  document.getElementById('gpu-status').className = system.gpu?.available ? 'badge bg-success' : 'badge bg-secondary';
1207
  document.getElementById('cache-size').textContent = formatBytes(system.cache?.total_bytes || 0);
1208
- renderRecentJobs(jobs.jobs || []);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1209
  } catch (error) {
1210
  console.error('Error loading dashboard:', error);
1211
  }
1212
  }
1213
 
1214
- function renderRecentJobs(jobs) {
1215
- const container = document.getElementById('recent-jobs-list');
1216
- if (!jobs || jobs.length === 0) {
1217
- container.innerHTML = '<div class="text-center text-muted py-4"><i class="bi bi-inbox fs-1 d-block mb-2"></i>No jobs yet</div>';
1218
  return;
1219
  }
1220
- container.innerHTML = jobs.map(job =>
1221
- '<div class="card mb-2 model-card" style="cursor:pointer" onclick="showJobDetail(\'' + job.job_id + '\')">' +
1222
- '<div class="card-body py-2"><div class="row align-items-center">' +
1223
- '<div class="col-md-4"><strong>' + (job.model_name || job.name || 'Unknown') + '</strong><br>' +
 
 
 
1224
  '<small class="text-muted">' + (job.dataset_name || '') + '</small></div>' +
1225
  '<div class="col-md-3"><span class="job-status status-' + job.status + '">' + job.status + '</span></div>' +
1226
- '<div class="col-md-3"><div class="progress" style="height: 20px;">' +
1227
- '<div class="progress-bar" style="width: ' + (job.progress || 0) + '%">' + (job.progress || 0).toFixed(0) + '%</div></div></div>' +
1228
- '<div class="col-md-2 text-end"><small class="text-muted">' + formatDate(job.created_at) + '</small></div>' +
1229
- '</div></div></div>'
1230
- ).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1231
  }
1232
 
1233
  function selectTask(task) {
@@ -1238,7 +999,6 @@
1238
  document.getElementById('selected-task').value = task;
1239
  updateStepIndicator();
1240
  loadRecommendations();
1241
- document.getElementById('form-step-5').style.display = 'block';
1242
  }
1243
 
1244
  async function loadRecommendations() {
@@ -1301,7 +1061,7 @@
1301
  }).join('');
1302
  }
1303
 
1304
- async function selectModel(modelId) {
1305
  selectedModel = modelId;
1306
  document.getElementById('selected-model').value = modelId;
1307
  document.querySelectorAll('#model-results .task-card').forEach(card => card.classList.remove('selected'));
@@ -1344,81 +1104,84 @@
1344
  document.getElementById('selected-dataset').value = datasetId;
1345
  document.querySelectorAll('#dataset-results .task-card').forEach(card => card.classList.remove('selected'));
1346
  if (event && event.currentTarget) event.currentTarget.classList.add('selected');
1347
- await loadDatasetConfig(datasetId);
1348
  updateStepIndicator();
1349
  }
1350
 
1351
- async function loadDatasetConfig(datasetId) {
1352
- const container = document.getElementById('dataset-config-container');
1353
  document.getElementById('form-step-4').style.display = 'block';
1354
- container.innerHTML = '<div class="text-center"><div class="spinner-border text-primary"></div> Loading dataset structure...</div>';
 
1355
  try {
1356
  const res = await fetch(API_BASE + '/training/dataset/preview/' + encodeURIComponent(datasetId));
1357
  if (!res.ok) throw new Error('Failed to load dataset');
1358
  datasetInfo = await res.json();
1359
  datasetColumns = datasetInfo.columns || [];
1360
  datasetSplits = datasetInfo.splits || [];
1361
- renderDatasetConfig();
1362
- updateColumnVariableTags();
1363
  } catch (error) {
1364
  container.innerHTML = '<div class="error-message">Error loading dataset: ' + error.message + '</div>';
1365
  }
1366
  }
1367
 
1368
- function renderDatasetConfig() {
1369
- const container = document.getElementById('dataset-config-container');
 
 
 
 
 
 
 
 
 
 
1370
  let html = '<div class="row">';
1371
 
1372
- // Splits Section
1373
- html += '<div class="col-md-4"><div class="card h-100"><div class="card-header py-2"><i class="bi bi-diagram-3 me-2"></i>Select Splits</div><div class="card-body">';
1374
- html += '<div class="mb-3"><label class="form-label fw-bold">Training Split</label><div>';
1375
  datasetSplits.forEach(split => {
1376
- const isSelected = split === 'train' || split === datasetSplits[0];
1377
- html += '<span class="split-chip ' + (isSelected ? 'selected' : 'available') + '" onclick="selectTrainSplit(\'' + split + '\')">' + split + '</span> ';
1378
  });
1379
- html += '</div></div>';
1380
- html += '<div class="mb-3"><label class="form-label fw-bold">Validation Split</label>';
1381
- html += '<select class="form-select" id="val-split-select">';
1382
  datasetSplits.forEach(split => {
1383
- html += '<option value="' + split + '">' + split + '</option>';
 
1384
  });
1385
  html += '</select></div></div></div></div>';
1386
 
1387
- // Column Mapping Section
1388
- html += '<div class="col-md-4"><div class="card h-100"><div class="card-header py-2"><i class="bi bi-columns me-2"></i>Map Columns</div><div class="card-body">';
1389
 
1390
- const roles = [
1391
- { key: 'text', label: 'Text/Content', icon: 'file-text' },
1392
- { key: 'input', label: 'Input', icon: 'box-arrow-in-right' },
1393
- { key: 'output', label: 'Output/Response', icon: 'box-arrow-right' },
1394
- { key: 'instruction', label: 'Instruction', icon: 'lightbulb' },
1395
- { key: 'question', label: 'Question', icon: 'question-circle' },
1396
- { key: 'answer', label: 'Answer', icon: 'check-circle' },
1397
- { key: 'context', label: 'Context', icon: 'file-earmark-text' },
1398
- { key: 'reasoning', label: 'Reasoning', icon: 'brain' },
1399
- { key: 'label', label: 'Label', icon: 'tag' }
1400
- ];
1401
 
1402
  roles.forEach(role => {
1403
- const suggested = datasetInfo.suggested_column_mapping?.[role.key + '_column'];
1404
- html += '<div class="mb-2"><label class="form-label small"><i class="bi bi-' + role.icon + ' me-1"></i>' + role.label + '</label>';
1405
- html += '<select class="form-select form-select-sm" id="col-' + role.key + '" onchange="updateColumnMapping(\'' + role.key + '\', this.value)">';
 
1406
  html += '<option value="">-- None --</option>';
1407
  datasetColumns.forEach(col => {
1408
  const colName = col.name || col;
1409
- const selected = suggested === colName || colName.toLowerCase().includes(role.key) ? 'selected' : '';
 
 
1410
  html += '<option value="' + colName + '" ' + selected + '>' + colName + '</option>';
1411
  });
1412
- html += '</select></div>';
1413
  });
1414
 
1415
  html += '</div></div></div>';
1416
 
1417
- // Preview Section
1418
- html += '<div class="col-md-4"><div class="card h-100"><div class="card-header py-2"><i class="bi bi-eye me-2"></i>Data Preview</div>';
1419
- html += '<div class="card-body" style="max-height: 300px; overflow-y: auto;">';
1420
- html += '<table class="table table-sm table-bordered"><thead><tr>';
1421
- datasetColumns.slice(0, 5).forEach(col => {
1422
  const colName = col.name || col;
1423
  html += '<th>' + colName + '</th>';
1424
  });
@@ -1427,7 +1190,7 @@
1427
  const sampleData = datasetInfo.sample_data || [];
1428
  sampleData.slice(0, 5).forEach(row => {
1429
  html += '<tr>';
1430
- datasetColumns.slice(0, 5).forEach(col => {
1431
  const colName = col.name || col;
1432
  let val = row[colName] || '';
1433
  if (typeof val === 'object') val = JSON.stringify(val);
@@ -1438,9 +1201,10 @@
1438
  });
1439
 
1440
  html += '</tbody></table></div></div></div></div>';
 
1441
  container.innerHTML = html;
1442
 
1443
- // Initialize column mapping from suggestions
1444
  columnMapping = {};
1445
  roles.forEach(role => {
1446
  const select = document.getElementById('col-' + role.key);
@@ -1448,18 +1212,10 @@
1448
  columnMapping[role.key] = select.value;
1449
  }
1450
  });
1451
- }
1452
-
1453
- function selectTrainSplit(splitName) {
1454
- document.querySelectorAll('.split-chip').forEach(chip => chip.classList.remove('selected'));
1455
- if (event && event.target) event.target.classList.add('selected');
1456
- }
1457
-
1458
- function escapeHtml(text) {
1459
- if (text === null || text === undefined) return '';
1460
- const div = document.createElement('div');
1461
- div.textContent = String(text);
1462
- return div.innerHTML;
1463
  }
1464
 
1465
  function updateColumnMapping(role, value) {
@@ -1468,75 +1224,14 @@
1468
  } else {
1469
  delete columnMapping[role];
1470
  }
1471
- updateColumnVariableTags();
1472
- updatePromptPreview();
1473
  updateStepIndicator();
1474
  }
1475
 
1476
- function updateColumnVariableTags() {
1477
- const sections = ['system', 'context', 'user', 'reasoning', 'assistant'];
1478
- sections.forEach(section => {
1479
- const container = document.getElementById(section + '-columns');
1480
- if (!container) return;
1481
- let html = '';
1482
- const mappedCols = Object.values(columnMapping);
1483
- mappedCols.forEach(colName => {
1484
- html += '<span class="variable-tag" onclick="insertVariable(\'' + section + '\', \'{' + colName + '}\')">{' + colName + '}</span> ';
1485
- });
1486
- if (datasetColumns) {
1487
- datasetColumns.forEach(col => {
1488
- const colName = col.name || col;
1489
- if (!mappedCols.includes(colName)) {
1490
- html += '<span class="variable-tag" style="background: #999" onclick="insertVariable(\'' + section + '\', \'{' + colName + '}\')">{' + colName + '}</span> ';
1491
- }
1492
- });
1493
- }
1494
- container.innerHTML = html || '<small class="text-muted">No columns mapped</small>';
1495
- });
1496
- }
1497
-
1498
- function insertVariable(section, variable) {
1499
- const textarea = document.getElementById(section + '-template');
1500
- if (!textarea) return;
1501
- const start = textarea.selectionStart;
1502
- const end = textarea.selectionEnd;
1503
- const text = textarea.value;
1504
- textarea.value = text.substring(0, start) + variable + text.substring(end);
1505
- textarea.focus();
1506
- textarea.selectionStart = textarea.selectionEnd = start + variable.length;
1507
- updatePromptPreview();
1508
- }
1509
-
1510
- function togglePromptSection(section) {
1511
- const body = document.getElementById(section + '-body');
1512
- if (body) body.classList.toggle('show');
1513
- }
1514
-
1515
- function selectPromptPreset(preset) {
1516
- document.querySelectorAll('[data-preset]').forEach(el => el.classList.remove('selected'));
1517
- const el = document.querySelector('[data-preset="' + preset + '"]');
1518
- if (el) el.classList.add('selected');
1519
- updatePromptPreview();
1520
- }
1521
-
1522
- function updatePromptPreview() {
1523
- const preview = document.getElementById('template-preview');
1524
- let html = '';
1525
- const sections = ['system', 'context', 'user', 'reasoning', 'assistant'];
1526
- sections.forEach(section => {
1527
- const enabledEl = document.getElementById(section + '-enabled');
1528
- if (!enabledEl || !enabledEl.checked) return;
1529
- const templateEl = document.getElementById(section + '-template');
1530
- const prefixEl = document.getElementById(section + '-prefix');
1531
- const suffixEl = document.getElementById(section + '-suffix');
1532
- const prefix = prefixEl ? prefixEl.value : '';
1533
- const template = templateEl ? templateEl.value : '';
1534
- const suffix = suffixEl ? suffixEl.value : '';
1535
- if (template || prefix || suffix) {
1536
- html += '<span class="' + section + '">' + escapeHtml(prefix) + escapeHtml(template) + escapeHtml(suffix) + '</span>\n';
1537
- }
1538
- });
1539
- preview.innerHTML = html || '<span class="text-muted">Configure your prompt template above...</span>';
1540
  }
1541
 
1542
  function updateRangeValue(input) {
@@ -1557,36 +1252,46 @@
1557
  return new Date(dateStr).toLocaleDateString();
1558
  }
1559
 
1560
- function refreshJobs() { loadJobs(); }
1561
-
1562
  async function loadJobs() {
1563
  try {
1564
- const res = await fetch(API_BASE + '/jobs/');
1565
  const data = await res.json();
1566
  renderJobsList(data.jobs || []);
1567
  } catch (error) {
1568
  console.error('Error loading jobs:', error);
 
1569
  }
1570
  }
1571
 
1572
  function renderJobsList(jobs) {
1573
  const container = document.getElementById('jobs-list');
1574
  if (!jobs || jobs.length === 0) {
1575
- container.innerHTML = '<div class="text-center text-muted py-4"><i class="bi bi-inbox fs-1 d-block mb-2"></i>No jobs yet</div>';
1576
  return;
1577
  }
1578
  container.innerHTML = jobs.map(job =>
1579
  '<div class="card mb-2 model-card" style="cursor:pointer" onclick="showJobDetail(\'' + job.job_id + '\')">' +
1580
  '<div class="card-body"><div class="row align-items-center">' +
1581
- '<div class="col-md-3"><strong>' + (job.name || 'Unknown') + '</strong></div>' +
1582
- '<div class="col-md-3"><span class="job-status status-' + job.status + '">' + job.status + '</span></div>' +
1583
- '<div class="col-md-3"><div class="progress"><div class="progress-bar" style="width: ' + (job.progress || 0) + '%">' +
 
1584
  (job.progress || 0).toFixed(0) + '%</div></div></div>' +
1585
  '<div class="col-md-3 text-end"><small class="text-muted">' + formatDate(job.created_at) + '</small></div>' +
1586
  '</div></div></div>'
1587
  ).join('');
1588
  }
1589
 
 
 
 
 
 
 
 
 
 
 
1590
  async function loadPopularModels() {
1591
  const container = document.getElementById('models-page-results');
1592
  container.innerHTML = '<div class="col-12 text-center"><div class="spinner-border text-primary"></div></div>';
@@ -1684,19 +1389,34 @@
1684
  // Training form submission
1685
  document.getElementById('training-form').addEventListener('submit', async (e) => {
1686
  e.preventDefault();
 
1687
  if (!selectedTask || !selectedModel || !selectedDataset) {
1688
  alert('Please complete all required steps: select a task, model, and dataset');
1689
  return;
1690
  }
1691
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1692
  const formData = {
1693
- name: document.getElementById('job_name').value || 'training-job',
1694
  task_type: selectedTask,
1695
  base_model: selectedModel,
1696
  dataset: {
1697
  name: selectedDataset,
1698
- train_split: 'train',
1699
- validation_split: document.getElementById('val-split-select')?.value || 'validation',
1700
  column_mapping: columnMapping,
1701
  max_length: parseInt(document.getElementById('max_length').value)
1702
  },
@@ -1712,7 +1432,10 @@
1712
  r: parseInt(document.getElementById('lora_r').value),
1713
  alpha: parseInt(document.getElementById('lora_alpha').value),
1714
  dropout: parseFloat(document.getElementById('lora_dropout').value)
1715
- } : null
 
 
 
1716
  };
1717
 
1718
  try {
@@ -1722,17 +1445,29 @@
1722
  body: JSON.stringify(formData)
1723
  });
1724
  const data = await res.json();
1725
- if (data.job_id) {
1726
- alert('Training started! Job ID: ' + data.job_id);
1727
- showSection('jobs');
 
 
1728
  } else {
1729
- alert('Error: ' + (data.detail || JSON.stringify(data)));
1730
  }
1731
  } catch (error) {
1732
  alert('Error: ' + error.message);
 
 
 
1733
  }
1734
  });
1735
 
 
 
 
 
 
 
 
1736
  document.addEventListener('DOMContentLoaded', () => {
1737
  checkAuth();
1738
  const savedToken = localStorage.getItem('hf_token');
 
310
  background: rgba(255,255,255,0.3);
311
  color: white;
312
  }
313
+ .column-role-card {
314
+ border: 2px solid #e0e0e0;
315
+ border-radius: 10px;
316
+ padding: 15px;
317
+ margin-bottom: 10px;
318
+ transition: all 0.2s;
319
+ }
320
+ .column-role-card:hover {
321
+ border-color: #667eea;
322
+ }
323
+ .column-role-card.active {
324
+ border-color: #667eea;
325
+ background: #f8f9ff;
326
+ }
327
  </style>
328
  </head>
329
  <body>
 
424
  <div class="row g-4">
425
  <div class="col-lg-8">
426
  <div class="card">
427
+ <div class="card-header d-flex justify-content-between align-items-center">
428
+ <span><i class="bi bi-clock-history me-2"></i>Current Training</span>
429
+ <button class="btn btn-sm btn-outline-light" onclick="refreshCurrentJob()">
430
+ <i class="bi bi-arrow-clockwise me-1"></i>Refresh
431
+ </button>
432
  </div>
433
  <div class="card-body">
434
+ <div id="current-job-container">
435
  <div class="text-center text-muted py-4">
436
  <i class="bi bi-inbox fs-1 d-block mb-2"></i>
437
+ No active training. Start a new training job!
438
  </div>
439
  </div>
440
  </div>
 
521
  <div class="step-connector" id="connector-4-5"></div>
522
  <div class="step" id="step-5">
523
  <div class="step-number">5</div>
 
 
 
 
 
524
  <span class="step-label">Train</span>
525
  </div>
526
  </div>
 
631
  <div id="dataset-recommendations" class="mt-3"></div>
632
  </div>
633
 
634
+ <!-- Step 4: Column Mapping (SIMPLIFIED) -->
635
  <div class="mb-4" id="form-step-4" style="display: none;">
636
+ <h5 class="mb-3"><i class="bi bi-4-circle me-2"></i>Map Dataset Columns</h5>
637
+ <div id="column-mapping-container">
638
  <div class="text-center text-muted py-4">
639
  <i class="bi bi-database fs-1 d-block mb-2"></i>
640
+ Select a dataset to configure columns
641
  </div>
642
  </div>
643
  </div>
644
 
645
+ <!-- Step 5: Training Configuration -->
646
  <div class="mb-4" id="form-step-5" style="display: none;">
647
+ <h5 class="mb-3"><i class="bi bi-5-circle me-2"></i>Training Configuration</h5>
648
+
649
+ <div class="row g-3 mb-4">
650
+ <div class="col-md-4">
651
+ <label class="form-label">Job Name</label>
652
+ <input type="text" class="form-control" id="job_name" name="job_name" placeholder="my-training-job">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  </div>
654
+ <div class="col-md-4">
655
+ <label class="form-label">Epochs</label>
656
+ <input type="number" class="form-control" id="epochs" name="epochs" value="3" min="1" max="100">
657
+ </div>
658
+ <div class="col-md-4">
659
+ <label class="form-label">Batch Size</label>
660
+ <input type="number" class="form-control" id="batch_size" name="batch_size" value="1" min="1" max="64">
661
+ </div>
662
+ <div class="col-md-4">
663
+ <label class="form-label">Learning Rate</label>
664
+ <input type="text" class="form-control" id="learning_rate" name="learning_rate" value="5e-5">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
665
  </div>
666
+ <div class="col-md-4">
667
+ <label class="form-label">Max Sequence Length</label>
668
+ <input type="range" class="form-range" id="max_length" name="max_length" min="128" max="4096" value="512" oninput="updateRangeValue(this)">
669
+ <div class="d-flex justify-content-between">
670
+ <span>128</span>
671
+ <span class="range-value" id="max_length-value">512</span>
672
+ <span>4096</span>
673
  </div>
674
  </div>
675
+ <div class="col-md-4">
676
+ <label class="form-label">Warmup Steps</label>
677
+ <input type="number" class="form-control" id="warmup_steps" name="warmup_steps" value="100" min="0">
678
+ </div>
679
  </div>
 
680
 
681
+ <!-- PEFT Settings -->
682
+ <div class="card mb-3">
683
+ <div class="card-header py-2">
684
+ <i class="bi bi-tuning me-2"></i>PEFT/LoRA Settings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  </div>
686
+ <div class="card-body">
687
+ <div class="form-check form-switch mb-3">
688
+ <input class="form-check-input" type="checkbox" id="use_peft" name="use_peft" checked>
689
+ <label class="form-check-label" for="use_peft">Enable PEFT/LoRA (Recommended for CPU training)</label>
690
+ </div>
691
+ <div id="peft-options">
692
+ <div class="row g-3">
693
+ <div class="col-md-3">
694
+ <label class="form-label">Method</label>
695
+ <select class="form-select" id="peft_method" name="peft_method">
696
+ <option value="lora">LoRA</option>
697
+ <option value="adalora">AdaLoRA</option>
698
+ <option value="ia3">IA3</option>
699
+ </select>
700
  </div>
701
+ <div class="col-md-3">
702
+ <label class="form-label">LoRA Rank (r)</label>
703
+ <input type="number" class="form-control" id="lora_r" name="lora_r" value="8" min="1" max="256">
704
+ </div>
705
+ <div class="col-md-3">
706
+ <label class="form-label">LoRA Alpha</label>
707
+ <input type="number" class="form-control" id="lora_alpha" name="lora_alpha" value="16" min="1">
708
+ </div>
709
+ <div class="col-md-3">
710
+ <label class="form-label">LoRA Dropout</label>
711
+ <input type="number" class="form-control" id="lora_dropout" name="lora_dropout" value="0.05" min="0" max="1" step="0.01">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  </div>
713
  </div>
714
  </div>
715
  </div>
716
  </div>
 
717
 
718
+ <!-- Output Settings -->
719
+ <div class="card">
720
+ <div class="card-header py-2">
721
+ <i class="bi bi-cloud-upload me-2"></i>Output Settings
 
 
 
722
  </div>
723
+ <div class="card-body">
724
+ <div class="row g-3">
725
+ <div class="col-md-6">
726
+ <label class="form-label">Output Model Name</label>
727
+ <input type="text" class="form-control" id="output_name" name="output_name" placeholder="my-finetuned-model">
728
+ </div>
729
+ <div class="col-md-6">
730
+ <div class="form-check form-switch mt-4">
731
+ <input class="form-check-input" type="checkbox" id="push_to_hub" name="push_to_hub">
732
+ <label class="form-check-label">Push to HuggingFace Hub after training</label>
733
+ </div>
734
+ </div>
735
  </div>
736
  </div>
737
  </div>
738
  </div>
739
 
740
  <div class="text-center">
741
+ <button type="submit" class="btn btn-primary btn-lg" id="start-training-btn">
742
  <i class="bi bi-rocket-takeoff me-2"></i>Start Training
743
  </button>
744
  </div>
 
752
  <div class="card">
753
  <div class="card-header d-flex justify-content-between align-items-center">
754
  <span><i class="bi bi-list-task me-2"></i>Training Jobs</span>
755
+ <button class="btn btn-sm btn-outline-light" onclick="loadJobs()">
756
  <i class="bi bi-arrow-clockwise me-1"></i>Refresh
757
  </button>
758
  </div>
 
849
  let datasetSplits = [];
850
  let datasetInfo = null;
851
  let columnMapping = {};
852
+ let selectedPromptPreset = 'none';
853
+ let currentJobId = null;
854
 
855
  // Check authentication on load
856
  async function checkAuth() {
 
886
  }
887
 
888
  function updateStepIndicator() {
889
+ for (let i = 1; i <= 5; i++) {
890
  const stepEl = document.getElementById('step-' + i);
891
  if (!stepEl) continue;
892
  stepEl.classList.remove('active', 'completed');
 
898
  else if (i === 3 && selectedModel) stepEl.classList.add('active');
899
  else if (i === 4 && Object.keys(columnMapping).length > 0) stepEl.classList.add('completed');
900
  else if (i === 4 && selectedDataset) stepEl.classList.add('active');
901
+ else if (i === 5) stepEl.classList.add('active');
 
902
  }
903
  }
904
 
905
  async function loadDashboard() {
906
  try {
907
+ const [systemRes, jobsRes] = await Promise.all([
 
908
  fetch(API_BASE + '/system/resources'),
909
+ fetch(API_BASE + '/jobs/?limit=10')
910
  ]);
 
911
  const system = await systemRes.json();
912
+ const jobsData = await jobsRes.json();
913
+
914
+ // System stats
 
 
915
  document.getElementById('cpu-usage').textContent = (system.cpu?.percent || 0).toFixed(1) + '%';
916
  document.getElementById('cpu-bar').style.width = (system.cpu?.percent || 0) + '%';
917
  document.getElementById('memory-usage').textContent = (system.memory?.percent || 0).toFixed(1) + '%';
 
921
  document.getElementById('gpu-status').textContent = system.gpu?.available ? 'Available' : 'Not Available';
922
  document.getElementById('gpu-status').className = system.gpu?.available ? 'badge bg-success' : 'badge bg-secondary';
923
  document.getElementById('cache-size').textContent = formatBytes(system.cache?.total_bytes || 0);
924
+
925
+ // Job stats
926
+ const jobs = jobsData.jobs || [];
927
+ const totalJobs = jobs.length;
928
+ const activeJobs = jobs.filter(j => j.status === 'running' || j.status === 'queued').length;
929
+ const completedJobs = jobs.filter(j => j.status === 'completed').length;
930
+ const successRate = totalJobs > 0 ? (completedJobs / totalJobs * 100) : 0;
931
+
932
+ document.getElementById('stat-total-jobs').textContent = totalJobs;
933
+ document.getElementById('stat-active-jobs').textContent = activeJobs;
934
+ document.getElementById('stat-completed-jobs').textContent = completedJobs;
935
+ document.getElementById('stat-success-rate').textContent = successRate.toFixed(1) + '%';
936
+
937
+ // Current job
938
+ renderCurrentJob(jobs.find(j => j.status === 'running' || j.status === 'queued') || jobs[0]);
939
  } catch (error) {
940
  console.error('Error loading dashboard:', error);
941
  }
942
  }
943
 
944
+ function renderCurrentJob(job) {
945
+ const container = document.getElementById('current-job-container');
946
+ if (!job) {
947
+ container.innerHTML = '<div class="text-center text-muted py-4"><i class="bi bi-inbox fs-1 d-block mb-2"></i>No active training. Start a new training job!</div>';
948
  return;
949
  }
950
+
951
+ currentJobId = job.job_id;
952
+ container.innerHTML =
953
+ '<div class="card model-card">' +
954
+ '<div class="card-body">' +
955
+ '<div class="row align-items-center mb-3">' +
956
+ '<div class="col-md-4"><strong>' + (job.name || job.model_name || 'Training Job') + '</strong><br>' +
957
  '<small class="text-muted">' + (job.dataset_name || '') + '</small></div>' +
958
  '<div class="col-md-3"><span class="job-status status-' + job.status + '">' + job.status + '</span></div>' +
959
+ '<div class="col-md-5">' +
960
+ '<div class="progress" style="height: 25px;">' +
961
+ '<div class="progress-bar" style="width: ' + (job.progress || 0) + '%">' +
962
+ (job.progress || 0).toFixed(1) + '%</div></div>' +
963
+ '</div></div>' +
964
+ (job.current_loss ? '<div class="row mt-2"><div class="col-md-6"><small>Loss: ' + (job.current_loss || 0).toFixed(4) + '</small></div>' +
965
+ '<div class="col-md-6"><small>Step: ' + (job.current_step || 0) + '/' + (job.total_steps || '?') + '</small></div></div>' : '') +
966
+ (job.status === 'running' ? '<div class="mt-3"><button class="btn btn-danger btn-sm" onclick="cancelJob(\'' + job.job_id + '\')"><i class="bi bi-stop-circle me-1"></i>Cancel</button></div>' : '') +
967
+ '</div></div>';
968
+ }
969
+
970
+ async function refreshCurrentJob() {
971
+ if (!currentJobId) {
972
+ loadDashboard();
973
+ return;
974
+ }
975
+ try {
976
+ const res = await fetch(API_BASE + '/jobs/' + currentJobId);
977
+ const job = await res.json();
978
+ renderCurrentJob(job);
979
+ } catch (error) {
980
+ console.error('Error refreshing job:', error);
981
+ }
982
+ }
983
+
984
+ async function cancelJob(jobId) {
985
+ if (!confirm('Are you sure you want to cancel this job?')) return;
986
+ try {
987
+ await fetch(API_BASE + '/jobs/' + jobId + '/cancel', { method: 'POST' });
988
+ loadDashboard();
989
+ } catch (error) {
990
+ console.error('Error canceling job:', error);
991
+ }
992
  }
993
 
994
  function selectTask(task) {
 
999
  document.getElementById('selected-task').value = task;
1000
  updateStepIndicator();
1001
  loadRecommendations();
 
1002
  }
1003
 
1004
  async function loadRecommendations() {
 
1061
  }).join('');
1062
  }
1063
 
1064
+ function selectModel(modelId) {
1065
  selectedModel = modelId;
1066
  document.getElementById('selected-model').value = modelId;
1067
  document.querySelectorAll('#model-results .task-card').forEach(card => card.classList.remove('selected'));
 
1104
  document.getElementById('selected-dataset').value = datasetId;
1105
  document.querySelectorAll('#dataset-results .task-card').forEach(card => card.classList.remove('selected'));
1106
  if (event && event.currentTarget) event.currentTarget.classList.add('selected');
1107
+ await loadDatasetColumns(datasetId);
1108
  updateStepIndicator();
1109
  }
1110
 
1111
+ async function loadDatasetColumns(datasetId) {
1112
+ const container = document.getElementById('column-mapping-container');
1113
  document.getElementById('form-step-4').style.display = 'block';
1114
+ container.innerHTML = '<div class="text-center"><div class="spinner-border text-primary"></div> Loading dataset columns...</div>';
1115
+
1116
  try {
1117
  const res = await fetch(API_BASE + '/training/dataset/preview/' + encodeURIComponent(datasetId));
1118
  if (!res.ok) throw new Error('Failed to load dataset');
1119
  datasetInfo = await res.json();
1120
  datasetColumns = datasetInfo.columns || [];
1121
  datasetSplits = datasetInfo.splits || [];
1122
+ renderColumnMapping();
 
1123
  } catch (error) {
1124
  container.innerHTML = '<div class="error-message">Error loading dataset: ' + error.message + '</div>';
1125
  }
1126
  }
1127
 
1128
+ function renderColumnMapping() {
1129
+ const container = document.getElementById('column-mapping-container');
1130
+
1131
+ // Define the roles for columns
1132
+ const roles = [
1133
+ { key: 'system', label: 'System Prompt', desc: 'System instructions (optional)', icon: 'gear' },
1134
+ { key: 'input', label: 'User Input', desc: 'The user query/prompt', icon: 'person' },
1135
+ { key: 'output', label: 'Target Output', desc: 'The expected response', icon: 'robot' },
1136
+ { key: 'context', label: 'Context', desc: 'Additional context (optional)', icon: 'file-text' },
1137
+ { key: 'instruction', label: 'Instruction', desc: 'Task instruction (optional)', icon: 'lightbulb' }
1138
+ ];
1139
+
1140
  let html = '<div class="row">';
1141
 
1142
+ // Splits selection
1143
+ html += '<div class="col-md-3"><div class="card h-100"><div class="card-header py-2">Data Splits</div><div class="card-body">';
1144
+ html += '<div class="mb-3"><label class="form-label small fw-bold">Training Split</label><select class="form-select form-select-sm" id="train-split-select">';
1145
  datasetSplits.forEach(split => {
1146
+ const selected = split === 'train' ? 'selected' : '';
1147
+ html += '<option value="' + split + '" ' + selected + '>' + split + '</option>';
1148
  });
1149
+ html += '</select></div>';
1150
+ html += '<div class="mb-3"><label class="form-label small fw-bold">Validation Split</label><select class="form-select form-select-sm" id="val-split-select">';
1151
+ html += '<option value="">None</option>';
1152
  datasetSplits.forEach(split => {
1153
+ const selected = split === 'validation' || split === 'val' ? 'selected' : '';
1154
+ html += '<option value="' + split + '" ' + selected + '>' + split + '</option>';
1155
  });
1156
  html += '</select></div></div></div></div>';
1157
 
1158
+ // Column mapping
1159
+ html += '<div class="col-md-5"><div class="card h-100"><div class="card-header py-2">Map Columns to Roles</div><div class="card-body">';
1160
 
1161
+ const suggestedMapping = datasetInfo.suggested_column_mapping || {};
 
 
 
 
 
 
 
 
 
 
1162
 
1163
  roles.forEach(role => {
1164
+ const suggestedCol = suggestedMapping[role.key + '_column'] || suggestedMapping[role.key];
1165
+ html += '<div class="row align-items-center mb-2">';
1166
+ html += '<div class="col-5"><label class="form-label small mb-0"><i class="bi bi-' + role.icon + ' me-1"></i>' + role.label + '</label></div>';
1167
+ html += '<div class="col-7"><select class="form-select form-select-sm" id="col-' + role.key + '" onchange="updateColumnMapping(\'' + role.key + '\', this.value)">';
1168
  html += '<option value="">-- None --</option>';
1169
  datasetColumns.forEach(col => {
1170
  const colName = col.name || col;
1171
+ let selected = '';
1172
+ if (suggestedCol === colName) selected = 'selected';
1173
+ else if (!suggestedCol && colName.toLowerCase().includes(role.key)) selected = 'selected';
1174
  html += '<option value="' + colName + '" ' + selected + '>' + colName + '</option>';
1175
  });
1176
+ html += '</select></div></div>';
1177
  });
1178
 
1179
  html += '</div></div></div>';
1180
 
1181
+ // Data preview
1182
+ html += '<div class="col-md-4"><div class="card h-100"><div class="card-header py-2">Data Preview</div>';
1183
+ html += '<div class="card-body" style="max-height: 300px; overflow-y: auto;"><table class="table table-sm table-bordered table-hover"><thead><tr>';
1184
+ datasetColumns.slice(0, 4).forEach(col => {
 
1185
  const colName = col.name || col;
1186
  html += '<th>' + colName + '</th>';
1187
  });
 
1190
  const sampleData = datasetInfo.sample_data || [];
1191
  sampleData.slice(0, 5).forEach(row => {
1192
  html += '<tr>';
1193
+ datasetColumns.slice(0, 4).forEach(col => {
1194
  const colName = col.name || col;
1195
  let val = row[colName] || '';
1196
  if (typeof val === 'object') val = JSON.stringify(val);
 
1201
  });
1202
 
1203
  html += '</tbody></table></div></div></div></div>';
1204
+
1205
  container.innerHTML = html;
1206
 
1207
+ // Initialize column mapping
1208
  columnMapping = {};
1209
  roles.forEach(role => {
1210
  const select = document.getElementById('col-' + role.key);
 
1212
  columnMapping[role.key] = select.value;
1213
  }
1214
  });
1215
+
1216
+ // Show step 5
1217
+ document.getElementById('form-step-5').style.display = 'block';
1218
+ updateStepIndicator();
 
 
 
 
 
 
 
 
1219
  }
1220
 
1221
  function updateColumnMapping(role, value) {
 
1224
  } else {
1225
  delete columnMapping[role];
1226
  }
 
 
1227
  updateStepIndicator();
1228
  }
1229
 
1230
+ function escapeHtml(text) {
1231
+ if (text === null || text === undefined) return '';
1232
+ const div = document.createElement('div');
1233
+ div.textContent = String(text);
1234
+ return div.innerHTML;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1235
  }
1236
 
1237
  function updateRangeValue(input) {
 
1252
  return new Date(dateStr).toLocaleDateString();
1253
  }
1254
 
 
 
1255
  async function loadJobs() {
1256
  try {
1257
+ const res = await fetch(API_BASE + '/jobs/?limit=20');
1258
  const data = await res.json();
1259
  renderJobsList(data.jobs || []);
1260
  } catch (error) {
1261
  console.error('Error loading jobs:', error);
1262
+ document.getElementById('jobs-list').innerHTML = '<div class="error-message">Error loading jobs: ' + error.message + '</div>';
1263
  }
1264
  }
1265
 
1266
  function renderJobsList(jobs) {
1267
  const container = document.getElementById('jobs-list');
1268
  if (!jobs || jobs.length === 0) {
1269
+ container.innerHTML = '<div class="text-center text-muted py-4"><i class="bi bi-inbox fs-1 d-block mb-2"></i>No jobs yet. Start a new training!</div>';
1270
  return;
1271
  }
1272
  container.innerHTML = jobs.map(job =>
1273
  '<div class="card mb-2 model-card" style="cursor:pointer" onclick="showJobDetail(\'' + job.job_id + '\')">' +
1274
  '<div class="card-body"><div class="row align-items-center">' +
1275
+ '<div class="col-md-3"><strong>' + (job.name || 'Training Job') + '</strong><br>' +
1276
+ '<small class="text-muted">' + (job.model_name || '') + '</small></div>' +
1277
+ '<div class="col-md-2"><span class="job-status status-' + job.status + '">' + job.status + '</span></div>' +
1278
+ '<div class="col-md-4"><div class="progress" style="height: 20px;"><div class="progress-bar" style="width: ' + (job.progress || 0) + '%">' +
1279
  (job.progress || 0).toFixed(0) + '%</div></div></div>' +
1280
  '<div class="col-md-3 text-end"><small class="text-muted">' + formatDate(job.created_at) + '</small></div>' +
1281
  '</div></div></div>'
1282
  ).join('');
1283
  }
1284
 
1285
+ async function showJobDetail(jobId) {
1286
+ try {
1287
+ const res = await fetch(API_BASE + '/jobs/' + jobId);
1288
+ const job = await res.json();
1289
+ alert('Job: ' + job.name + '\nStatus: ' + job.status + '\nProgress: ' + (job.progress || 0).toFixed(1) + '%\nModel: ' + job.model_name + '\nDataset: ' + job.dataset_name);
1290
+ } catch (error) {
1291
+ console.error('Error loading job:', error);
1292
+ }
1293
+ }
1294
+
1295
  async function loadPopularModels() {
1296
  const container = document.getElementById('models-page-results');
1297
  container.innerHTML = '<div class="col-12 text-center"><div class="spinner-border text-primary"></div></div>';
 
1389
  // Training form submission
1390
  document.getElementById('training-form').addEventListener('submit', async (e) => {
1391
  e.preventDefault();
1392
+
1393
  if (!selectedTask || !selectedModel || !selectedDataset) {
1394
  alert('Please complete all required steps: select a task, model, and dataset');
1395
  return;
1396
  }
1397
 
1398
+ // Check if at least input and output are mapped
1399
+ if (!columnMapping.input && !columnMapping.text) {
1400
+ alert('Please map at least the Input column from your dataset');
1401
+ return;
1402
+ }
1403
+ if (!columnMapping.output && !columnMapping.text) {
1404
+ alert('Please map at least the Output column from your dataset');
1405
+ return;
1406
+ }
1407
+
1408
+ const btn = document.getElementById('start-training-btn');
1409
+ btn.disabled = true;
1410
+ btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Starting...';
1411
+
1412
  const formData = {
1413
+ name: document.getElementById('job_name').value || 'training-job-' + Date.now(),
1414
  task_type: selectedTask,
1415
  base_model: selectedModel,
1416
  dataset: {
1417
  name: selectedDataset,
1418
+ train_split: document.getElementById('train-split-select')?.value || 'train',
1419
+ validation_split: document.getElementById('val-split-select')?.value || '',
1420
  column_mapping: columnMapping,
1421
  max_length: parseInt(document.getElementById('max_length').value)
1422
  },
 
1432
  r: parseInt(document.getElementById('lora_r').value),
1433
  alpha: parseInt(document.getElementById('lora_alpha').value),
1434
  dropout: parseFloat(document.getElementById('lora_dropout').value)
1435
+ } : null,
1436
+ prompt_template: {
1437
+ preset: selectedPromptPreset
1438
+ }
1439
  };
1440
 
1441
  try {
 
1445
  body: JSON.stringify(formData)
1446
  });
1447
  const data = await res.json();
1448
+
1449
+ if (res.ok && data.job_id) {
1450
+ alert('Training started successfully! Job ID: ' + data.job_id);
1451
+ currentJobId = data.job_id;
1452
+ showSection('dashboard');
1453
  } else {
1454
+ alert('Error starting training: ' + (data.detail || JSON.stringify(data)));
1455
  }
1456
  } catch (error) {
1457
  alert('Error: ' + error.message);
1458
+ } finally {
1459
+ btn.disabled = false;
1460
+ btn.innerHTML = '<i class="bi bi-rocket-takeoff me-2"></i>Start Training';
1461
  }
1462
  });
1463
 
1464
+ // Auto-refresh current job
1465
+ setInterval(() => {
1466
+ if (currentJobId) {
1467
+ refreshCurrentJob();
1468
+ }
1469
+ }, 5000);
1470
+
1471
  document.addEventListener('DOMContentLoaded', () => {
1472
  checkAuth();
1473
  const savedToken = localStorage.getItem('hf_token');