easy_ml 0.1.4 → 0.2.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (239) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -26
  3. data/Rakefile +45 -0
  4. data/app/controllers/easy_ml/application_controller.rb +67 -0
  5. data/app/controllers/easy_ml/columns_controller.rb +38 -0
  6. data/app/controllers/easy_ml/datasets_controller.rb +156 -0
  7. data/app/controllers/easy_ml/datasources_controller.rb +88 -0
  8. data/app/controllers/easy_ml/deploys_controller.rb +20 -0
  9. data/app/controllers/easy_ml/models_controller.rb +151 -0
  10. data/app/controllers/easy_ml/retraining_runs_controller.rb +19 -0
  11. data/app/controllers/easy_ml/settings_controller.rb +59 -0
  12. data/app/frontend/components/AlertProvider.tsx +108 -0
  13. data/app/frontend/components/DatasetPreview.tsx +161 -0
  14. data/app/frontend/components/EmptyState.tsx +28 -0
  15. data/app/frontend/components/ModelCard.tsx +255 -0
  16. data/app/frontend/components/ModelDetails.tsx +334 -0
  17. data/app/frontend/components/ModelForm.tsx +384 -0
  18. data/app/frontend/components/Navigation.tsx +300 -0
  19. data/app/frontend/components/Pagination.tsx +72 -0
  20. data/app/frontend/components/Popover.tsx +55 -0
  21. data/app/frontend/components/PredictionStream.tsx +105 -0
  22. data/app/frontend/components/ScheduleModal.tsx +726 -0
  23. data/app/frontend/components/SearchInput.tsx +23 -0
  24. data/app/frontend/components/SearchableSelect.tsx +132 -0
  25. data/app/frontend/components/dataset/AutosaveIndicator.tsx +39 -0
  26. data/app/frontend/components/dataset/ColumnConfigModal.tsx +431 -0
  27. data/app/frontend/components/dataset/ColumnFilters.tsx +256 -0
  28. data/app/frontend/components/dataset/ColumnList.tsx +101 -0
  29. data/app/frontend/components/dataset/FeatureConfigPopover.tsx +57 -0
  30. data/app/frontend/components/dataset/FeaturePicker.tsx +205 -0
  31. data/app/frontend/components/dataset/PreprocessingConfig.tsx +704 -0
  32. data/app/frontend/components/dataset/SplitConfigurator.tsx +120 -0
  33. data/app/frontend/components/dataset/splitters/DateSplitter.tsx +58 -0
  34. data/app/frontend/components/dataset/splitters/KFoldSplitter.tsx +68 -0
  35. data/app/frontend/components/dataset/splitters/LeavePOutSplitter.tsx +29 -0
  36. data/app/frontend/components/dataset/splitters/PredefinedSplitter.tsx +146 -0
  37. data/app/frontend/components/dataset/splitters/RandomSplitter.tsx +85 -0
  38. data/app/frontend/components/dataset/splitters/StratifiedSplitter.tsx +79 -0
  39. data/app/frontend/components/dataset/splitters/constants.ts +77 -0
  40. data/app/frontend/components/dataset/splitters/types.ts +168 -0
  41. data/app/frontend/components/dataset/splitters/utils.ts +53 -0
  42. data/app/frontend/components/features/CodeEditor.tsx +46 -0
  43. data/app/frontend/components/features/DataPreview.tsx +150 -0
  44. data/app/frontend/components/features/FeatureCard.tsx +88 -0
  45. data/app/frontend/components/features/FeatureForm.tsx +235 -0
  46. data/app/frontend/components/features/FeatureGroupCard.tsx +54 -0
  47. data/app/frontend/components/settings/PluginSettings.tsx +81 -0
  48. data/app/frontend/components/ui/badge.tsx +44 -0
  49. data/app/frontend/components/ui/collapsible.tsx +9 -0
  50. data/app/frontend/components/ui/scroll-area.tsx +46 -0
  51. data/app/frontend/components/ui/separator.tsx +29 -0
  52. data/app/frontend/entrypoints/App.tsx +40 -0
  53. data/app/frontend/entrypoints/Application.tsx +24 -0
  54. data/app/frontend/hooks/useAutosave.ts +61 -0
  55. data/app/frontend/layouts/Layout.tsx +38 -0
  56. data/app/frontend/lib/utils.ts +6 -0
  57. data/app/frontend/mockData.ts +272 -0
  58. data/app/frontend/pages/DatasetDetailsPage.tsx +103 -0
  59. data/app/frontend/pages/DatasetsPage.tsx +261 -0
  60. data/app/frontend/pages/DatasourceFormPage.tsx +147 -0
  61. data/app/frontend/pages/DatasourcesPage.tsx +261 -0
  62. data/app/frontend/pages/EditModelPage.tsx +45 -0
  63. data/app/frontend/pages/EditTransformationPage.tsx +56 -0
  64. data/app/frontend/pages/ModelsPage.tsx +115 -0
  65. data/app/frontend/pages/NewDatasetPage.tsx +366 -0
  66. data/app/frontend/pages/NewModelPage.tsx +45 -0
  67. data/app/frontend/pages/NewTransformationPage.tsx +43 -0
  68. data/app/frontend/pages/SettingsPage.tsx +272 -0
  69. data/app/frontend/pages/ShowModelPage.tsx +30 -0
  70. data/app/frontend/pages/TransformationsPage.tsx +95 -0
  71. data/app/frontend/styles/application.css +100 -0
  72. data/app/frontend/types/dataset.ts +146 -0
  73. data/app/frontend/types/datasource.ts +33 -0
  74. data/app/frontend/types/preprocessing.ts +1 -0
  75. data/app/frontend/types.ts +113 -0
  76. data/app/helpers/easy_ml/application_helper.rb +10 -0
  77. data/app/jobs/easy_ml/application_job.rb +21 -0
  78. data/app/jobs/easy_ml/batch_job.rb +46 -0
  79. data/app/jobs/easy_ml/compute_feature_job.rb +19 -0
  80. data/app/jobs/easy_ml/deploy_job.rb +13 -0
  81. data/app/jobs/easy_ml/finalize_feature_job.rb +15 -0
  82. data/app/jobs/easy_ml/refresh_dataset_job.rb +32 -0
  83. data/app/jobs/easy_ml/schedule_retraining_job.rb +11 -0
  84. data/app/jobs/easy_ml/sync_datasource_job.rb +17 -0
  85. data/app/jobs/easy_ml/training_job.rb +62 -0
  86. data/app/models/easy_ml/adapters/base_adapter.rb +45 -0
  87. data/app/models/easy_ml/adapters/polars_adapter.rb +77 -0
  88. data/app/models/easy_ml/cleaner.rb +82 -0
  89. data/app/models/easy_ml/column.rb +124 -0
  90. data/app/models/easy_ml/column_history.rb +30 -0
  91. data/app/models/easy_ml/column_list.rb +122 -0
  92. data/app/models/easy_ml/concerns/configurable.rb +61 -0
  93. data/app/models/easy_ml/concerns/versionable.rb +19 -0
  94. data/app/models/easy_ml/dataset.rb +767 -0
  95. data/app/models/easy_ml/dataset_history.rb +56 -0
  96. data/app/models/easy_ml/datasource.rb +182 -0
  97. data/app/models/easy_ml/datasource_history.rb +24 -0
  98. data/app/models/easy_ml/datasources/base_datasource.rb +54 -0
  99. data/app/models/easy_ml/datasources/file_datasource.rb +58 -0
  100. data/app/models/easy_ml/datasources/polars_datasource.rb +89 -0
  101. data/app/models/easy_ml/datasources/s3_datasource.rb +97 -0
  102. data/app/models/easy_ml/deploy.rb +114 -0
  103. data/app/models/easy_ml/event.rb +79 -0
  104. data/app/models/easy_ml/feature.rb +437 -0
  105. data/app/models/easy_ml/feature_history.rb +38 -0
  106. data/app/models/easy_ml/model.rb +575 -41
  107. data/app/models/easy_ml/model_file.rb +133 -0
  108. data/app/models/easy_ml/model_file_history.rb +24 -0
  109. data/app/models/easy_ml/model_history.rb +51 -0
  110. data/app/models/easy_ml/models/base_model.rb +58 -0
  111. data/app/models/easy_ml/models/hyperparameters/base.rb +99 -0
  112. data/app/models/easy_ml/models/hyperparameters/xgboost/dart.rb +82 -0
  113. data/app/models/easy_ml/models/hyperparameters/xgboost/gblinear.rb +82 -0
  114. data/app/models/easy_ml/models/hyperparameters/xgboost/gbtree.rb +97 -0
  115. data/app/models/easy_ml/models/hyperparameters/xgboost.rb +71 -0
  116. data/app/models/easy_ml/models/xgboost/evals_callback.rb +138 -0
  117. data/app/models/easy_ml/models/xgboost/progress_callback.rb +39 -0
  118. data/app/models/easy_ml/models/xgboost.rb +544 -5
  119. data/app/models/easy_ml/prediction.rb +44 -0
  120. data/app/models/easy_ml/retraining_job.rb +278 -0
  121. data/app/models/easy_ml/retraining_run.rb +184 -0
  122. data/app/models/easy_ml/settings.rb +37 -0
  123. data/app/models/easy_ml/splitter.rb +90 -0
  124. data/app/models/easy_ml/splitters/base_splitter.rb +28 -0
  125. data/app/models/easy_ml/splitters/date_splitter.rb +91 -0
  126. data/app/models/easy_ml/splitters/predefined_splitter.rb +74 -0
  127. data/app/models/easy_ml/splitters/random_splitter.rb +82 -0
  128. data/app/models/easy_ml/tuner_job.rb +56 -0
  129. data/app/models/easy_ml/tuner_run.rb +31 -0
  130. data/app/models/splitter_history.rb +6 -0
  131. data/app/serializers/easy_ml/column_serializer.rb +27 -0
  132. data/app/serializers/easy_ml/dataset_serializer.rb +73 -0
  133. data/app/serializers/easy_ml/datasource_serializer.rb +64 -0
  134. data/app/serializers/easy_ml/feature_serializer.rb +27 -0
  135. data/app/serializers/easy_ml/model_serializer.rb +90 -0
  136. data/app/serializers/easy_ml/retraining_job_serializer.rb +22 -0
  137. data/app/serializers/easy_ml/retraining_run_serializer.rb +39 -0
  138. data/app/serializers/easy_ml/settings_serializer.rb +9 -0
  139. data/app/views/layouts/easy_ml/application.html.erb +15 -0
  140. data/config/initializers/resque.rb +3 -0
  141. data/config/resque-pool.yml +6 -0
  142. data/config/routes.rb +39 -0
  143. data/config/spring.rb +1 -0
  144. data/config/vite.json +15 -0
  145. data/lib/easy_ml/configuration.rb +64 -0
  146. data/lib/easy_ml/core/evaluators/base_evaluator.rb +53 -0
  147. data/lib/easy_ml/core/evaluators/classification_evaluators.rb +126 -0
  148. data/lib/easy_ml/core/evaluators/regression_evaluators.rb +66 -0
  149. data/lib/easy_ml/core/model_evaluator.rb +161 -89
  150. data/lib/easy_ml/core/tuner/adapters/base_adapter.rb +28 -18
  151. data/lib/easy_ml/core/tuner/adapters/xgboost_adapter.rb +4 -25
  152. data/lib/easy_ml/core/tuner.rb +123 -62
  153. data/lib/easy_ml/core.rb +0 -3
  154. data/lib/easy_ml/core_ext/hash.rb +24 -0
  155. data/lib/easy_ml/core_ext/pathname.rb +11 -5
  156. data/lib/easy_ml/data/date_converter.rb +90 -0
  157. data/lib/easy_ml/data/filter_extensions.rb +31 -0
  158. data/lib/easy_ml/data/polars_column.rb +126 -0
  159. data/lib/easy_ml/data/polars_reader.rb +297 -0
  160. data/lib/easy_ml/data/preprocessor.rb +280 -142
  161. data/lib/easy_ml/data/simple_imputer.rb +255 -0
  162. data/lib/easy_ml/data/splits/file_split.rb +252 -0
  163. data/lib/easy_ml/data/splits/in_memory_split.rb +54 -0
  164. data/lib/easy_ml/data/splits/split.rb +95 -0
  165. data/lib/easy_ml/data/splits.rb +9 -0
  166. data/lib/easy_ml/data/statistics_learner.rb +93 -0
  167. data/lib/easy_ml/data/synced_directory.rb +341 -0
  168. data/lib/easy_ml/data.rb +6 -2
  169. data/lib/easy_ml/engine.rb +105 -6
  170. data/lib/easy_ml/feature_store.rb +227 -0
  171. data/lib/easy_ml/features.rb +61 -0
  172. data/lib/easy_ml/initializers/inflections.rb +17 -3
  173. data/lib/easy_ml/logging.rb +2 -2
  174. data/lib/easy_ml/predict.rb +74 -0
  175. data/lib/easy_ml/railtie/generators/migration/migration_generator.rb +192 -36
  176. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_column_histories.rb.tt +9 -0
  177. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_columns.rb.tt +25 -0
  178. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_dataset_histories.rb.tt +9 -0
  179. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasets.rb.tt +31 -0
  180. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasource_histories.rb.tt +9 -0
  181. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasources.rb.tt +16 -0
  182. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_deploys.rb.tt +24 -0
  183. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_events.rb.tt +20 -0
  184. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_feature_histories.rb.tt +14 -0
  185. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_features.rb.tt +32 -0
  186. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_file_histories.rb.tt +9 -0
  187. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_files.rb.tt +17 -0
  188. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_histories.rb.tt +9 -0
  189. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_models.rb.tt +20 -9
  190. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_predictions.rb.tt +17 -0
  191. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_retraining_jobs.rb.tt +77 -0
  192. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_settings.rb.tt +9 -0
  193. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitter_histories.rb.tt +9 -0
  194. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitters.rb.tt +15 -0
  195. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_tuner_jobs.rb.tt +40 -0
  196. data/lib/easy_ml/support/est.rb +5 -1
  197. data/lib/easy_ml/support/file_rotate.rb +79 -15
  198. data/lib/easy_ml/support/file_support.rb +9 -0
  199. data/lib/easy_ml/support/local_file.rb +24 -0
  200. data/lib/easy_ml/support/lockable.rb +62 -0
  201. data/lib/easy_ml/support/synced_file.rb +103 -0
  202. data/lib/easy_ml/support/utc.rb +5 -1
  203. data/lib/easy_ml/support.rb +6 -3
  204. data/lib/easy_ml/version.rb +4 -1
  205. data/lib/easy_ml.rb +7 -2
  206. metadata +355 -72
  207. data/app/models/easy_ml/models.rb +0 -5
  208. data/lib/easy_ml/core/model.rb +0 -30
  209. data/lib/easy_ml/core/model_core.rb +0 -181
  210. data/lib/easy_ml/core/models/hyperparameters/base.rb +0 -34
  211. data/lib/easy_ml/core/models/hyperparameters/xgboost.rb +0 -19
  212. data/lib/easy_ml/core/models/xgboost.rb +0 -10
  213. data/lib/easy_ml/core/models/xgboost_core.rb +0 -220
  214. data/lib/easy_ml/core/models.rb +0 -10
  215. data/lib/easy_ml/core/uploaders/model_uploader.rb +0 -24
  216. data/lib/easy_ml/core/uploaders.rb +0 -7
  217. data/lib/easy_ml/data/dataloader.rb +0 -6
  218. data/lib/easy_ml/data/dataset/data/preprocessor/statistics.json +0 -31
  219. data/lib/easy_ml/data/dataset/data/sample_info.json +0 -1
  220. data/lib/easy_ml/data/dataset/dataset/files/sample_info.json +0 -1
  221. data/lib/easy_ml/data/dataset/splits/file_split.rb +0 -140
  222. data/lib/easy_ml/data/dataset/splits/in_memory_split.rb +0 -49
  223. data/lib/easy_ml/data/dataset/splits/split.rb +0 -98
  224. data/lib/easy_ml/data/dataset/splits.rb +0 -11
  225. data/lib/easy_ml/data/dataset/splitters/date_splitter.rb +0 -43
  226. data/lib/easy_ml/data/dataset/splitters.rb +0 -9
  227. data/lib/easy_ml/data/dataset.rb +0 -430
  228. data/lib/easy_ml/data/datasource/datasource_factory.rb +0 -60
  229. data/lib/easy_ml/data/datasource/file_datasource.rb +0 -40
  230. data/lib/easy_ml/data/datasource/merged_datasource.rb +0 -64
  231. data/lib/easy_ml/data/datasource/polars_datasource.rb +0 -41
  232. data/lib/easy_ml/data/datasource/s3_datasource.rb +0 -89
  233. data/lib/easy_ml/data/datasource.rb +0 -33
  234. data/lib/easy_ml/data/preprocessor/preprocessor.rb +0 -205
  235. data/lib/easy_ml/data/preprocessor/simple_imputer.rb +0 -402
  236. data/lib/easy_ml/deployment.rb +0 -5
  237. data/lib/easy_ml/support/synced_directory.rb +0 -134
  238. data/lib/easy_ml/transforms.rb +0 -29
  239. /data/{lib/easy_ml/core → app/models/easy_ml}/models/hyperparameters.rb +0 -0
