easy_ml 0.1.4 → 0.2.0.pre.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }