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,72 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
3
|
+
|
4
|
+
interface PaginationProps {
|
5
|
+
currentPage: number;
|
6
|
+
totalPages: number;
|
7
|
+
onPageChange: (page: number) => void;
|
8
|
+
}
|
9
|
+
|
10
|
+
export function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) {
|
11
|
+
return (
|
12
|
+
<div className="flex items-center justify-between px-4 py-3 border-t border-gray-200">
|
13
|
+
<div className="flex-1 flex justify-between sm:hidden">
|
14
|
+
<button
|
15
|
+
onClick={() => onPageChange(currentPage - 1)}
|
16
|
+
disabled={currentPage === 1}
|
17
|
+
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:bg-gray-100 disabled:text-gray-400"
|
18
|
+
>
|
19
|
+
Previous
|
20
|
+
</button>
|
21
|
+
<button
|
22
|
+
onClick={() => onPageChange(currentPage + 1)}
|
23
|
+
disabled={currentPage === totalPages}
|
24
|
+
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:bg-gray-100 disabled:text-gray-400"
|
25
|
+
>
|
26
|
+
Next
|
27
|
+
</button>
|
28
|
+
</div>
|
29
|
+
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
30
|
+
<div>
|
31
|
+
<p className="text-sm text-gray-700">
|
32
|
+
Page <span className="font-medium">{currentPage}</span> of{' '}
|
33
|
+
<span className="font-medium">{totalPages}</span>
|
34
|
+
</p>
|
35
|
+
</div>
|
36
|
+
<div>
|
37
|
+
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
38
|
+
<button
|
39
|
+
onClick={() => onPageChange(currentPage - 1)}
|
40
|
+
disabled={currentPage === 1}
|
41
|
+
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:bg-gray-100 disabled:text-gray-400"
|
42
|
+
>
|
43
|
+
<span className="sr-only">Previous</span>
|
44
|
+
<ChevronLeft className="h-5 w-5" />
|
45
|
+
</button>
|
46
|
+
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
47
|
+
<button
|
48
|
+
key={page}
|
49
|
+
onClick={() => onPageChange(page)}
|
50
|
+
className={`relative inline-flex items-center px-4 py-2 border text-sm font-medium ${
|
51
|
+
page === currentPage
|
52
|
+
? 'z-10 bg-blue-50 border-blue-500 text-blue-600'
|
53
|
+
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
|
54
|
+
}`}
|
55
|
+
>
|
56
|
+
{page}
|
57
|
+
</button>
|
58
|
+
))}
|
59
|
+
<button
|
60
|
+
onClick={() => onPageChange(currentPage + 1)}
|
61
|
+
disabled={currentPage === totalPages}
|
62
|
+
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:bg-gray-100 disabled:text-gray-400"
|
63
|
+
>
|
64
|
+
<span className="sr-only">Next</span>
|
65
|
+
<ChevronRight className="h-5 w-5" />
|
66
|
+
</button>
|
67
|
+
</nav>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
);
|
72
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
2
|
+
import { X } from "lucide-react";
|
3
|
+
|
4
|
+
interface PopoverProps {
|
5
|
+
trigger: React.ReactElement;
|
6
|
+
children: React.ReactNode;
|
7
|
+
className?: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
export function Popover({ trigger, children, className = "" }: PopoverProps) {
|
11
|
+
const [isOpen, setIsOpen] = useState(false);
|
12
|
+
const popoverRef = useRef<HTMLDivElement>(null);
|
13
|
+
const triggerRef = useRef<HTMLDivElement>(null);
|
14
|
+
|
15
|
+
useEffect(() => {
|
16
|
+
const handleClickOutside = (event: MouseEvent) => {
|
17
|
+
if (
|
18
|
+
popoverRef.current &&
|
19
|
+
!popoverRef.current.contains(event.target as Node) &&
|
20
|
+
!triggerRef.current?.contains(event.target as Node)
|
21
|
+
) {
|
22
|
+
setIsOpen(false);
|
23
|
+
}
|
24
|
+
};
|
25
|
+
|
26
|
+
document.addEventListener("mousedown", handleClickOutside);
|
27
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
28
|
+
}, []);
|
29
|
+
|
30
|
+
return (
|
31
|
+
<div className="relative">
|
32
|
+
<div ref={triggerRef} onClick={() => setIsOpen(!isOpen)}>
|
33
|
+
{trigger}
|
34
|
+
</div>
|
35
|
+
|
36
|
+
{isOpen && (
|
37
|
+
<div
|
38
|
+
ref={popoverRef}
|
39
|
+
className={`absolute z-50 right-0 mt-2 bg-white rounded-lg shadow-lg border border-gray-200 ${className}`}
|
40
|
+
>
|
41
|
+
<div className="flex justify-between items-center p-3 border-b border-gray-200">
|
42
|
+
<h3 className="font-medium">Feature Configuration</h3>
|
43
|
+
<button
|
44
|
+
onClick={() => setIsOpen(false)}
|
45
|
+
className="text-gray-400 hover:text-gray-600"
|
46
|
+
>
|
47
|
+
<X className="w-4 h-4" />
|
48
|
+
</button>
|
49
|
+
</div>
|
50
|
+
<div className="p-4">{children}</div>
|
51
|
+
</div>
|
52
|
+
)}
|
53
|
+
</div>
|
54
|
+
);
|
55
|
+
}
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { ChevronLeft, ChevronRight, Clock, CheckCircle2, XCircle } from 'lucide-react';
|
3
|
+
import type { Prediction } from '../types';
|
4
|
+
|
5
|
+
interface PredictionStreamProps {
|
6
|
+
predictions: Prediction[];
|
7
|
+
}
|
8
|
+
|
9
|
+
const ITEMS_PER_PAGE = 10;
|
10
|
+
|
11
|
+
export function PredictionStream({ predictions }: PredictionStreamProps) {
|
12
|
+
const [currentPage, setCurrentPage] = useState(1);
|
13
|
+
const totalPages = Math.ceil(predictions.length / ITEMS_PER_PAGE);
|
14
|
+
|
15
|
+
const paginatedPredictions = predictions.slice(
|
16
|
+
(currentPage - 1) * ITEMS_PER_PAGE,
|
17
|
+
currentPage * ITEMS_PER_PAGE
|
18
|
+
);
|
19
|
+
|
20
|
+
return (
|
21
|
+
<div className="bg-white rounded-lg shadow-lg p-6">
|
22
|
+
<div className="flex justify-between items-center mb-6">
|
23
|
+
<h3 className="text-lg font-semibold">Live Predictions</h3>
|
24
|
+
<div className="flex items-center gap-2">
|
25
|
+
<button
|
26
|
+
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
27
|
+
disabled={currentPage === 1}
|
28
|
+
className="p-1 rounded-md hover:bg-gray-100 disabled:opacity-50"
|
29
|
+
>
|
30
|
+
<ChevronLeft className="w-5 h-5" />
|
31
|
+
</button>
|
32
|
+
<span className="text-sm text-gray-600">
|
33
|
+
Page {currentPage} of {totalPages}
|
34
|
+
</span>
|
35
|
+
<button
|
36
|
+
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
37
|
+
disabled={currentPage === totalPages}
|
38
|
+
className="p-1 rounded-md hover:bg-gray-100 disabled:opacity-50"
|
39
|
+
>
|
40
|
+
<ChevronRight className="w-5 h-5" />
|
41
|
+
</button>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div className="space-y-4">
|
46
|
+
{paginatedPredictions.map((prediction) => (
|
47
|
+
<div
|
48
|
+
key={prediction.id}
|
49
|
+
className="border border-gray-200 rounded-lg p-4 hover:border-gray-300 transition-colors"
|
50
|
+
>
|
51
|
+
<div className="flex justify-between items-start mb-3">
|
52
|
+
<div className="flex items-center gap-2">
|
53
|
+
<Clock className="w-4 h-4 text-gray-400" />
|
54
|
+
<span className="text-sm text-gray-500">
|
55
|
+
{new Date(prediction.timestamp).toLocaleString()}
|
56
|
+
</span>
|
57
|
+
</div>
|
58
|
+
<div className="flex items-center gap-2">
|
59
|
+
<span className="text-sm text-gray-500">
|
60
|
+
{prediction.latencyMs.toFixed(2)}ms
|
61
|
+
</span>
|
62
|
+
{prediction.groundTruth !== undefined && (
|
63
|
+
prediction.output === prediction.groundTruth ? (
|
64
|
+
<CheckCircle2 className="w-5 h-5 text-green-500" />
|
65
|
+
) : (
|
66
|
+
<XCircle className="w-5 h-5 text-red-500" />
|
67
|
+
)
|
68
|
+
)}
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
|
72
|
+
<div className="grid md:grid-cols-2 gap-6">
|
73
|
+
<div>
|
74
|
+
<h4 className="text-sm font-medium text-gray-500 mb-2">Input</h4>
|
75
|
+
<div className="bg-gray-50 rounded-md p-3">
|
76
|
+
<pre className="text-sm whitespace-pre-wrap">
|
77
|
+
{JSON.stringify(prediction.input, null, 2)}
|
78
|
+
</pre>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
|
82
|
+
<div>
|
83
|
+
<h4 className="text-sm font-medium text-gray-500 mb-2">Prediction</h4>
|
84
|
+
<div className="bg-gray-50 rounded-md p-3">
|
85
|
+
<div className="flex items-center justify-between">
|
86
|
+
<span className="font-medium">
|
87
|
+
{typeof prediction.output === 'boolean'
|
88
|
+
? prediction.output ? 'Will Churn' : 'Will Not Churn'
|
89
|
+
: prediction.output}
|
90
|
+
</span>
|
91
|
+
{prediction.groundTruth !== undefined && (
|
92
|
+
<span className="text-sm text-gray-500">
|
93
|
+
Ground Truth: {prediction.groundTruth ? 'Churned' : 'Retained'}
|
94
|
+
</span>
|
95
|
+
)}
|
96
|
+
</div>
|
97
|
+
</div>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
))}
|
102
|
+
</div>
|
103
|
+
</div>
|
104
|
+
);
|
105
|
+
}
|