@@ -0,0 +1,366 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Database, AlertCircle, ChevronDown, ChevronUp, Loader2 } from 'lucide-react';
3
+ import { SearchableSelect } from '../components/SearchableSelect';
4
+ import { useInertiaForm } from 'use-inertia-form';
5
+ import { usePage, router } from '@inertiajs/react';
6
+ import type { Datasource } from '../types/datasource';
7
+ import type {
8
+ NewDatasetForm,
9
+ NewDatasetFormProps,
10
+ SplitterType,
11
+ SplitConfig,
12
+ DateSplitConfig,
13
+ RandomSplitConfig,
14
+ PredefinedSplitConfig,
15
+ StratifiedSplitConfig,
16
+ KFoldConfig,
17
+ LeavePOutConfig,
18
+ ColumnConfig
19
+ } from '../components/dataset/splitters/types';
20
+ import { SplitConfigurator } from '../components/dataset/SplitConfigurator';
21
+ import { validateSplitterConfig } from '../components/dataset/splitters/types';
22
+
23
+ export default function NewDatasetPage({ constants, datasources }: NewDatasetFormProps) {
24
+ const [step, setStep] = useState(1);
25
+ const [showError, setShowError] = useState<number | null>(null);
26
+ const [selectedSplitterType, setSelectedSplitterType] = useState<SplitterType>('random');
27
+ const { rootPath } = usePage().props;
28
+
29
+ const getDefaultConfig = (type: SplitterType): SplitConfig => {
30
+ switch (type) {
31
+ case 'date':
32
+ const dateConfig: DateSplitConfig = {
33
+ date_column: '',
34
+ months_test: 2,
35
+ months_valid: 2
36
+ };
37
+ return dateConfig;
38
+ case 'random':
39
+ const randomConfig: RandomSplitConfig = {};
40
+ return randomConfig;
41
+ case 'predefined':
42
+ const predefinedConfig: PredefinedSplitConfig = {
43
+ train_files: [],
44
+ test_files: [],
45
+ valid_files: []
46
+ };
47
+ return predefinedConfig;
48
+ case 'stratified':
49
+ const stratifiedConfig: StratifiedSplitConfig = {
50
+ stratify_column: '',
51
+ train_ratio: 0.6,
52
+ test_ratio: 0.2,
53
+ valid_ratio: 0.2
54
+ };
55
+ return stratifiedConfig;
56
+ case 'stratified_kfold':
57
+ case 'group_kfold':
58
+ const kfoldConfig: KFoldConfig = {
59
+ target_column: '',
60
+ group_column: '',
61
+ n_splits: 5
62
+ };
63
+ return kfoldConfig;
64
+ case 'leave_p_out':
65
+ const lpoConfig: LeavePOutConfig = {
66
+ p: 1,
67
+ shuffle: true,
68
+ random_state: 42
69
+ };
70
+ return lpoConfig;
71
+ default:
72
+ const defaultConfig: RandomSplitConfig = {};
73
+ return defaultConfig;
74
+ }
75
+ };
76
+
77
+ const form = useInertiaForm<NewDatasetForm>({
78
+ dataset: {
79
+ name: '',
80
+ datasource_id: '',
81
+ splitter_attributes: {
82
+ splitter_type: selectedSplitterType,
83
+ ...getDefaultConfig(selectedSplitterType)
84
+ }
85
+ }
86
+ });
87
+
88
+ // Update form when splitter type changes
89
+ useEffect(() => {
90
+ form.setData('dataset.splitter_attributes', {
91
+ splitter_type: selectedSplitterType,
92
+ ...getDefaultConfig(selectedSplitterType)
93
+ });
94
+ }, [selectedSplitterType]);
95
+
96
+ const handleSplitterChange = (type: SplitterType, attributes: SplitConfig) => {
97
+ setSelectedSplitterType(type);
98
+ form.setData('dataset.splitter_attributes', {
99
+ splitter_type: type,
100
+ ...attributes
101
+ });
102
+ };
103
+ console.log(form.dataset?.splitter_attributes)
104
+
105
+ const { data: formData, setData, post } = form;
106
+
107
+ const selectedDatasource = formData.dataset.datasource_id
108
+ ? datasources.find(d => d.id === Number(formData.dataset.datasource_id))
109
+ : null;
110
+
111
+ const availableCols: ColumnConfig[] = (selectedDatasource?.columns || []).map(col => ({
112
+ name: col,
113
+ type: (selectedDatasource?.schema || {})[col] || ''
114
+ }));
115
+
116
+ const isDatasourceReady = selectedDatasource &&
117
+ !selectedDatasource.is_syncing &&
118
+ !selectedDatasource.sync_error;
119
+
120
+ const canProceedToStep2 = formData.dataset.name && isDatasourceReady;
121
+
122
+ const handleDatasourceSelect = () => {
123
+ if (!canProceedToStep2) return;
124
+ setStep(2);
125
+ };
126
+
127
+ const handleSubmit = (e: React.FormEvent) => {
128
+ e.preventDefault();
129
+
130
+ post(`${rootPath}/datasets`, {
131
+ onSuccess: () => {
132
+ router.visit(`${rootPath}/datasets`);
133
+ },
134
+ onError: (errors) => {
135
+ console.error('Failed to create dataset:', errors);
136
+ }
137
+ });
138
+ };
139
+
140
+ const getValidationError = (): string | undefined => {
141
+ if (!formData.dataset.name) {
142
+ return "Please enter a dataset name";
143
+ }
144
+ if (!formData.dataset.datasource_id) {
145
+ return "Please select a datasource";
146
+ }
147
+
148
+ const splitterValidation = validateSplitterConfig(
149
+ formData.dataset.splitter_attributes.splitter_type,
150
+ formData.dataset.splitter_attributes
151
+ );
152
+
153
+ return splitterValidation.error;
154
+ };
155
+
156
+ const isFormValid = () => {
157
+ return !getValidationError();
158
+ };
159
+
160
+ return (
161
+ <div className="max-w-2xl mx-auto p-8">
162
+ <div className="bg-white rounded-lg shadow-lg p-6">
163
+ <h2 className="text-xl font-semibold text-gray-900 mb-6">
164
+ Create New Dataset
165
+ </h2>
166
+
167
+ <div className="mb-8">
168
+ <div className="flex items-center">
169
+ <div
170
+ className={`flex items-center justify-center w-8 h-8 rounded-full ${
171
+ step >= 1 ? 'bg-blue-600' : 'bg-gray-200'
172
+ } text-white font-medium text-sm`}
173
+ >
174
+ 1
175
+ </div>
176
+ <div
177
+ className={`flex-1 h-0.5 mx-2 ${
178
+ step >= 2 ? 'bg-blue-600' : 'bg-gray-200'
179
+ }`}
180
+ />
181
+ <div
182
+ className={`flex items-center justify-center w-8 h-8 rounded-full ${
183
+ step >= 2 ? 'bg-blue-600' : 'bg-gray-200'
184
+ } text-white font-medium text-sm`}
185
+ >
186
+ 2
187
+ </div>
188
+ </div>
189
+ <div className="flex justify-between mt-2">
190
+ <span className="text-sm font-medium text-gray-600">
191
+ Basic Info
192
+ </span>
193
+ <span className="text-sm font-medium text-gray-600 mr-4">
194
+ Configure Split
195
+ </span>
196
+ </div>
197
+ </div>
198
+
199
+ {step === 1 ? (
200
+ <div className="space-y-6">
201
+ <div>
202
+ <label
203
+ htmlFor="name"
204
+ className="block text-sm font-medium text-gray-700"
205
+ >
206
+ Dataset Name
207
+ </label>
208
+ <input
209
+ type="text"
210
+ id="name"
211
+ value={formData.dataset.name}
212
+ onChange={(e) => setData('dataset.name', e.target.value)}
213
+ className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-2 px-4 shadow-sm border-gray-300 border"
214
+ required
215
+ />
216
+ </div>
217
+
218
+ <div>
219
+ <label
220
+ htmlFor="description"
221
+ className="block text-sm font-medium text-gray-700"
222
+ >
223
+ Description
224
+ </label>
225
+ <textarea
226
+ id="description"
227
+ value={formData.dataset.description}
228
+ onChange={(e) => setData('dataset.description', e.target.value)}
229
+ rows={3}
230
+ className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-2 px-4 shadow-sm border-gray-300 border"
231
+ />
232
+ </div>
233
+
234
+ <div>
235
+ <label
236
+ htmlFor="datasource"
237
+ className="block text-sm font-medium text-gray-700 mb-1"
238
+ >
239
+ Datasource
240
+ </label>
241
+ <SearchableSelect
242
+ value={formData.dataset.datasource_id}
243
+ onChange={(value) => setData('dataset.datasource_id', value)}
244
+ options={datasources.map(datasource => ({
245
+ value: datasource.id,
246
+ label: datasource.name
247
+ }))}
248
+ placeholder="Select a datasource..."
249
+ />
250
+ </div>
251
+
252
+ {selectedDatasource && (
253
+ <div className={`rounded-lg p-4 ${
254
+ selectedDatasource.sync_error
255
+ ? 'bg-red-50'
256
+ : selectedDatasource.is_syncing
257
+ ? 'bg-blue-50'
258
+ : 'bg-green-50'
259
+ }`}>
260
+ <div className="flex items-start gap-2">
261
+ {selectedDatasource.is_syncing ? (
262
+ <>
263
+ <Loader2 className="w-5 h-5 text-blue-500 animate-spin" />
264
+ <div>
265
+ <h4 className="text-sm font-medium text-blue-800">
266
+ Datasource is syncing
267
+ </h4>
268
+ <p className="mt-1 text-sm text-blue-700">
269
+ Please wait while we sync your data. This may take a few minutes.
270
+ </p>
271
+ </div>
272
+ </>
273
+ ) : selectedDatasource.sync_error ? (
274
+ <>
275
+ <AlertCircle className="w-5 h-5 text-red-500" />
276
+ <div>
277
+ <h4 className="text-sm font-medium text-red-800">
278
+ Sync failed
279
+ </h4>
280
+ <p className="mt-1 text-sm text-red-700">
281
+ There was an error syncing your datasource.
282
+ </p>
283
+ <button
284
+ onClick={() => setShowError(selectedDatasource.id)}
285
+ className="mt-2 flex items-center gap-1 text-sm text-red-700 hover:text-red-800"
286
+ >
287
+ View error details
288
+ {showError === selectedDatasource.id ? (
289
+ <ChevronUp className="w-4 h-4" />
290
+ ) : (
291
+ <ChevronDown className="w-4 h-4" />
292
+ )}
293
+ </button>
294
+ {showError === selectedDatasource.id && (
295
+ <pre className="mt-2 p-2 text-xs text-red-700 bg-red-100 rounded-md whitespace-pre-wrap break-words font-mono max-h-32 overflow-y-auto">
296
+ {selectedDatasource.stacktrace}
297
+ </pre>
298
+ )}
299
+ </div>
300
+ </>
301
+ ) : (
302
+ <>
303
+ <Database className="w-5 h-5 text-green-500" />
304
+ <div>
305
+ <h4 className="text-sm font-medium text-green-800">
306
+ Datasource ready
307
+ </h4>
308
+ <p className="mt-1 text-sm text-green-700">
309
+ Your datasource is synced and ready to use.
310
+ </p>
311
+ </div>
312
+ </>
313
+ )}
314
+ </div>
315
+ </div>
316
+ )}
317
+
318
+ <div className="flex justify-end">
319
+ <button
320
+ type="button"
321
+ onClick={handleDatasourceSelect}
322
+ disabled={!canProceedToStep2}
323
+ className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-gray-300 disabled:cursor-not-allowed"
324
+ >
325
+ Next
326
+ </button>
327
+ </div>
328
+ </div>
329
+ ) : (
330
+ <form onSubmit={handleSubmit} className="space-y-6">
331
+ <SplitConfigurator
332
+ type={selectedSplitterType}
333
+ splitter_attributes={form.data.dataset.splitter_attributes}
334
+ columns={availableCols}
335
+ available_files={selectedDatasource.available_files}
336
+ onChange={handleSplitterChange}
337
+ />
338
+
339
+ {getValidationError() && (
340
+ <div className="mt-2 text-sm text-red-600">
341
+ {getValidationError()}
342
+ </div>
343
+ )}
344
+
345
+ <div className="flex justify-between">
346
+ <button
347
+ type="button"
348
+ onClick={() => setStep(1)}
349
+ className="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-900"
350
+ >
351
+ Back
352
+ </button>
353
+ <button
354
+ type="submit"
355
+ disabled={!isFormValid()}
356
+ className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-gray-300 disabled:cursor-not-allowed"
357
+ >
358
+ Create Dataset
359
+ </button>
360
+ </div>
361
+ </form>
362
+ )}
363
+ </div>
364
+ </div>
365
+ );
366
+ }
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { ModelForm } from '../components/ModelForm';
3
+
4
+ interface Props {
5
+ datasets: Array<{
6
+ id: number;
7
+ name: string;
8
+ rowCount: number;
9
+ }>;
10
+ constants: {
11
+ tasks: Array<{
12
+ value: string;
13
+ label: string;
14
+ description: string;
15
+ }>;
16
+ objectives: Record<string, Array<{
17
+ value: string;
18
+ label: string;
19
+ description: string;
20
+ }>>;
21
+ metrics: Record<string, Array<{
22
+ value: string;
23
+ label: string;
24
+ direction: string;
25
+ }>>;
26
+ };
27
+ errors: Record<string, string[]>;
28
+ }
29
+
30
+ export default function NewModelPage({ datasets, constants, errors }: Props) {
31
+ return (
32
+ <div className="max-w-2xl mx-auto p-8">
33
+ <div className="bg-white rounded-lg shadow-lg p-6">
34
+ <h2 className="text-xl font-semibold text-gray-900 mb-6">
35
+ Create New Model
36
+ </h2>
37
+ <ModelForm
38
+ datasets={datasets}
39
+ constants={constants}
40
+ errors={errors}
41
+ />
42
+ </div>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,43 @@
1
+ import React, { useState } from 'react';
2
+ // import { useNavigate } from 'react-router-dom';
3
+ import { Code2 } from 'lucide-react';
4
+ import { mockDatasets, mockFeatureGroups } from '../mockData';
5
+ import { FeatureForm } from '../components/features/FeatureForm';
6
+
7
+ export default function NewFeaturePage() {
8
+ const navigate = useNavigate();
9
+ const [formData, setFormData] = useState({
10
+ name: '',
11
+ description: '',
12
+ groupId: '',
13
+ testDatasetId: '',
14
+ inputColumns: [] as string[],
15
+ outputColumns: [] as string[],
16
+ code: ''
17
+ });
18
+
19
+ const handleSubmit = (data: typeof formData) => {
20
+ console.log('Creating new feature:', data);
21
+ navigate('/features');
22
+ };
23
+
24
+ return (
25
+ <div className="max-w-4xl mx-auto p-8">
26
+ <div className="bg-white rounded-lg shadow-lg">
27
+ <div className="px-6 py-4 border-b border-gray-200">
28
+ <div className="flex items-center gap-3">
29
+ <Code2 className="w-6 h-6 text-blue-600" />
30
+ <h2 className="text-xl font-semibold text-gray-900">New Feature</h2>
31
+ </div>
32
+ </div>
33
+
34
+ <FeatureForm
35
+ datasets={mockDatasets}
36
+ groups={mockFeatureGroups}
37
+ onSubmit={handleSubmit}
38
+ onCancel={() => navigate('/features')}
39
+ />
40
+ </div>
41
+ </div>
42
+ );
43
+ }