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 |
+
}
|