Spaces:
Sleeping
Sleeping
froont
Browse files- .gitignore +2 -1
- frontend/package-lock.json +0 -0
- frontend/package.json +40 -0
- frontend/postcss.config.js +6 -0
- frontend/src/App.tsx +63 -0
- frontend/src/api/api.ts +62 -0
- frontend/src/components/Classify.tsx +221 -0
- frontend/src/components/Home.tsx +122 -0
- frontend/src/components/Improve.tsx +172 -0
- frontend/src/components/Validate.tsx +156 -0
- frontend/src/index.css +17 -0
- frontend/src/index.tsx +14 -0
- frontend/src/types/api.ts +65 -0
- frontend/tailwind.config.js +24 -0
- frontend/tsconfig.json +20 -0
.gitignore
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
.env
|
2 |
*.pyc
|
3 |
-
/temp_exports/*
|
|
|
|
1 |
.env
|
2 |
*.pyc
|
3 |
+
/temp_exports/*
|
4 |
+
/frontend/node_modules/*
|
frontend/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
frontend/package.json
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "text-classifier-frontend",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"dependencies": {
|
6 |
+
"@types/node": "^16.18.0",
|
7 |
+
"@types/react": "^18.0.0",
|
8 |
+
"@types/react-dom": "^18.0.0",
|
9 |
+
"axios": "^0.27.2",
|
10 |
+
"react": "^18.2.0",
|
11 |
+
"react-dom": "^18.2.0",
|
12 |
+
"react-router-dom": "^6.3.0",
|
13 |
+
"react-scripts": "5.0.1",
|
14 |
+
"typescript": "^4.8.4"
|
15 |
+
},
|
16 |
+
"scripts": {
|
17 |
+
"start": "react-scripts start",
|
18 |
+
"build": "react-scripts build",
|
19 |
+
"test": "react-scripts test",
|
20 |
+
"eject": "react-scripts eject"
|
21 |
+
},
|
22 |
+
"eslintConfig": {
|
23 |
+
"extends": [
|
24 |
+
"react-app",
|
25 |
+
"react-app/jest"
|
26 |
+
]
|
27 |
+
},
|
28 |
+
"browserslist": {
|
29 |
+
"production": [
|
30 |
+
">0.2%",
|
31 |
+
"not dead",
|
32 |
+
"not op_mini all"
|
33 |
+
],
|
34 |
+
"development": [
|
35 |
+
"last 1 chrome version",
|
36 |
+
"last 1 firefox version",
|
37 |
+
"last 1 safari version"
|
38 |
+
]
|
39 |
+
}
|
40 |
+
}
|
frontend/postcss.config.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
}
|
frontend/src/App.tsx
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react';
|
2 |
+
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
|
3 |
+
import Home from './components/Home';
|
4 |
+
import Classify from './components/Classify';
|
5 |
+
import Validate from './components/Validate';
|
6 |
+
import Improve from './components/Improve';
|
7 |
+
|
8 |
+
const App: React.FC = () => {
|
9 |
+
return (
|
10 |
+
<Router>
|
11 |
+
<div className="min-h-screen bg-gray-50">
|
12 |
+
<nav className="bg-white shadow-sm">
|
13 |
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
14 |
+
<div className="flex justify-between h-16">
|
15 |
+
<div className="flex">
|
16 |
+
<div className="flex-shrink-0 flex items-center">
|
17 |
+
<span className="text-xl font-bold text-primary-600">Text Classifier</span>
|
18 |
+
</div>
|
19 |
+
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
|
20 |
+
<Link
|
21 |
+
to="/"
|
22 |
+
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
|
23 |
+
>
|
24 |
+
Home
|
25 |
+
</Link>
|
26 |
+
<Link
|
27 |
+
to="/classify"
|
28 |
+
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
|
29 |
+
>
|
30 |
+
Classify
|
31 |
+
</Link>
|
32 |
+
<Link
|
33 |
+
to="/validate"
|
34 |
+
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
|
35 |
+
>
|
36 |
+
Validate
|
37 |
+
</Link>
|
38 |
+
<Link
|
39 |
+
to="/improve"
|
40 |
+
className="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium"
|
41 |
+
>
|
42 |
+
Improve
|
43 |
+
</Link>
|
44 |
+
</div>
|
45 |
+
</div>
|
46 |
+
</div>
|
47 |
+
</div>
|
48 |
+
</nav>
|
49 |
+
|
50 |
+
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
51 |
+
<Routes>
|
52 |
+
<Route path="/" element={<Home />} />
|
53 |
+
<Route path="/classify" element={<Classify />} />
|
54 |
+
<Route path="/validate" element={<Validate />} />
|
55 |
+
<Route path="/improve" element={<Improve />} />
|
56 |
+
</Routes>
|
57 |
+
</main>
|
58 |
+
</div>
|
59 |
+
</Router>
|
60 |
+
);
|
61 |
+
};
|
62 |
+
|
63 |
+
export default App;
|
frontend/src/api/api.ts
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import axios from 'axios';
|
2 |
+
import {
|
3 |
+
ClassificationResponse,
|
4 |
+
BatchClassificationResponse,
|
5 |
+
CategorySuggestionResponse,
|
6 |
+
ValidationRequest,
|
7 |
+
ValidationResponse,
|
8 |
+
ImprovementRequest,
|
9 |
+
ImprovementResponse,
|
10 |
+
ModelInfoResponse,
|
11 |
+
HealthResponse
|
12 |
+
} from '../types/api';
|
13 |
+
|
14 |
+
const API_BASE_URL = 'http://localhost:8000';
|
15 |
+
|
16 |
+
const api = axios.create({
|
17 |
+
baseURL: API_BASE_URL,
|
18 |
+
headers: {
|
19 |
+
'Content-Type': 'application/json',
|
20 |
+
},
|
21 |
+
});
|
22 |
+
|
23 |
+
export const healthCheck = async (): Promise<HealthResponse> => {
|
24 |
+
const response = await api.get<HealthResponse>('/health');
|
25 |
+
return response.data;
|
26 |
+
};
|
27 |
+
|
28 |
+
export const getModelInfo = async (): Promise<ModelInfoResponse> => {
|
29 |
+
const response = await api.get<ModelInfoResponse>('/model-info');
|
30 |
+
return response.data;
|
31 |
+
};
|
32 |
+
|
33 |
+
export const classifyText = async (text: string, categories?: string[]): Promise<ClassificationResponse> => {
|
34 |
+
const response = await api.post<ClassificationResponse>('/classify', {
|
35 |
+
text,
|
36 |
+
categories,
|
37 |
+
});
|
38 |
+
return response.data;
|
39 |
+
};
|
40 |
+
|
41 |
+
export const classifyBatch = async (texts: string[], categories?: string[]): Promise<BatchClassificationResponse> => {
|
42 |
+
const response = await api.post<BatchClassificationResponse>('/classify-batch', {
|
43 |
+
texts,
|
44 |
+
categories,
|
45 |
+
});
|
46 |
+
return response.data;
|
47 |
+
};
|
48 |
+
|
49 |
+
export const suggestCategories = async (texts: string[]): Promise<CategorySuggestionResponse> => {
|
50 |
+
const response = await api.post<CategorySuggestionResponse>('/suggest-categories', texts);
|
51 |
+
return response.data;
|
52 |
+
};
|
53 |
+
|
54 |
+
export const validateClassifications = async (request: ValidationRequest): Promise<ValidationResponse> => {
|
55 |
+
const response = await api.post<ValidationResponse>('/validate', request);
|
56 |
+
return response.data;
|
57 |
+
};
|
58 |
+
|
59 |
+
export const improveClassification = async (request: ImprovementRequest): Promise<ImprovementResponse> => {
|
60 |
+
const response = await api.post<ImprovementResponse>('/improve-classification', request);
|
61 |
+
return response.data;
|
62 |
+
};
|
frontend/src/components/Classify.tsx
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState } from 'react';
|
2 |
+
import { classifyText, classifyBatch, suggestCategories } from '../api/api';
|
3 |
+
import { ClassificationResponse, CategorySuggestionResponse } from '../types/api';
|
4 |
+
|
5 |
+
const Classify: React.FC = () => {
|
6 |
+
const [text, setText] = useState('');
|
7 |
+
const [categories, setCategories] = useState<string[]>([]);
|
8 |
+
const [result, setResult] = useState<ClassificationResponse | null>(null);
|
9 |
+
const [batchResults, setBatchResults] = useState<ClassificationResponse[]>([]);
|
10 |
+
const [loading, setLoading] = useState(false);
|
11 |
+
const [error, setError] = useState<string | null>(null);
|
12 |
+
const [suggestedCategories, setSuggestedCategories] = useState<string[]>([]);
|
13 |
+
|
14 |
+
const handleClassify = async () => {
|
15 |
+
if (!text) return;
|
16 |
+
|
17 |
+
setLoading(true);
|
18 |
+
setError(null);
|
19 |
+
|
20 |
+
try {
|
21 |
+
const response = await classifyText(text, categories.length > 0 ? categories : undefined);
|
22 |
+
setResult(response);
|
23 |
+
} catch (err) {
|
24 |
+
setError('Failed to classify text');
|
25 |
+
} finally {
|
26 |
+
setLoading(false);
|
27 |
+
}
|
28 |
+
};
|
29 |
+
|
30 |
+
const handleBatchClassify = async () => {
|
31 |
+
if (!text) return;
|
32 |
+
|
33 |
+
setLoading(true);
|
34 |
+
setError(null);
|
35 |
+
|
36 |
+
try {
|
37 |
+
const texts = text.split('\n').filter(t => t.trim());
|
38 |
+
const response = await classifyBatch(texts, categories.length > 0 ? categories : undefined);
|
39 |
+
setBatchResults(response.results);
|
40 |
+
} catch (err) {
|
41 |
+
setError('Failed to classify texts');
|
42 |
+
} finally {
|
43 |
+
setLoading(false);
|
44 |
+
}
|
45 |
+
};
|
46 |
+
|
47 |
+
const handleSuggestCategories = async () => {
|
48 |
+
if (!text) return;
|
49 |
+
|
50 |
+
setLoading(true);
|
51 |
+
setError(null);
|
52 |
+
|
53 |
+
try {
|
54 |
+
const texts = text.split('\n').filter(t => t.trim());
|
55 |
+
const response = await suggestCategories(texts);
|
56 |
+
setSuggestedCategories(response.categories);
|
57 |
+
} catch (err) {
|
58 |
+
setError('Failed to suggest categories');
|
59 |
+
} finally {
|
60 |
+
setLoading(false);
|
61 |
+
}
|
62 |
+
};
|
63 |
+
|
64 |
+
return (
|
65 |
+
<div className="space-y-6">
|
66 |
+
<div className="bg-white shadow sm:rounded-lg">
|
67 |
+
<div className="px-4 py-5 sm:p-6">
|
68 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Text Classification</h3>
|
69 |
+
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
70 |
+
<p>Enter text to classify or multiple texts (one per line) for batch classification.</p>
|
71 |
+
</div>
|
72 |
+
<div className="mt-5">
|
73 |
+
<textarea
|
74 |
+
rows={4}
|
75 |
+
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
76 |
+
placeholder="Enter text to classify..."
|
77 |
+
value={text}
|
78 |
+
onChange={(e) => setText(e.target.value)}
|
79 |
+
/>
|
80 |
+
</div>
|
81 |
+
<div className="mt-5">
|
82 |
+
<div className="flex items-center space-x-4">
|
83 |
+
<button
|
84 |
+
type="button"
|
85 |
+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
86 |
+
onClick={handleClassify}
|
87 |
+
disabled={loading}
|
88 |
+
>
|
89 |
+
Classify
|
90 |
+
</button>
|
91 |
+
<button
|
92 |
+
type="button"
|
93 |
+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
94 |
+
onClick={handleBatchClassify}
|
95 |
+
disabled={loading}
|
96 |
+
>
|
97 |
+
Batch Classify
|
98 |
+
</button>
|
99 |
+
<button
|
100 |
+
type="button"
|
101 |
+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
102 |
+
onClick={handleSuggestCategories}
|
103 |
+
disabled={loading}
|
104 |
+
>
|
105 |
+
Suggest Categories
|
106 |
+
</button>
|
107 |
+
</div>
|
108 |
+
</div>
|
109 |
+
</div>
|
110 |
+
</div>
|
111 |
+
|
112 |
+
{loading && (
|
113 |
+
<div className="flex justify-center">
|
114 |
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500"></div>
|
115 |
+
</div>
|
116 |
+
)}
|
117 |
+
|
118 |
+
{error && (
|
119 |
+
<div className="bg-red-50 border-l-4 border-red-400 p-4">
|
120 |
+
<div className="flex">
|
121 |
+
<div className="flex-shrink-0">
|
122 |
+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
123 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
124 |
+
</svg>
|
125 |
+
</div>
|
126 |
+
<div className="ml-3">
|
127 |
+
<p className="text-sm text-red-700">{error}</p>
|
128 |
+
</div>
|
129 |
+
</div>
|
130 |
+
</div>
|
131 |
+
)}
|
132 |
+
|
133 |
+
{result && (
|
134 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
135 |
+
<div className="px-4 py-5 sm:px-6">
|
136 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Classification Result</h3>
|
137 |
+
</div>
|
138 |
+
<div className="border-t border-gray-200">
|
139 |
+
<dl>
|
140 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
141 |
+
<dt className="text-sm font-medium text-gray-500">Category</dt>
|
142 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
143 |
+
{result.category}
|
144 |
+
</dd>
|
145 |
+
</div>
|
146 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
147 |
+
<dt className="text-sm font-medium text-gray-500">Confidence</dt>
|
148 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
149 |
+
{(result.confidence * 100).toFixed(2)}%
|
150 |
+
</dd>
|
151 |
+
</div>
|
152 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
153 |
+
<dt className="text-sm font-medium text-gray-500">Explanation</dt>
|
154 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
155 |
+
{result.explanation}
|
156 |
+
</dd>
|
157 |
+
</div>
|
158 |
+
</dl>
|
159 |
+
</div>
|
160 |
+
</div>
|
161 |
+
)}
|
162 |
+
|
163 |
+
{batchResults.length > 0 && (
|
164 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
165 |
+
<div className="px-4 py-5 sm:px-6">
|
166 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Batch Classification Results</h3>
|
167 |
+
</div>
|
168 |
+
<div className="border-t border-gray-200">
|
169 |
+
<ul className="divide-y divide-gray-200">
|
170 |
+
{batchResults.map((result, index) => (
|
171 |
+
<li key={index} className="px-4 py-4 sm:px-6">
|
172 |
+
<div className="flex items-center justify-between">
|
173 |
+
<p className="text-sm font-medium text-primary-600 truncate">
|
174 |
+
Category: {result.category}
|
175 |
+
</p>
|
176 |
+
<div className="ml-2 flex-shrink-0 flex">
|
177 |
+
<p className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
178 |
+
{(result.confidence * 100).toFixed(2)}%
|
179 |
+
</p>
|
180 |
+
</div>
|
181 |
+
</div>
|
182 |
+
<div className="mt-2 sm:flex sm:justify-between">
|
183 |
+
<div className="sm:flex">
|
184 |
+
<p className="flex items-center text-sm text-gray-500">
|
185 |
+
{result.explanation}
|
186 |
+
</p>
|
187 |
+
</div>
|
188 |
+
</div>
|
189 |
+
</li>
|
190 |
+
))}
|
191 |
+
</ul>
|
192 |
+
</div>
|
193 |
+
</div>
|
194 |
+
)}
|
195 |
+
|
196 |
+
{suggestedCategories.length > 0 && (
|
197 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
198 |
+
<div className="px-4 py-5 sm:px-6">
|
199 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Suggested Categories</h3>
|
200 |
+
</div>
|
201 |
+
<div className="border-t border-gray-200">
|
202 |
+
<div className="px-4 py-5 sm:p-6">
|
203 |
+
<div className="flex flex-wrap gap-2">
|
204 |
+
{suggestedCategories.map((category, index) => (
|
205 |
+
<span
|
206 |
+
key={index}
|
207 |
+
className="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-primary-100 text-primary-800"
|
208 |
+
>
|
209 |
+
{category}
|
210 |
+
</span>
|
211 |
+
))}
|
212 |
+
</div>
|
213 |
+
</div>
|
214 |
+
</div>
|
215 |
+
</div>
|
216 |
+
)}
|
217 |
+
</div>
|
218 |
+
);
|
219 |
+
};
|
220 |
+
|
221 |
+
export default Classify;
|
frontend/src/components/Home.tsx
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState } from 'react';
|
2 |
+
import { healthCheck, getModelInfo } from '../api/api';
|
3 |
+
import { HealthResponse, ModelInfoResponse } from '../types/api';
|
4 |
+
|
5 |
+
const Home: React.FC = () => {
|
6 |
+
const [health, setHealth] = useState<HealthResponse | null>(null);
|
7 |
+
const [modelInfo, setModelInfo] = useState<ModelInfoResponse | null>(null);
|
8 |
+
const [loading, setLoading] = useState(true);
|
9 |
+
const [error, setError] = useState<string | null>(null);
|
10 |
+
|
11 |
+
useEffect(() => {
|
12 |
+
const fetchData = async () => {
|
13 |
+
try {
|
14 |
+
const [healthData, modelData] = await Promise.all([
|
15 |
+
healthCheck(),
|
16 |
+
getModelInfo()
|
17 |
+
]);
|
18 |
+
setHealth(healthData);
|
19 |
+
setModelInfo(modelData);
|
20 |
+
} catch (err) {
|
21 |
+
setError('Failed to fetch data from the server');
|
22 |
+
} finally {
|
23 |
+
setLoading(false);
|
24 |
+
}
|
25 |
+
};
|
26 |
+
|
27 |
+
fetchData();
|
28 |
+
}, []);
|
29 |
+
|
30 |
+
if (loading) {
|
31 |
+
return (
|
32 |
+
<div className="flex justify-center items-center h-64">
|
33 |
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500"></div>
|
34 |
+
</div>
|
35 |
+
);
|
36 |
+
}
|
37 |
+
|
38 |
+
if (error) {
|
39 |
+
return (
|
40 |
+
<div className="bg-red-50 border-l-4 border-red-400 p-4">
|
41 |
+
<div className="flex">
|
42 |
+
<div className="flex-shrink-0">
|
43 |
+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
44 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
45 |
+
</svg>
|
46 |
+
</div>
|
47 |
+
<div className="ml-3">
|
48 |
+
<p className="text-sm text-red-700">{error}</p>
|
49 |
+
</div>
|
50 |
+
</div>
|
51 |
+
</div>
|
52 |
+
);
|
53 |
+
}
|
54 |
+
|
55 |
+
return (
|
56 |
+
<div className="space-y-6">
|
57 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
58 |
+
<div className="px-4 py-5 sm:px-6">
|
59 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">System Status</h3>
|
60 |
+
</div>
|
61 |
+
<div className="border-t border-gray-200">
|
62 |
+
<dl>
|
63 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
64 |
+
<dt className="text-sm font-medium text-gray-500">Status</dt>
|
65 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
66 |
+
{health?.status}
|
67 |
+
</dd>
|
68 |
+
</div>
|
69 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
70 |
+
<dt className="text-sm font-medium text-gray-500">Model Ready</dt>
|
71 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
72 |
+
{health?.model_ready ? 'Yes' : 'No'}
|
73 |
+
</dd>
|
74 |
+
</div>
|
75 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
76 |
+
<dt className="text-sm font-medium text-gray-500">API Key Configured</dt>
|
77 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
78 |
+
{health?.api_key_configured ? 'Yes' : 'No'}
|
79 |
+
</dd>
|
80 |
+
</div>
|
81 |
+
</dl>
|
82 |
+
</div>
|
83 |
+
</div>
|
84 |
+
|
85 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
86 |
+
<div className="px-4 py-5 sm:px-6">
|
87 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Model Information</h3>
|
88 |
+
</div>
|
89 |
+
<div className="border-t border-gray-200">
|
90 |
+
<dl>
|
91 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
92 |
+
<dt className="text-sm font-medium text-gray-500">Model Name</dt>
|
93 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
94 |
+
{modelInfo?.model_name}
|
95 |
+
</dd>
|
96 |
+
</div>
|
97 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
98 |
+
<dt className="text-sm font-medium text-gray-500">Model Version</dt>
|
99 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
100 |
+
{modelInfo?.model_version}
|
101 |
+
</dd>
|
102 |
+
</div>
|
103 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
104 |
+
<dt className="text-sm font-medium text-gray-500">Max Tokens</dt>
|
105 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
106 |
+
{modelInfo?.max_tokens}
|
107 |
+
</dd>
|
108 |
+
</div>
|
109 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
110 |
+
<dt className="text-sm font-medium text-gray-500">Temperature</dt>
|
111 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
112 |
+
{modelInfo?.temperature}
|
113 |
+
</dd>
|
114 |
+
</div>
|
115 |
+
</dl>
|
116 |
+
</div>
|
117 |
+
</div>
|
118 |
+
</div>
|
119 |
+
);
|
120 |
+
};
|
121 |
+
|
122 |
+
export default Home;
|
frontend/src/components/Improve.tsx
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState } from 'react';
|
2 |
+
import { improveClassification } from '../api/api';
|
3 |
+
import { ImprovementRequest, ImprovementResponse } from '../types/api';
|
4 |
+
|
5 |
+
const Improve: React.FC = () => {
|
6 |
+
const [text, setText] = useState('');
|
7 |
+
const [categories, setCategories] = useState('');
|
8 |
+
const [validationReport, setValidationReport] = useState('');
|
9 |
+
const [improvementResult, setImprovementResult] = useState<ImprovementResponse | null>(null);
|
10 |
+
const [loading, setLoading] = useState(false);
|
11 |
+
const [error, setError] = useState<string | null>(null);
|
12 |
+
|
13 |
+
const handleImprove = async () => {
|
14 |
+
if (!text || !categories || !validationReport) return;
|
15 |
+
|
16 |
+
setLoading(true);
|
17 |
+
setError(null);
|
18 |
+
|
19 |
+
try {
|
20 |
+
const request: ImprovementRequest = {
|
21 |
+
df: { text: text.split('\n').filter(t => t.trim()) },
|
22 |
+
validation_report: validationReport,
|
23 |
+
text_columns: ['text'],
|
24 |
+
categories,
|
25 |
+
classifier_type: 'gpt35',
|
26 |
+
show_explanations: true,
|
27 |
+
file_path: 'examples/emails.csv'
|
28 |
+
};
|
29 |
+
|
30 |
+
const response = await improveClassification(request);
|
31 |
+
setImprovementResult(response);
|
32 |
+
} catch (err) {
|
33 |
+
setError('Failed to improve classifications');
|
34 |
+
} finally {
|
35 |
+
setLoading(false);
|
36 |
+
}
|
37 |
+
};
|
38 |
+
|
39 |
+
return (
|
40 |
+
<div className="space-y-6">
|
41 |
+
<div className="bg-white shadow sm:rounded-lg">
|
42 |
+
<div className="px-4 py-5 sm:p-6">
|
43 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Improve Classifications</h3>
|
44 |
+
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
45 |
+
<p>Enter text samples and validation report to improve classifications.</p>
|
46 |
+
</div>
|
47 |
+
<div className="mt-5 space-y-4">
|
48 |
+
<div>
|
49 |
+
<label htmlFor="text" className="block text-sm font-medium text-gray-700">
|
50 |
+
Text Samples
|
51 |
+
</label>
|
52 |
+
<div className="mt-1">
|
53 |
+
<textarea
|
54 |
+
id="text"
|
55 |
+
rows={4}
|
56 |
+
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
57 |
+
placeholder="Enter text samples (one per line)..."
|
58 |
+
value={text}
|
59 |
+
onChange={(e) => setText(e.target.value)}
|
60 |
+
/>
|
61 |
+
</div>
|
62 |
+
</div>
|
63 |
+
<div>
|
64 |
+
<label htmlFor="categories" className="block text-sm font-medium text-gray-700">
|
65 |
+
Categories
|
66 |
+
</label>
|
67 |
+
<div className="mt-1">
|
68 |
+
<input
|
69 |
+
type="text"
|
70 |
+
id="categories"
|
71 |
+
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
72 |
+
placeholder="Enter categories (comma-separated)"
|
73 |
+
value={categories}
|
74 |
+
onChange={(e) => setCategories(e.target.value)}
|
75 |
+
/>
|
76 |
+
</div>
|
77 |
+
</div>
|
78 |
+
<div>
|
79 |
+
<label htmlFor="validation-report" className="block text-sm font-medium text-gray-700">
|
80 |
+
Validation Report
|
81 |
+
</label>
|
82 |
+
<div className="mt-1">
|
83 |
+
<textarea
|
84 |
+
id="validation-report"
|
85 |
+
rows={4}
|
86 |
+
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
87 |
+
placeholder="Enter validation report..."
|
88 |
+
value={validationReport}
|
89 |
+
onChange={(e) => setValidationReport(e.target.value)}
|
90 |
+
/>
|
91 |
+
</div>
|
92 |
+
</div>
|
93 |
+
</div>
|
94 |
+
<div className="mt-5">
|
95 |
+
<button
|
96 |
+
type="button"
|
97 |
+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
98 |
+
onClick={handleImprove}
|
99 |
+
disabled={loading}
|
100 |
+
>
|
101 |
+
Improve
|
102 |
+
</button>
|
103 |
+
</div>
|
104 |
+
</div>
|
105 |
+
</div>
|
106 |
+
|
107 |
+
{loading && (
|
108 |
+
<div className="flex justify-center">
|
109 |
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500"></div>
|
110 |
+
</div>
|
111 |
+
)}
|
112 |
+
|
113 |
+
{error && (
|
114 |
+
<div className="bg-red-50 border-l-4 border-red-400 p-4">
|
115 |
+
<div className="flex">
|
116 |
+
<div className="flex-shrink-0">
|
117 |
+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
118 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
119 |
+
</svg>
|
120 |
+
</div>
|
121 |
+
<div className="ml-3">
|
122 |
+
<p className="text-sm text-red-700">{error}</p>
|
123 |
+
</div>
|
124 |
+
</div>
|
125 |
+
</div>
|
126 |
+
)}
|
127 |
+
|
128 |
+
{improvementResult && (
|
129 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
130 |
+
<div className="px-4 py-5 sm:px-6">
|
131 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Improvement Results</h3>
|
132 |
+
</div>
|
133 |
+
<div className="border-t border-gray-200">
|
134 |
+
<dl>
|
135 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
136 |
+
<dt className="text-sm font-medium text-gray-500">Success</dt>
|
137 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
138 |
+
{improvementResult.success ? 'Yes' : 'No'}
|
139 |
+
</dd>
|
140 |
+
</div>
|
141 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
142 |
+
<dt className="text-sm font-medium text-gray-500">New Validation Report</dt>
|
143 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
144 |
+
{improvementResult.new_validation_report}
|
145 |
+
</dd>
|
146 |
+
</div>
|
147 |
+
{improvementResult.updated_categories.length > 0 && (
|
148 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
149 |
+
<dt className="text-sm font-medium text-gray-500">Updated Categories</dt>
|
150 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
151 |
+
<div className="flex flex-wrap gap-2">
|
152 |
+
{improvementResult.updated_categories.map((category, index) => (
|
153 |
+
<span
|
154 |
+
key={index}
|
155 |
+
className="inline-flex items-center px-3 py-0.5 rounded-full text-sm font-medium bg-primary-100 text-primary-800"
|
156 |
+
>
|
157 |
+
{category}
|
158 |
+
</span>
|
159 |
+
))}
|
160 |
+
</div>
|
161 |
+
</dd>
|
162 |
+
</div>
|
163 |
+
)}
|
164 |
+
</dl>
|
165 |
+
</div>
|
166 |
+
</div>
|
167 |
+
)}
|
168 |
+
</div>
|
169 |
+
);
|
170 |
+
};
|
171 |
+
|
172 |
+
export default Improve;
|
frontend/src/components/Validate.tsx
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState } from 'react';
|
2 |
+
import { validateClassifications } from '../api/api';
|
3 |
+
import { ValidationRequest, ValidationResponse } from '../types/api';
|
4 |
+
|
5 |
+
const Validate: React.FC = () => {
|
6 |
+
const [text, setText] = useState('');
|
7 |
+
const [categories, setCategories] = useState<string[]>([]);
|
8 |
+
const [validationResult, setValidationResult] = useState<ValidationResponse | null>(null);
|
9 |
+
const [loading, setLoading] = useState(false);
|
10 |
+
const [error, setError] = useState<string | null>(null);
|
11 |
+
|
12 |
+
const handleValidate = async () => {
|
13 |
+
if (!text) return;
|
14 |
+
|
15 |
+
setLoading(true);
|
16 |
+
setError(null);
|
17 |
+
|
18 |
+
try {
|
19 |
+
const texts = text.split('\n').filter(t => t.trim());
|
20 |
+
const samples = texts.map(t => ({
|
21 |
+
text: t,
|
22 |
+
assigned_category: '', // This would be filled with actual classifications
|
23 |
+
confidence: 0
|
24 |
+
}));
|
25 |
+
|
26 |
+
const request: ValidationRequest = {
|
27 |
+
samples,
|
28 |
+
current_categories: categories,
|
29 |
+
text_columns: ['text']
|
30 |
+
};
|
31 |
+
|
32 |
+
const response = await validateClassifications(request);
|
33 |
+
setValidationResult(response);
|
34 |
+
} catch (err) {
|
35 |
+
setError('Failed to validate classifications');
|
36 |
+
} finally {
|
37 |
+
setLoading(false);
|
38 |
+
}
|
39 |
+
};
|
40 |
+
|
41 |
+
return (
|
42 |
+
<div className="space-y-6">
|
43 |
+
<div className="bg-white shadow sm:rounded-lg">
|
44 |
+
<div className="px-4 py-5 sm:p-6">
|
45 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Validate Classifications</h3>
|
46 |
+
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
47 |
+
<p>Enter text samples (one per line) to validate their classifications.</p>
|
48 |
+
</div>
|
49 |
+
<div className="mt-5">
|
50 |
+
<textarea
|
51 |
+
rows={4}
|
52 |
+
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
53 |
+
placeholder="Enter text samples..."
|
54 |
+
value={text}
|
55 |
+
onChange={(e) => setText(e.target.value)}
|
56 |
+
/>
|
57 |
+
</div>
|
58 |
+
<div className="mt-5">
|
59 |
+
<button
|
60 |
+
type="button"
|
61 |
+
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
62 |
+
onClick={handleValidate}
|
63 |
+
disabled={loading}
|
64 |
+
>
|
65 |
+
Validate
|
66 |
+
</button>
|
67 |
+
</div>
|
68 |
+
</div>
|
69 |
+
</div>
|
70 |
+
|
71 |
+
{loading && (
|
72 |
+
<div className="flex justify-center">
|
73 |
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500"></div>
|
74 |
+
</div>
|
75 |
+
)}
|
76 |
+
|
77 |
+
{error && (
|
78 |
+
<div className="bg-red-50 border-l-4 border-red-400 p-4">
|
79 |
+
<div className="flex">
|
80 |
+
<div className="flex-shrink-0">
|
81 |
+
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
82 |
+
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
83 |
+
</svg>
|
84 |
+
</div>
|
85 |
+
<div className="ml-3">
|
86 |
+
<p className="text-sm text-red-700">{error}</p>
|
87 |
+
</div>
|
88 |
+
</div>
|
89 |
+
</div>
|
90 |
+
)}
|
91 |
+
|
92 |
+
{validationResult && (
|
93 |
+
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
|
94 |
+
<div className="px-4 py-5 sm:px-6">
|
95 |
+
<h3 className="text-lg leading-6 font-medium text-gray-900">Validation Results</h3>
|
96 |
+
</div>
|
97 |
+
<div className="border-t border-gray-200">
|
98 |
+
<dl>
|
99 |
+
{validationResult.accuracy_score !== undefined && (
|
100 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
101 |
+
<dt className="text-sm font-medium text-gray-500">Accuracy Score</dt>
|
102 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
103 |
+
{(validationResult.accuracy_score * 100).toFixed(2)}%
|
104 |
+
</dd>
|
105 |
+
</div>
|
106 |
+
)}
|
107 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
108 |
+
<dt className="text-sm font-medium text-gray-500">Validation Report</dt>
|
109 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
110 |
+
{validationResult.validation_report}
|
111 |
+
</dd>
|
112 |
+
</div>
|
113 |
+
{validationResult.misclassifications && validationResult.misclassifications.length > 0 && (
|
114 |
+
<div className="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
115 |
+
<dt className="text-sm font-medium text-gray-500">Misclassifications</dt>
|
116 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
117 |
+
<ul className="border border-gray-200 rounded-md divide-y divide-gray-200">
|
118 |
+
{validationResult.misclassifications.map((item, index) => (
|
119 |
+
<li key={index} className="pl-3 pr-4 py-3 flex items-center justify-between text-sm">
|
120 |
+
<div className="w-0 flex-1 flex items-center">
|
121 |
+
<span className="ml-2 flex-1 w-0 truncate">
|
122 |
+
{item.text}
|
123 |
+
</span>
|
124 |
+
</div>
|
125 |
+
<div className="ml-4 flex-shrink-0">
|
126 |
+
<span className="text-gray-500">
|
127 |
+
Current: {item.current_category}
|
128 |
+
</span>
|
129 |
+
</div>
|
130 |
+
</li>
|
131 |
+
))}
|
132 |
+
</ul>
|
133 |
+
</dd>
|
134 |
+
</div>
|
135 |
+
)}
|
136 |
+
{validationResult.suggested_improvements && validationResult.suggested_improvements.length > 0 && (
|
137 |
+
<div className="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
138 |
+
<dt className="text-sm font-medium text-gray-500">Suggested Improvements</dt>
|
139 |
+
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
140 |
+
<ul className="list-disc pl-5 space-y-1">
|
141 |
+
{validationResult.suggested_improvements.map((improvement, index) => (
|
142 |
+
<li key={index}>{improvement}</li>
|
143 |
+
))}
|
144 |
+
</ul>
|
145 |
+
</dd>
|
146 |
+
</div>
|
147 |
+
)}
|
148 |
+
</dl>
|
149 |
+
</div>
|
150 |
+
</div>
|
151 |
+
)}
|
152 |
+
</div>
|
153 |
+
);
|
154 |
+
};
|
155 |
+
|
156 |
+
export default Validate;
|
frontend/src/index.css
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
body {
|
6 |
+
margin: 0;
|
7 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
8 |
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
9 |
+
sans-serif;
|
10 |
+
-webkit-font-smoothing: antialiased;
|
11 |
+
-moz-osx-font-smoothing: grayscale;
|
12 |
+
}
|
13 |
+
|
14 |
+
code {
|
15 |
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
16 |
+
monospace;
|
17 |
+
}
|
frontend/src/index.tsx
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react';
|
2 |
+
import ReactDOM from 'react-dom/client';
|
3 |
+
import './index.css';
|
4 |
+
import App from './App';
|
5 |
+
|
6 |
+
const root = ReactDOM.createRoot(
|
7 |
+
document.getElementById('root') as HTMLElement
|
8 |
+
);
|
9 |
+
|
10 |
+
root.render(
|
11 |
+
<React.StrictMode>
|
12 |
+
<App />
|
13 |
+
</React.StrictMode>
|
14 |
+
);
|
frontend/src/types/api.ts
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface ClassificationResponse {
|
2 |
+
category: string;
|
3 |
+
confidence: number;
|
4 |
+
explanation: string;
|
5 |
+
}
|
6 |
+
|
7 |
+
export interface BatchClassificationResponse {
|
8 |
+
results: ClassificationResponse[];
|
9 |
+
}
|
10 |
+
|
11 |
+
export interface CategorySuggestionResponse {
|
12 |
+
categories: string[];
|
13 |
+
}
|
14 |
+
|
15 |
+
export interface ValidationSample {
|
16 |
+
text: string;
|
17 |
+
assigned_category: string;
|
18 |
+
confidence: number;
|
19 |
+
}
|
20 |
+
|
21 |
+
export interface ValidationRequest {
|
22 |
+
samples: ValidationSample[];
|
23 |
+
current_categories: string[];
|
24 |
+
text_columns: string[];
|
25 |
+
}
|
26 |
+
|
27 |
+
export interface ValidationResponse {
|
28 |
+
validation_report: string;
|
29 |
+
accuracy_score?: number;
|
30 |
+
misclassifications?: Array<{
|
31 |
+
text: string;
|
32 |
+
current_category: string;
|
33 |
+
}>;
|
34 |
+
suggested_improvements?: string[];
|
35 |
+
}
|
36 |
+
|
37 |
+
export interface ImprovementRequest {
|
38 |
+
df: Record<string, any>;
|
39 |
+
validation_report: string;
|
40 |
+
text_columns: string[];
|
41 |
+
categories: string;
|
42 |
+
classifier_type: string;
|
43 |
+
show_explanations: boolean;
|
44 |
+
file_path: string;
|
45 |
+
}
|
46 |
+
|
47 |
+
export interface ImprovementResponse {
|
48 |
+
improved_df: Record<string, any>;
|
49 |
+
new_validation_report: string;
|
50 |
+
success: boolean;
|
51 |
+
updated_categories: string[];
|
52 |
+
}
|
53 |
+
|
54 |
+
export interface ModelInfoResponse {
|
55 |
+
model_name: string;
|
56 |
+
model_version: string;
|
57 |
+
max_tokens: number;
|
58 |
+
temperature: number;
|
59 |
+
}
|
60 |
+
|
61 |
+
export interface HealthResponse {
|
62 |
+
status: string;
|
63 |
+
model_ready: boolean;
|
64 |
+
api_key_configured: boolean;
|
65 |
+
}
|
frontend/tailwind.config.js
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
content: [
|
3 |
+
"./src/**/*.{js,jsx,ts,tsx}",
|
4 |
+
],
|
5 |
+
theme: {
|
6 |
+
extend: {
|
7 |
+
colors: {
|
8 |
+
primary: {
|
9 |
+
50: '#f0f9ff',
|
10 |
+
100: '#e0f2fe',
|
11 |
+
200: '#bae6fd',
|
12 |
+
300: '#7dd3fc',
|
13 |
+
400: '#38bdf8',
|
14 |
+
500: '#0ea5e9',
|
15 |
+
600: '#0284c7',
|
16 |
+
700: '#0369a1',
|
17 |
+
800: '#075985',
|
18 |
+
900: '#0c4a6e',
|
19 |
+
},
|
20 |
+
},
|
21 |
+
},
|
22 |
+
},
|
23 |
+
plugins: [],
|
24 |
+
}
|
frontend/tsconfig.json
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "es5",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"esModuleInterop": true,
|
8 |
+
"allowSyntheticDefaultImports": true,
|
9 |
+
"strict": true,
|
10 |
+
"forceConsistentCasingInFileNames": true,
|
11 |
+
"noFallthroughCasesInSwitch": true,
|
12 |
+
"module": "esnext",
|
13 |
+
"moduleResolution": "node",
|
14 |
+
"resolveJsonModule": true,
|
15 |
+
"isolatedModules": true,
|
16 |
+
"noEmit": true,
|
17 |
+
"jsx": "react-jsx"
|
18 |
+
},
|
19 |
+
"include": ["src"]
|
20 |
+
}
|