simondh commited on
Commit
73d286b
·
1 Parent(s): cc651f6
.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
+ }