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.
- checksums.yaml +4 -4
- data/README.md +234 -26
- data/Rakefile +45 -0
- data/app/controllers/easy_ml/application_controller.rb +67 -0
- data/app/controllers/easy_ml/columns_controller.rb +38 -0
- data/app/controllers/easy_ml/datasets_controller.rb +156 -0
- data/app/controllers/easy_ml/datasources_controller.rb +88 -0
- data/app/controllers/easy_ml/deploys_controller.rb +20 -0
- data/app/controllers/easy_ml/models_controller.rb +151 -0
- data/app/controllers/easy_ml/retraining_runs_controller.rb +19 -0
- data/app/controllers/easy_ml/settings_controller.rb +59 -0
- data/app/frontend/components/AlertProvider.tsx +108 -0
- data/app/frontend/components/DatasetPreview.tsx +161 -0
- data/app/frontend/components/EmptyState.tsx +28 -0
- data/app/frontend/components/ModelCard.tsx +255 -0
- data/app/frontend/components/ModelDetails.tsx +334 -0
- data/app/frontend/components/ModelForm.tsx +384 -0
- data/app/frontend/components/Navigation.tsx +300 -0
- data/app/frontend/components/Pagination.tsx +72 -0
- data/app/frontend/components/Popover.tsx +55 -0
- data/app/frontend/components/PredictionStream.tsx +105 -0
- data/app/frontend/components/ScheduleModal.tsx +726 -0
- data/app/frontend/components/SearchInput.tsx +23 -0
- data/app/frontend/components/SearchableSelect.tsx +132 -0
- data/app/frontend/components/dataset/AutosaveIndicator.tsx +39 -0
- data/app/frontend/components/dataset/ColumnConfigModal.tsx +431 -0
- data/app/frontend/components/dataset/ColumnFilters.tsx +256 -0
- data/app/frontend/components/dataset/ColumnList.tsx +101 -0
- data/app/frontend/components/dataset/FeatureConfigPopover.tsx +57 -0
- data/app/frontend/components/dataset/FeaturePicker.tsx +205 -0
- data/app/frontend/components/dataset/PreprocessingConfig.tsx +704 -0
- data/app/frontend/components/dataset/SplitConfigurator.tsx +120 -0
- data/app/frontend/components/dataset/splitters/DateSplitter.tsx +58 -0
- data/app/frontend/components/dataset/splitters/KFoldSplitter.tsx +68 -0
- data/app/frontend/components/dataset/splitters/LeavePOutSplitter.tsx +29 -0
- data/app/frontend/components/dataset/splitters/PredefinedSplitter.tsx +146 -0
- data/app/frontend/components/dataset/splitters/RandomSplitter.tsx +85 -0
- data/app/frontend/components/dataset/splitters/StratifiedSplitter.tsx +79 -0
- data/app/frontend/components/dataset/splitters/constants.ts +77 -0
- data/app/frontend/components/dataset/splitters/types.ts +168 -0
- data/app/frontend/components/dataset/splitters/utils.ts +53 -0
- data/app/frontend/components/features/CodeEditor.tsx +46 -0
- data/app/frontend/components/features/DataPreview.tsx +150 -0
- data/app/frontend/components/features/FeatureCard.tsx +88 -0
- data/app/frontend/components/features/FeatureForm.tsx +235 -0
- data/app/frontend/components/features/FeatureGroupCard.tsx +54 -0
- data/app/frontend/components/settings/PluginSettings.tsx +81 -0
- data/app/frontend/components/ui/badge.tsx +44 -0
- data/app/frontend/components/ui/collapsible.tsx +9 -0
- data/app/frontend/components/ui/scroll-area.tsx +46 -0
- data/app/frontend/components/ui/separator.tsx +29 -0
- data/app/frontend/entrypoints/App.tsx +40 -0
- data/app/frontend/entrypoints/Application.tsx +24 -0
- data/app/frontend/hooks/useAutosave.ts +61 -0
- data/app/frontend/layouts/Layout.tsx +38 -0
- data/app/frontend/lib/utils.ts +6 -0
- data/app/frontend/mockData.ts +272 -0
- data/app/frontend/pages/DatasetDetailsPage.tsx +103 -0
- data/app/frontend/pages/DatasetsPage.tsx +261 -0
- data/app/frontend/pages/DatasourceFormPage.tsx +147 -0
- data/app/frontend/pages/DatasourcesPage.tsx +261 -0
- data/app/frontend/pages/EditModelPage.tsx +45 -0
- data/app/frontend/pages/EditTransformationPage.tsx +56 -0
- data/app/frontend/pages/ModelsPage.tsx +115 -0
- data/app/frontend/pages/NewDatasetPage.tsx +366 -0
- data/app/frontend/pages/NewModelPage.tsx +45 -0
- data/app/frontend/pages/NewTransformationPage.tsx +43 -0
- data/app/frontend/pages/SettingsPage.tsx +272 -0
- data/app/frontend/pages/ShowModelPage.tsx +30 -0
- data/app/frontend/pages/TransformationsPage.tsx +95 -0
- data/app/frontend/styles/application.css +100 -0
- data/app/frontend/types/dataset.ts +146 -0
- data/app/frontend/types/datasource.ts +33 -0
- data/app/frontend/types/preprocessing.ts +1 -0
- data/app/frontend/types.ts +113 -0
- data/app/helpers/easy_ml/application_helper.rb +10 -0
- data/app/jobs/easy_ml/application_job.rb +21 -0
- data/app/jobs/easy_ml/batch_job.rb +46 -0
- data/app/jobs/easy_ml/compute_feature_job.rb +19 -0
- data/app/jobs/easy_ml/deploy_job.rb +13 -0
- data/app/jobs/easy_ml/finalize_feature_job.rb +15 -0
- data/app/jobs/easy_ml/refresh_dataset_job.rb +32 -0
- data/app/jobs/easy_ml/schedule_retraining_job.rb +11 -0
- data/app/jobs/easy_ml/sync_datasource_job.rb +17 -0
- data/app/jobs/easy_ml/training_job.rb +62 -0
- data/app/models/easy_ml/adapters/base_adapter.rb +45 -0
- data/app/models/easy_ml/adapters/polars_adapter.rb +77 -0
- data/app/models/easy_ml/cleaner.rb +82 -0
- data/app/models/easy_ml/column.rb +124 -0
- data/app/models/easy_ml/column_history.rb +30 -0
- data/app/models/easy_ml/column_list.rb +122 -0
- data/app/models/easy_ml/concerns/configurable.rb +61 -0
- data/app/models/easy_ml/concerns/versionable.rb +19 -0
- data/app/models/easy_ml/dataset.rb +767 -0
- data/app/models/easy_ml/dataset_history.rb +56 -0
- data/app/models/easy_ml/datasource.rb +182 -0
- data/app/models/easy_ml/datasource_history.rb +24 -0
- data/app/models/easy_ml/datasources/base_datasource.rb +54 -0
- data/app/models/easy_ml/datasources/file_datasource.rb +58 -0
- data/app/models/easy_ml/datasources/polars_datasource.rb +89 -0
- data/app/models/easy_ml/datasources/s3_datasource.rb +97 -0
- data/app/models/easy_ml/deploy.rb +114 -0
- data/app/models/easy_ml/event.rb +79 -0
- data/app/models/easy_ml/feature.rb +437 -0
- data/app/models/easy_ml/feature_history.rb +38 -0
- data/app/models/easy_ml/model.rb +575 -41
- data/app/models/easy_ml/model_file.rb +133 -0
- data/app/models/easy_ml/model_file_history.rb +24 -0
- data/app/models/easy_ml/model_history.rb +51 -0
- data/app/models/easy_ml/models/base_model.rb +58 -0
- data/app/models/easy_ml/models/hyperparameters/base.rb +99 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost/dart.rb +82 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost/gblinear.rb +82 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost/gbtree.rb +97 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost.rb +71 -0
- data/app/models/easy_ml/models/xgboost/evals_callback.rb +138 -0
- data/app/models/easy_ml/models/xgboost/progress_callback.rb +39 -0
- data/app/models/easy_ml/models/xgboost.rb +544 -5
- data/app/models/easy_ml/prediction.rb +44 -0
- data/app/models/easy_ml/retraining_job.rb +278 -0
- data/app/models/easy_ml/retraining_run.rb +184 -0
- data/app/models/easy_ml/settings.rb +37 -0
- data/app/models/easy_ml/splitter.rb +90 -0
- data/app/models/easy_ml/splitters/base_splitter.rb +28 -0
- data/app/models/easy_ml/splitters/date_splitter.rb +91 -0
- data/app/models/easy_ml/splitters/predefined_splitter.rb +74 -0
- data/app/models/easy_ml/splitters/random_splitter.rb +82 -0
- data/app/models/easy_ml/tuner_job.rb +56 -0
- data/app/models/easy_ml/tuner_run.rb +31 -0
- data/app/models/splitter_history.rb +6 -0
- data/app/serializers/easy_ml/column_serializer.rb +27 -0
- data/app/serializers/easy_ml/dataset_serializer.rb +73 -0
- data/app/serializers/easy_ml/datasource_serializer.rb +64 -0
- data/app/serializers/easy_ml/feature_serializer.rb +27 -0
- data/app/serializers/easy_ml/model_serializer.rb +90 -0
- data/app/serializers/easy_ml/retraining_job_serializer.rb +22 -0
- data/app/serializers/easy_ml/retraining_run_serializer.rb +39 -0
- data/app/serializers/easy_ml/settings_serializer.rb +9 -0
- data/app/views/layouts/easy_ml/application.html.erb +15 -0
- data/config/initializers/resque.rb +3 -0
- data/config/resque-pool.yml +6 -0
- data/config/routes.rb +39 -0
- data/config/spring.rb +1 -0
- data/config/vite.json +15 -0
- data/lib/easy_ml/configuration.rb +64 -0
- data/lib/easy_ml/core/evaluators/base_evaluator.rb +53 -0
- data/lib/easy_ml/core/evaluators/classification_evaluators.rb +126 -0
- data/lib/easy_ml/core/evaluators/regression_evaluators.rb +66 -0
- data/lib/easy_ml/core/model_evaluator.rb +161 -89
- data/lib/easy_ml/core/tuner/adapters/base_adapter.rb +28 -18
- data/lib/easy_ml/core/tuner/adapters/xgboost_adapter.rb +4 -25
- data/lib/easy_ml/core/tuner.rb +123 -62
- data/lib/easy_ml/core.rb +0 -3
- data/lib/easy_ml/core_ext/hash.rb +24 -0
- data/lib/easy_ml/core_ext/pathname.rb +11 -5
- data/lib/easy_ml/data/date_converter.rb +90 -0
- data/lib/easy_ml/data/filter_extensions.rb +31 -0
- data/lib/easy_ml/data/polars_column.rb +126 -0
- data/lib/easy_ml/data/polars_reader.rb +297 -0
- data/lib/easy_ml/data/preprocessor.rb +280 -142
- data/lib/easy_ml/data/simple_imputer.rb +255 -0
- data/lib/easy_ml/data/splits/file_split.rb +252 -0
- data/lib/easy_ml/data/splits/in_memory_split.rb +54 -0
- data/lib/easy_ml/data/splits/split.rb +95 -0
- data/lib/easy_ml/data/splits.rb +9 -0
- data/lib/easy_ml/data/statistics_learner.rb +93 -0
- data/lib/easy_ml/data/synced_directory.rb +341 -0
- data/lib/easy_ml/data.rb +6 -2
- data/lib/easy_ml/engine.rb +105 -6
- data/lib/easy_ml/feature_store.rb +227 -0
- data/lib/easy_ml/features.rb +61 -0
- data/lib/easy_ml/initializers/inflections.rb +17 -3
- data/lib/easy_ml/logging.rb +2 -2
- data/lib/easy_ml/predict.rb +74 -0
- data/lib/easy_ml/railtie/generators/migration/migration_generator.rb +192 -36
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_column_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_columns.rb.tt +25 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_dataset_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasets.rb.tt +31 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasource_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasources.rb.tt +16 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_deploys.rb.tt +24 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_events.rb.tt +20 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_feature_histories.rb.tt +14 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_features.rb.tt +32 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_file_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_files.rb.tt +17 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_models.rb.tt +20 -9
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_predictions.rb.tt +17 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_retraining_jobs.rb.tt +77 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_settings.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitter_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitters.rb.tt +15 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_tuner_jobs.rb.tt +40 -0
- data/lib/easy_ml/support/est.rb +5 -1
- data/lib/easy_ml/support/file_rotate.rb +79 -15
- data/lib/easy_ml/support/file_support.rb +9 -0
- data/lib/easy_ml/support/local_file.rb +24 -0
- data/lib/easy_ml/support/lockable.rb +62 -0
- data/lib/easy_ml/support/synced_file.rb +103 -0
- data/lib/easy_ml/support/utc.rb +5 -1
- data/lib/easy_ml/support.rb +6 -3
- data/lib/easy_ml/version.rb +4 -1
- data/lib/easy_ml.rb +7 -2
- metadata +355 -72
- data/app/models/easy_ml/models.rb +0 -5
- data/lib/easy_ml/core/model.rb +0 -30
- data/lib/easy_ml/core/model_core.rb +0 -181
- data/lib/easy_ml/core/models/hyperparameters/base.rb +0 -34
- data/lib/easy_ml/core/models/hyperparameters/xgboost.rb +0 -19
- data/lib/easy_ml/core/models/xgboost.rb +0 -10
- data/lib/easy_ml/core/models/xgboost_core.rb +0 -220
- data/lib/easy_ml/core/models.rb +0 -10
- data/lib/easy_ml/core/uploaders/model_uploader.rb +0 -24
- data/lib/easy_ml/core/uploaders.rb +0 -7
- data/lib/easy_ml/data/dataloader.rb +0 -6
- data/lib/easy_ml/data/dataset/data/preprocessor/statistics.json +0 -31
- data/lib/easy_ml/data/dataset/data/sample_info.json +0 -1
- data/lib/easy_ml/data/dataset/dataset/files/sample_info.json +0 -1
- data/lib/easy_ml/data/dataset/splits/file_split.rb +0 -140
- data/lib/easy_ml/data/dataset/splits/in_memory_split.rb +0 -49
- data/lib/easy_ml/data/dataset/splits/split.rb +0 -98
- data/lib/easy_ml/data/dataset/splits.rb +0 -11
- data/lib/easy_ml/data/dataset/splitters/date_splitter.rb +0 -43
- data/lib/easy_ml/data/dataset/splitters.rb +0 -9
- data/lib/easy_ml/data/dataset.rb +0 -430
- data/lib/easy_ml/data/datasource/datasource_factory.rb +0 -60
- data/lib/easy_ml/data/datasource/file_datasource.rb +0 -40
- data/lib/easy_ml/data/datasource/merged_datasource.rb +0 -64
- data/lib/easy_ml/data/datasource/polars_datasource.rb +0 -41
- data/lib/easy_ml/data/datasource/s3_datasource.rb +0 -89
- data/lib/easy_ml/data/datasource.rb +0 -33
- data/lib/easy_ml/data/preprocessor/preprocessor.rb +0 -205
- data/lib/easy_ml/data/preprocessor/simple_imputer.rb +0 -402
- data/lib/easy_ml/deployment.rb +0 -5
- data/lib/easy_ml/support/synced_directory.rb +0 -134
- data/lib/easy_ml/transforms.rb +0 -29
- /data/{lib/easy_ml/core → app/models/easy_ml}/models/hyperparameters.rb +0 -0
@@ -0,0 +1,272 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { usePage } from '@inertiajs/react'
|
3
|
+
import { useInertiaForm } from 'use-inertia-form';
|
4
|
+
import { Settings2, Save, AlertCircle, Key, Database, Globe2 } from 'lucide-react';
|
5
|
+
import { PluginSettings } from '../components/settings/PluginSettings';
|
6
|
+
|
7
|
+
interface Settings {
|
8
|
+
settings: {
|
9
|
+
timezone: string;
|
10
|
+
s3_bucket: string;
|
11
|
+
s3_region: string;
|
12
|
+
s3_access_key_id: string;
|
13
|
+
s3_secret_access_key: string;
|
14
|
+
wandb_api_key: string;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
const TIMEZONES = [
|
19
|
+
{ value: 'America/New_York', label: 'Eastern Time' },
|
20
|
+
{ value: 'America/Chicago', label: 'Central Time' },
|
21
|
+
{ value: 'America/Denver', label: 'Mountain Time' },
|
22
|
+
{ value: 'America/Los_Angeles', label: 'Pacific Time' }
|
23
|
+
];
|
24
|
+
|
25
|
+
export default function SettingsPage({ settings: initialSettings }: { settings: Settings }) {
|
26
|
+
const { rootPath } = usePage().props;
|
27
|
+
|
28
|
+
const form = useInertiaForm<Settings>({
|
29
|
+
settings: {
|
30
|
+
timezone: initialSettings?.settings?.timezone || 'America/New_York',
|
31
|
+
s3_bucket: initialSettings?.settings?.s3_bucket || '',
|
32
|
+
s3_region: initialSettings?.settings?.s3_region || 'us-east-1',
|
33
|
+
s3_access_key_id: initialSettings?.settings?.s3_access_key_id || '',
|
34
|
+
s3_secret_access_key: initialSettings?.settings?.s3_secret_access_key || '',
|
35
|
+
wandb_api_key: initialSettings?.settings?.wandb_api_key || ''
|
36
|
+
}
|
37
|
+
});
|
38
|
+
|
39
|
+
const { data: formData, setData: setFormData, patch, processing } = form;
|
40
|
+
|
41
|
+
const [showSecretKey, setShowSecretKey] = useState(false);
|
42
|
+
const [saved, setSaved] = useState(false);
|
43
|
+
const [error, setError] = useState<string | null>(null);
|
44
|
+
|
45
|
+
const handleSubmit = (e: React.FormEvent) => {
|
46
|
+
e.preventDefault();
|
47
|
+
setSaved(false);
|
48
|
+
setError(null);
|
49
|
+
|
50
|
+
const timeoutId = setTimeout(() => {
|
51
|
+
setError('Request timed out. Please try again.');
|
52
|
+
}, 3000);
|
53
|
+
|
54
|
+
patch(`${rootPath}/settings`, {
|
55
|
+
onSuccess: () => {
|
56
|
+
clearTimeout(timeoutId);
|
57
|
+
setSaved(true);
|
58
|
+
},
|
59
|
+
onError: () => {
|
60
|
+
clearTimeout(timeoutId);
|
61
|
+
setError('Failed to save settings. Please try again.');
|
62
|
+
}
|
63
|
+
});
|
64
|
+
};
|
65
|
+
|
66
|
+
return (
|
67
|
+
<div className="max-w-4xl mx-auto p-8">
|
68
|
+
<div className="bg-white rounded-lg shadow-lg">
|
69
|
+
<div className="px-6 py-4 border-b border-gray-200">
|
70
|
+
<div className="flex items-center gap-3">
|
71
|
+
<Settings2 className="w-6 h-6 text-blue-600" />
|
72
|
+
<h2 className="text-xl font-semibold text-gray-900">Settings</h2>
|
73
|
+
</div>
|
74
|
+
</div>
|
75
|
+
|
76
|
+
<form onSubmit={handleSubmit} className="p-6 space-y-8">
|
77
|
+
{/* General Settings */}
|
78
|
+
<div className="space-y-4">
|
79
|
+
<div className="flex items-center gap-2 mb-4">
|
80
|
+
<Globe2 className="w-5 h-5 text-gray-500" />
|
81
|
+
<h3 className="text-lg font-medium text-gray-900">General Settings</h3>
|
82
|
+
</div>
|
83
|
+
|
84
|
+
<div>
|
85
|
+
<label htmlFor="timezone" className="block text-sm font-medium text-gray-700 mb-1">
|
86
|
+
Timezone
|
87
|
+
</label>
|
88
|
+
<select
|
89
|
+
id="timezone"
|
90
|
+
value={formData.settings.timezone}
|
91
|
+
|
92
|
+
onChange={(e) => setFormData({
|
93
|
+
...formData,
|
94
|
+
settings: {
|
95
|
+
...formData.settings,
|
96
|
+
timezone: e.target.value
|
97
|
+
}
|
98
|
+
})}
|
99
|
+
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
100
|
+
>
|
101
|
+
{TIMEZONES.map((tz) => (
|
102
|
+
<option key={tz.value} value={tz.value}>
|
103
|
+
{tz.label}
|
104
|
+
</option>
|
105
|
+
))}
|
106
|
+
</select>
|
107
|
+
<p className="mt-1 text-sm text-gray-500">
|
108
|
+
All dates and times will be displayed in this timezone
|
109
|
+
</p>
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
|
113
|
+
{/* S3 Configuration */}
|
114
|
+
<div className="space-y-4">
|
115
|
+
<div className="flex items-center gap-2 mb-4">
|
116
|
+
|
117
|
+
<Database className="w-5 h-5 text-gray-500" />
|
118
|
+
<h3 className="text-lg font-medium text-gray-900">S3 Configuration</h3>
|
119
|
+
</div>
|
120
|
+
|
121
|
+
<div className="grid grid-cols-2 gap-6">
|
122
|
+
<div>
|
123
|
+
<label htmlFor="bucket" className="block text-sm font-medium text-gray-700 mb-1">
|
124
|
+
Default S3 Bucket
|
125
|
+
</label>
|
126
|
+
<input
|
127
|
+
type="text"
|
128
|
+
id="bucket"
|
129
|
+
value={formData.settings.s3_bucket}
|
130
|
+
onChange={(e) => setFormData({
|
131
|
+
...formData,
|
132
|
+
settings: {
|
133
|
+
...formData.settings,
|
134
|
+
s3_bucket: e.target.value
|
135
|
+
}
|
136
|
+
})}
|
137
|
+
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
138
|
+
placeholder="my-bucket"
|
139
|
+
/>
|
140
|
+
</div>
|
141
|
+
|
142
|
+
<div>
|
143
|
+
<label htmlFor="region" className="block text-sm font-medium text-gray-700 mb-1">
|
144
|
+
AWS Region
|
145
|
+
</label>
|
146
|
+
<select
|
147
|
+
id="region"
|
148
|
+
value={formData.settings.s3_region}
|
149
|
+
onChange={(e) => setFormData({
|
150
|
+
...formData,
|
151
|
+
settings: {
|
152
|
+
...formData.settings,
|
153
|
+
s3_region: e.target.value
|
154
|
+
}
|
155
|
+
})}
|
156
|
+
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
157
|
+
>
|
158
|
+
<option value="us-east-1">US East (N. Virginia)</option>
|
159
|
+
<option value="us-east-2">US East (Ohio)</option>
|
160
|
+
<option value="us-west-1">US West (N. California)</option>
|
161
|
+
<option value="us-west-2">US West (Oregon)</option>
|
162
|
+
</select>
|
163
|
+
</div>
|
164
|
+
</div>
|
165
|
+
|
166
|
+
<div className="bg-blue-50 rounded-lg p-4">
|
167
|
+
<div className="flex gap-2">
|
168
|
+
<AlertCircle className="w-5 h-5 text-blue-500 mt-0.5" />
|
169
|
+
<div>
|
170
|
+
<h4 className="text-sm font-medium text-blue-900">AWS Credentials</h4>
|
171
|
+
<p className="mt-1 text-sm text-blue-700">
|
172
|
+
These credentials will be used as default for all S3 operations. You can override them per datasource.
|
173
|
+
</p>
|
174
|
+
</div>
|
175
|
+
</div>
|
176
|
+
</div>
|
177
|
+
|
178
|
+
<div className="grid grid-cols-2 gap-6">
|
179
|
+
<div>
|
180
|
+
<label htmlFor="accessKeyId" className="block text-sm font-medium text-gray-700 mb-1">
|
181
|
+
Access Key ID
|
182
|
+
</label>
|
183
|
+
<div className="relative">
|
184
|
+
<Key className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
185
|
+
<input
|
186
|
+
type="text"
|
187
|
+
id="accessKeyId"
|
188
|
+
value={formData.settings.s3_access_key_id}
|
189
|
+
onChange={(e) => setFormData({
|
190
|
+
...formData,
|
191
|
+
settings: {
|
192
|
+
...formData.settings,
|
193
|
+
s3_access_key_id: e.target.value
|
194
|
+
}
|
195
|
+
})}
|
196
|
+
className="mt-1 block w-full pl-9 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
197
|
+
placeholder="AKIA..."
|
198
|
+
/>
|
199
|
+
</div>
|
200
|
+
</div>
|
201
|
+
|
202
|
+
<div>
|
203
|
+
<label htmlFor="secretAccessKey" className="block text-sm font-medium text-gray-700 mb-1">
|
204
|
+
Secret Access Key
|
205
|
+
</label>
|
206
|
+
<div className="relative">
|
207
|
+
<Key className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
208
|
+
<input
|
209
|
+
type={showSecretKey ? 'text' : 'password'}
|
210
|
+
id="secretAccessKey"
|
211
|
+
value={formData.settings.s3_secret_access_key}
|
212
|
+
onChange={(e) => setFormData({
|
213
|
+
...formData,
|
214
|
+
settings: {
|
215
|
+
...formData.settings,
|
216
|
+
s3_secret_access_key: e.target.value
|
217
|
+
}
|
218
|
+
})}
|
219
|
+
className="mt-1 block w-full pl-9 pr-24 rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
220
|
+
placeholder="Your secret key"
|
221
|
+
/>
|
222
|
+
<button
|
223
|
+
type="button"
|
224
|
+
onClick={() => setShowSecretKey(!showSecretKey)}
|
225
|
+
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-sm text-gray-500 hover:text-gray-700"
|
226
|
+
>
|
227
|
+
{showSecretKey ? 'Hide' : 'Show'}
|
228
|
+
</button>
|
229
|
+
</div>
|
230
|
+
</div>
|
231
|
+
</div>
|
232
|
+
</div>
|
233
|
+
|
234
|
+
<div className="border-t border-gray-200 pt-8">
|
235
|
+
<PluginSettings
|
236
|
+
settings={formData.settings}
|
237
|
+
setData={(settings) => setFormData({ ...settings })}
|
238
|
+
/>
|
239
|
+
</div>
|
240
|
+
|
241
|
+
<div className="pt-6 border-t flex items-center justify-between">
|
242
|
+
{saved && (
|
243
|
+
<div className="flex items-center gap-2 text-green-600">
|
244
|
+
<Save className="w-4 h-4" />
|
245
|
+
<span className="text-sm font-medium">Settings saved successfully</span>
|
246
|
+
</div>
|
247
|
+
)}
|
248
|
+
{error && (
|
249
|
+
<div className="flex items-center gap-2 text-red-600">
|
250
|
+
<AlertCircle className="w-4 h-4" />
|
251
|
+
<span className="text-sm font-medium">{error}</span>
|
252
|
+
</div>
|
253
|
+
)}
|
254
|
+
<div className="flex gap-3">
|
255
|
+
<button
|
256
|
+
type="submit"
|
257
|
+
disabled={processing}
|
258
|
+
className={`px-4 py-2 text-white text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${
|
259
|
+
processing
|
260
|
+
? 'bg-blue-400 cursor-not-allowed'
|
261
|
+
: 'bg-blue-600 hover:bg-blue-700'
|
262
|
+
}`}
|
263
|
+
>
|
264
|
+
{processing ? 'Saving...' : 'Save Settings'}
|
265
|
+
</button>
|
266
|
+
</div>
|
267
|
+
</div>
|
268
|
+
</form>
|
269
|
+
</div>
|
270
|
+
</div>
|
271
|
+
);
|
272
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { ModelDetails } from '../components/ModelDetails';
|
3
|
+
import { router, Link, usePage } from "@inertiajs/react";
|
4
|
+
import { useInertiaForm } from "use-inertia-form";
|
5
|
+
import { ArrowLeft, Brain } from 'lucide-react';
|
6
|
+
import { ModelForm } from '../components/ModelForm';
|
7
|
+
import { Model, Dataset } from '../types';
|
8
|
+
|
9
|
+
interface ModelFormData {
|
10
|
+
model: Model;
|
11
|
+
}
|
12
|
+
|
13
|
+
interface PageProps {
|
14
|
+
model: Model;
|
15
|
+
datasets: Dataset[];
|
16
|
+
constants: {
|
17
|
+
modelTypes: string[];
|
18
|
+
tasks: string[];
|
19
|
+
objectives: string[];
|
20
|
+
metrics: string[];
|
21
|
+
};
|
22
|
+
}
|
23
|
+
|
24
|
+
export default function ShowModelPage({ model, rootPath }: PageProps) {
|
25
|
+
return (
|
26
|
+
<div className="max-w-3xl mx-auto py-8">
|
27
|
+
<ModelDetails model={model} rootPath={rootPath} />
|
28
|
+
</div>
|
29
|
+
);
|
30
|
+
}
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { Plus, FolderPlus } from 'lucide-react';
|
3
|
+
// import { Link } from 'react-router-dom';
|
4
|
+
import { mockFeatureGroups } from '../mockData';
|
5
|
+
import { FeatureCard } from '../components/features/FeatureCard';
|
6
|
+
import { FeatureGroupCard } from '../components/features/FeatureGroupCard';
|
7
|
+
import { EmptyState } from '../components/EmptyState';
|
8
|
+
|
9
|
+
export default function FeaturesPage() {
|
10
|
+
const [view, setView] = useState<'groups' | 'all'>('groups');
|
11
|
+
|
12
|
+
if (mockFeatureGroups.length === 0) {
|
13
|
+
return (
|
14
|
+
<div className="p-8">
|
15
|
+
<EmptyState
|
16
|
+
icon={FolderPlus}
|
17
|
+
title="Create your first feature group"
|
18
|
+
description="Create a group to organize your column features"
|
19
|
+
actionLabel="Create Group"
|
20
|
+
onAction={() => {/* Handle group creation */}}
|
21
|
+
/>
|
22
|
+
</div>
|
23
|
+
);
|
24
|
+
}
|
25
|
+
|
26
|
+
const allFeatures = mockFeatureGroups.flatMap(g => g.features);
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div className="p-8">
|
30
|
+
<div className="space-y-6">
|
31
|
+
<div className="flex justify-between items-center">
|
32
|
+
<div className="flex items-center gap-4">
|
33
|
+
<h2 className="text-xl font-semibold text-gray-900">Features</h2>
|
34
|
+
<div className="flex rounded-md shadow-sm">
|
35
|
+
<button
|
36
|
+
onClick={() => setView('groups')}
|
37
|
+
className={`px-4 py-2 text-sm font-medium rounded-l-md ${
|
38
|
+
view === 'groups'
|
39
|
+
? 'bg-blue-600 text-white'
|
40
|
+
: 'bg-white text-gray-700 hover:text-gray-900 border border-gray-300'
|
41
|
+
}`}
|
42
|
+
>
|
43
|
+
Groups
|
44
|
+
</button>
|
45
|
+
<button
|
46
|
+
onClick={() => setView('all')}
|
47
|
+
className={`px-4 py-2 text-sm font-medium rounded-r-md ${
|
48
|
+
view === 'all'
|
49
|
+
? 'bg-blue-600 text-white'
|
50
|
+
: 'bg-white text-gray-700 hover:text-gray-900 border border-l-0 border-gray-300'
|
51
|
+
}`}
|
52
|
+
>
|
53
|
+
All Features
|
54
|
+
</button>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
<div className="flex gap-3">
|
58
|
+
<Link
|
59
|
+
to="/features/groups/new"
|
60
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-white border border-gray-300 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-50"
|
61
|
+
>
|
62
|
+
<FolderPlus className="w-4 h-4" />
|
63
|
+
New Group
|
64
|
+
</Link>
|
65
|
+
<Link
|
66
|
+
to="/features/new"
|
67
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700"
|
68
|
+
>
|
69
|
+
<Plus className="w-4 h-4" />
|
70
|
+
New Feature
|
71
|
+
</Link>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
{view === 'groups' ? (
|
76
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
77
|
+
{mockFeatureGroups.map((group) => (
|
78
|
+
<FeatureGroupCard key={group.id} group={group} />
|
79
|
+
))}
|
80
|
+
</div>
|
81
|
+
) : (
|
82
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
83
|
+
{allFeatures.map((feature) => (
|
84
|
+
<FeatureCard
|
85
|
+
key={feature.id}
|
86
|
+
feature={feature}
|
87
|
+
group={mockFeatureGroups.find(g => g.id === feature.groupId)!}
|
88
|
+
/>
|
89
|
+
))}
|
90
|
+
</div>
|
91
|
+
)}
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
);
|
95
|
+
}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
@tailwind base;
|
2
|
+
@tailwind components;
|
3
|
+
@tailwind utilities;
|
4
|
+
|
5
|
+
:root {
|
6
|
+
--background: 0 0% 100%;
|
7
|
+
--foreground: 222.2 84% 4.9%;
|
8
|
+
--card: 0 0% 100%;
|
9
|
+
--card-foreground: 222.2 84% 4.9%;
|
10
|
+
--popover: 0 0% 100%;
|
11
|
+
--popover-foreground: 222.2 84% 4.9%;
|
12
|
+
--primary: 221.2 83.2% 53.3%;
|
13
|
+
--primary-foreground: 210 40% 98%;
|
14
|
+
--secondary: 210 40% 96.1%;
|
15
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
16
|
+
--muted: 210 40% 96.1%;
|
17
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
18
|
+
--accent: 210 40% 96.1%;
|
19
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
20
|
+
--destructive: 0 84.2% 60.2%;
|
21
|
+
--destructive-foreground: 210 40% 98%;
|
22
|
+
--border: 214.3 31.8% 91.4%;
|
23
|
+
--input: 214.3 31.8% 91.4%;
|
24
|
+
--ring: 221.2 83.2% 53.3%;
|
25
|
+
--radius: 0.5rem;
|
26
|
+
}
|
27
|
+
|
28
|
+
* {
|
29
|
+
@apply border-border;
|
30
|
+
}
|
31
|
+
|
32
|
+
body {
|
33
|
+
@apply bg-background text-foreground;
|
34
|
+
font-feature-settings: "rlig" 1, "calt" 1;
|
35
|
+
}
|
36
|
+
|
37
|
+
@layer base {
|
38
|
+
:root {
|
39
|
+
--background: 0 0% 100%;
|
40
|
+
--foreground: 0 0% 3.9%;
|
41
|
+
--card: 0 0% 100%;
|
42
|
+
--card-foreground: 0 0% 3.9%;
|
43
|
+
--popover: 0 0% 100%;
|
44
|
+
--popover-foreground: 0 0% 3.9%;
|
45
|
+
--primary: 0 0% 9%;
|
46
|
+
--primary-foreground: 0 0% 98%;
|
47
|
+
--secondary: 0 0% 96.1%;
|
48
|
+
--secondary-foreground: 0 0% 9%;
|
49
|
+
--muted: 0 0% 96.1%;
|
50
|
+
--muted-foreground: 0 0% 45.1%;
|
51
|
+
--accent: 0 0% 96.1%;
|
52
|
+
--accent-foreground: 0 0% 9%;
|
53
|
+
--destructive: 0 84.2% 60.2%;
|
54
|
+
--destructive-foreground: 0 0% 98%;
|
55
|
+
--border: 0 0% 89.8%;
|
56
|
+
--input: 0 0% 89.8%;
|
57
|
+
--ring: 0 0% 3.9%;
|
58
|
+
--chart-1: 12 76% 61%;
|
59
|
+
--chart-2: 173 58% 39%;
|
60
|
+
--chart-3: 197 37% 24%;
|
61
|
+
--chart-4: 43 74% 66%;
|
62
|
+
--chart-5: 27 87% 67%;
|
63
|
+
--radius: 0.5rem;
|
64
|
+
}
|
65
|
+
.dark {
|
66
|
+
--background: 0 0% 3.9%;
|
67
|
+
--foreground: 0 0% 98%;
|
68
|
+
--card: 0 0% 3.9%;
|
69
|
+
--card-foreground: 0 0% 98%;
|
70
|
+
--popover: 0 0% 3.9%;
|
71
|
+
--popover-foreground: 0 0% 98%;
|
72
|
+
--primary: 0 0% 98%;
|
73
|
+
--primary-foreground: 0 0% 9%;
|
74
|
+
--secondary: 0 0% 14.9%;
|
75
|
+
--secondary-foreground: 0 0% 98%;
|
76
|
+
--muted: 0 0% 14.9%;
|
77
|
+
--muted-foreground: 0 0% 63.9%;
|
78
|
+
--accent: 0 0% 14.9%;
|
79
|
+
--accent-foreground: 0 0% 98%;
|
80
|
+
--destructive: 0 62.8% 30.6%;
|
81
|
+
--destructive-foreground: 0 0% 98%;
|
82
|
+
--border: 0 0% 14.9%;
|
83
|
+
--input: 0 0% 14.9%;
|
84
|
+
--ring: 0 0% 83.1%;
|
85
|
+
--chart-1: 220 70% 50%;
|
86
|
+
--chart-2: 160 60% 45%;
|
87
|
+
--chart-3: 30 80% 55%;
|
88
|
+
--chart-4: 280 65% 60%;
|
89
|
+
--chart-5: 340 75% 55%;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
@layer base {
|
94
|
+
* {
|
95
|
+
@apply border-border;
|
96
|
+
}
|
97
|
+
body {
|
98
|
+
@apply bg-background text-foreground;
|
99
|
+
}
|
100
|
+
}
|
@@ -0,0 +1,146 @@
|
|
1
|
+
import type { Datasource } from "./datasource";
|
2
|
+
import type { SplitterConfig } from '../components/dataset/splitters/types';
|
3
|
+
|
4
|
+
export type DatasetWorkflowStatus = "analyzing" | "ready" | "failed" | "locked";
|
5
|
+
export type DatasetStatus = "training" | "inference" | "retired";
|
6
|
+
export type ColumnType =
|
7
|
+
| "float"
|
8
|
+
| "integer"
|
9
|
+
| "boolean"
|
10
|
+
| "categorical"
|
11
|
+
| "datetime"
|
12
|
+
| "text"
|
13
|
+
| "string";
|
14
|
+
|
15
|
+
export type PreprocessingSteps = {
|
16
|
+
training?: PreprocessingStep;
|
17
|
+
inference?: PreprocessingStep;
|
18
|
+
};
|
19
|
+
|
20
|
+
export type Feature = {
|
21
|
+
id?: number;
|
22
|
+
name: string;
|
23
|
+
feature_class: string;
|
24
|
+
feature_position: number;
|
25
|
+
dataset_id?: number;
|
26
|
+
description?: string;
|
27
|
+
feature_type?: "calculation" | "lookup" | "other";
|
28
|
+
_destroy?: boolean;
|
29
|
+
};
|
30
|
+
|
31
|
+
export type PreprocessingStep = {
|
32
|
+
method:
|
33
|
+
| "none"
|
34
|
+
| "mean"
|
35
|
+
| "median"
|
36
|
+
| "ffill"
|
37
|
+
| "most_frequent"
|
38
|
+
| "categorical"
|
39
|
+
| "constant"
|
40
|
+
| "today";
|
41
|
+
params: {
|
42
|
+
value?: number;
|
43
|
+
constant?: string;
|
44
|
+
categorical_min?: number;
|
45
|
+
clip?: {
|
46
|
+
min?: number;
|
47
|
+
max?: number;
|
48
|
+
};
|
49
|
+
one_hot?: boolean;
|
50
|
+
ordinal_encoding?: boolean;
|
51
|
+
};
|
52
|
+
};
|
53
|
+
|
54
|
+
export interface StatisticSet {
|
55
|
+
mean?: number;
|
56
|
+
median?: number;
|
57
|
+
min?: number;
|
58
|
+
max?: number;
|
59
|
+
last_value?: string;
|
60
|
+
count?: number;
|
61
|
+
null_count?: number;
|
62
|
+
unique_count?: number;
|
63
|
+
most_frequent_value?: string;
|
64
|
+
counts: object;
|
65
|
+
num_rows?: number;
|
66
|
+
sample_data?: any[];
|
67
|
+
}
|
68
|
+
|
69
|
+
export interface Statistics {
|
70
|
+
raw: StatisticSet;
|
71
|
+
processed: StatisticSet;
|
72
|
+
}
|
73
|
+
export interface Column {
|
74
|
+
id: number;
|
75
|
+
name: string;
|
76
|
+
type: ColumnType;
|
77
|
+
description?: string;
|
78
|
+
dataset_id: number;
|
79
|
+
datatype: string;
|
80
|
+
polars_datatype: string;
|
81
|
+
is_target: boolean;
|
82
|
+
hidden: boolean;
|
83
|
+
drop_if_null: boolean;
|
84
|
+
sample_values: {};
|
85
|
+
statistics?: Statistics;
|
86
|
+
preprocessing_steps?: PreprocessingSteps;
|
87
|
+
}
|
88
|
+
|
89
|
+
export interface Dataset {
|
90
|
+
id: number;
|
91
|
+
name: string;
|
92
|
+
description?: string;
|
93
|
+
status: DatasetStatus;
|
94
|
+
needs_refresh: boolean;
|
95
|
+
workflow_status: DatasetWorkflowStatus;
|
96
|
+
target?: string;
|
97
|
+
num_rows?: number;
|
98
|
+
drop_cols?: string[];
|
99
|
+
datasource_id: number;
|
100
|
+
columns: Array<Column>;
|
101
|
+
sample_data: Record<string, any>[];
|
102
|
+
features?: Array<Feature>;
|
103
|
+
preprocessing_steps: {
|
104
|
+
training: Record<string, any>;
|
105
|
+
};
|
106
|
+
splitter_attributes?: {
|
107
|
+
splitter_type: string;
|
108
|
+
date_col: string;
|
109
|
+
months_test: number;
|
110
|
+
months_valid: number;
|
111
|
+
};
|
112
|
+
stacktrace?: string;
|
113
|
+
}
|
114
|
+
|
115
|
+
export interface NewDatasetForm {
|
116
|
+
dataset: {
|
117
|
+
name: string;
|
118
|
+
datasource_id: string;
|
119
|
+
splitter_attributes: SplitterConfig;
|
120
|
+
};
|
121
|
+
}
|
122
|
+
|
123
|
+
export interface NewDatasetFormProps {
|
124
|
+
constants: {
|
125
|
+
splitter_constants: {
|
126
|
+
SPLITTER_TYPES: Array<{
|
127
|
+
value: string;
|
128
|
+
label: string;
|
129
|
+
description: string;
|
130
|
+
}>;
|
131
|
+
DEFAULT_CONFIGS: Record<string, any>;
|
132
|
+
};
|
133
|
+
};
|
134
|
+
datasources: Array<{
|
135
|
+
id: number;
|
136
|
+
name: string;
|
137
|
+
columns: string[];
|
138
|
+
}>;
|
139
|
+
}
|
140
|
+
|
141
|
+
export interface PreprocessingConstants {
|
142
|
+
column_types: Array<{ value: ColumnType; label: string }>;
|
143
|
+
preprocessing_strategies: {
|
144
|
+
[K in ColumnType]: Array<{ value: string; label: string }>;
|
145
|
+
};
|
146
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
export type ColumnType =
|
2
|
+
| "float"
|
3
|
+
| "integer"
|
4
|
+
| "boolean"
|
5
|
+
| "categorical"
|
6
|
+
| "datetime"
|
7
|
+
| "text"
|
8
|
+
| "string";
|
9
|
+
|
10
|
+
export interface Schema {
|
11
|
+
[key: string]: ColumnType;
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface Datasource {
|
15
|
+
id: number;
|
16
|
+
name: string;
|
17
|
+
datasource_type: string;
|
18
|
+
is_syncing: boolean;
|
19
|
+
last_synced_at: string | null;
|
20
|
+
columns: string[];
|
21
|
+
schema: Schema;
|
22
|
+
error?: string;
|
23
|
+
}
|
24
|
+
|
25
|
+
export interface DatasourceFormProps {
|
26
|
+
datasource?: Datasource;
|
27
|
+
constants: {
|
28
|
+
DATASOURCE_TYPES: Array<{ value: string; label: string; description: string }>;
|
29
|
+
s3: {
|
30
|
+
S3_REGIONS: Array<{ value: string; label: string }>;
|
31
|
+
};
|
32
|
+
};
|
33
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
|