Spaces:
Sleeping
Sleeping
Commit
·
d6791c3
0
Parent(s):
Initial commit with all files for TexClarity
Browse files- Dockerfile +17 -0
- README.md +11 -0
- app.py +45 -0
- requirements.txt +3 -0
- static/index.html +309 -0
Dockerfile
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use the official Python image as the base
|
2 |
+
FROM python:3.9-slim
|
3 |
+
|
4 |
+
# Set the working directory inside the container
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Copy all project files to the container
|
8 |
+
COPY . /app
|
9 |
+
|
10 |
+
# Install the required Python packages
|
11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
12 |
+
|
13 |
+
# Expose the port that FastAPI will run on
|
14 |
+
EXPOSE 7860
|
15 |
+
|
16 |
+
# Command to run the FastAPI application
|
17 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# TexClarity
|
2 |
+
|
3 |
+
TexClarity is a text classification app that uses zero-shot classification to categorize text into user-defined labels.
|
4 |
+
|
5 |
+
## How to Use
|
6 |
+
1. Enter your text in the textarea.
|
7 |
+
2. Provide labels (comma-separated) in the labels field.
|
8 |
+
3. Click "Classify" to see the results in a table and chart.
|
9 |
+
|
10 |
+
## Setup
|
11 |
+
This Space uses Docker to run a FastAPI backend with a static frontend.
|
app.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, Request
|
2 |
+
from fastapi.responses import HTMLResponse
|
3 |
+
from fastapi.staticfiles import StaticFiles
|
4 |
+
from fastapi.templating import Jinja2Templates
|
5 |
+
from transformers import pipeline
|
6 |
+
import os
|
7 |
+
import uvicorn
|
8 |
+
|
9 |
+
app = FastAPI()
|
10 |
+
|
11 |
+
# Mount the templates directory for serving HTML
|
12 |
+
templates = Jinja2Templates(directory="templates")
|
13 |
+
|
14 |
+
# Load the zero-shot classification model
|
15 |
+
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
|
16 |
+
|
17 |
+
# Route to serve index.html
|
18 |
+
@app.get("/", response_class=HTMLResponse)
|
19 |
+
async def index(request: Request):
|
20 |
+
return templates.TemplateResponse("index.html", {"request": request})
|
21 |
+
|
22 |
+
# Route to handle text classification requests
|
23 |
+
@app.post("/classify")
|
24 |
+
async def classify_text(data: dict):
|
25 |
+
try:
|
26 |
+
text = data.get("document")
|
27 |
+
labels = data.get("labels")
|
28 |
+
|
29 |
+
if not text or not labels:
|
30 |
+
return {"error": "Please provide both text and labels"}, 400
|
31 |
+
|
32 |
+
# Perform classification
|
33 |
+
result = classifier(text, labels, multi_label=False)
|
34 |
+
response = {
|
35 |
+
"labels": result["labels"],
|
36 |
+
"scores": result["scores"]
|
37 |
+
}
|
38 |
+
return response, 200
|
39 |
+
except Exception as e:
|
40 |
+
return {"error": str(e)}, 500
|
41 |
+
|
42 |
+
# Run the app on HF中国镜像站's required port
|
43 |
+
if __name__ == "__main__":
|
44 |
+
port = int(os.environ.get("PORT", 7860))
|
45 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
fastapi
|
2 |
+
uvicorn
|
3 |
+
transformers
|
static/index.html
ADDED
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>TexClarity</title>
|
7 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
8 |
+
<link href="https://fonts.googleapis.com/css2?family=Griffy&display=swap" rel="stylesheet">
|
9 |
+
<style>
|
10 |
+
body {
|
11 |
+
font-family: Arial, sans-serif;
|
12 |
+
background: linear-gradient(135deg, #000, #1a1a2e);
|
13 |
+
color: #fff;
|
14 |
+
margin: 0;
|
15 |
+
padding: 20px;
|
16 |
+
display: flex;
|
17 |
+
justify-content: center;
|
18 |
+
min-height: 100vh; /* Ensure background covers full height */
|
19 |
+
}
|
20 |
+
.container {
|
21 |
+
max-width: 1000px;
|
22 |
+
width: 100%;
|
23 |
+
}
|
24 |
+
h1 {
|
25 |
+
font-family: 'Griffy', cursive;
|
26 |
+
font-size: 2em;
|
27 |
+
margin-bottom: 0;
|
28 |
+
margin-top: 10px;
|
29 |
+
position: absolute;
|
30 |
+
top: 20px;
|
31 |
+
left: 20px;
|
32 |
+
color: #fff;
|
33 |
+
text-align: left;
|
34 |
+
box-shadow: 0 0 5px rgba(78, 121, 230, 0.2);
|
35 |
+
padding: 5px 10px;
|
36 |
+
border-radius: 5px;
|
37 |
+
animation: glow 2s infinite;
|
38 |
+
}
|
39 |
+
@keyframes glow {
|
40 |
+
0% { box-shadow: 0 0 5px rgba(78, 121, 230, 0.2); }
|
41 |
+
50% { box-shadow: 0 0 8px rgba(78, 121, 230, 0.4); }
|
42 |
+
100% { box-shadow: 0 0 5px rgba(78, 121, 230, 0.2); }
|
43 |
+
}
|
44 |
+
h2 {
|
45 |
+
font-family: 'Verdana', sans-serif;
|
46 |
+
font-size: 1.8em;
|
47 |
+
font-weight: normal;
|
48 |
+
margin-top: 30px;
|
49 |
+
}
|
50 |
+
h1, h2 {
|
51 |
+
color: #fff;
|
52 |
+
text-align: left;
|
53 |
+
}
|
54 |
+
.input-section {
|
55 |
+
margin-top: 60px;
|
56 |
+
margin-bottom: 20px;
|
57 |
+
}
|
58 |
+
textarea {
|
59 |
+
width: 100%;
|
60 |
+
height: 250px;
|
61 |
+
padding: 15px;
|
62 |
+
margin-bottom: 10px;
|
63 |
+
border: none;
|
64 |
+
border-radius: 8px;
|
65 |
+
background: #e0e0e0;
|
66 |
+
color: #333;
|
67 |
+
font-size: 1.2em;
|
68 |
+
box-sizing: border-box;
|
69 |
+
box-shadow: 0 0 10px rgba(78, 121, 230, 0.3);
|
70 |
+
transition: box-shadow 0.3s;
|
71 |
+
}
|
72 |
+
textarea:focus {
|
73 |
+
box-shadow: 0 0 15px rgba(78, 121, 230, 0.6);
|
74 |
+
}
|
75 |
+
.labels-section {
|
76 |
+
display: flex;
|
77 |
+
align-items: center;
|
78 |
+
gap: 10px;
|
79 |
+
margin-bottom: 10px;
|
80 |
+
}
|
81 |
+
.labels-section label {
|
82 |
+
font-size: 1.2em;
|
83 |
+
}
|
84 |
+
input {
|
85 |
+
flex: 2;
|
86 |
+
padding: 15px;
|
87 |
+
border: 2px solid #4e79e6;
|
88 |
+
border-radius: 8px;
|
89 |
+
background: #e0e0e0;
|
90 |
+
color: #333;
|
91 |
+
font-size: 1.2em;
|
92 |
+
box-shadow: 0 0 8px rgba(78, 121, 230, 0.3);
|
93 |
+
transition: box-shadow 0.3s;
|
94 |
+
}
|
95 |
+
input:focus {
|
96 |
+
box-shadow: 0 0 8px rgba(78, 121, 230, 0.5);
|
97 |
+
}
|
98 |
+
button {
|
99 |
+
background: linear-gradient(135deg, #2c5aa0, #4e79e6);
|
100 |
+
color: #fff;
|
101 |
+
padding: 10px 20px;
|
102 |
+
border: none;
|
103 |
+
border-radius: 8px;
|
104 |
+
cursor: pointer;
|
105 |
+
font-size: 1.1em;
|
106 |
+
box-shadow: 0 0 15px rgba(78, 121, 230, 0.5);
|
107 |
+
animation: pulse 2s infinite;
|
108 |
+
transition: transform 0.2s;
|
109 |
+
}
|
110 |
+
button:hover {
|
111 |
+
transform: scale(1.05);
|
112 |
+
box-shadow: 0 0 20px rgba(78, 121, 230, 0.8);
|
113 |
+
}
|
114 |
+
@keyframes pulse {
|
115 |
+
0% { box-shadow: 0 0 15px rgba(78, 121, 230, 0.5); }
|
116 |
+
50% { box-shadow: 0 0 25px rgba(78, 121, 230, 0.8); }
|
117 |
+
100% { box-shadow: 0 0 15px rgba(78, 121, 230, 0.5); }
|
118 |
+
}
|
119 |
+
.results {
|
120 |
+
display: flex;
|
121 |
+
flex-direction: column;
|
122 |
+
gap: 20px;
|
123 |
+
}
|
124 |
+
table {
|
125 |
+
width: 100%;
|
126 |
+
border-collapse: collapse;
|
127 |
+
background: linear-gradient(135deg, #000, #1a1a2e); /* Match body background */
|
128 |
+
border-radius: 8px;
|
129 |
+
overflow: hidden;
|
130 |
+
}
|
131 |
+
th, td {
|
132 |
+
padding: 12px;
|
133 |
+
text-align: left;
|
134 |
+
color: #fff; /* White text for contrast with dark background */
|
135 |
+
}
|
136 |
+
th {
|
137 |
+
font-weight: bold;
|
138 |
+
}
|
139 |
+
.chart-container {
|
140 |
+
display: none;
|
141 |
+
position: relative;
|
142 |
+
height: 300px;
|
143 |
+
background: linear-gradient(135deg, #000, #1a1a2e); /* Match body background */
|
144 |
+
border-radius: 8px;
|
145 |
+
padding: 10px;
|
146 |
+
}
|
147 |
+
.loading {
|
148 |
+
position: fixed;
|
149 |
+
top: 50%;
|
150 |
+
left: 50%;
|
151 |
+
transform: translate(-50%, -50%);
|
152 |
+
color: #4e79e6;
|
153 |
+
font-size: 3.5em;
|
154 |
+
display: none;
|
155 |
+
}
|
156 |
+
.loading::after {
|
157 |
+
content: '...';
|
158 |
+
animation: dots 1.5s infinite;
|
159 |
+
}
|
160 |
+
@keyframes dots {
|
161 |
+
0% { content: '.'; }
|
162 |
+
33% { content: '..'; }
|
163 |
+
66% { content: '...'; }
|
164 |
+
}
|
165 |
+
@media (max-width: 768px) {
|
166 |
+
.container {
|
167 |
+
padding: 10px;
|
168 |
+
}
|
169 |
+
textarea, input, button {
|
170 |
+
font-size: 1em;
|
171 |
+
}
|
172 |
+
.labels-section {
|
173 |
+
flex-direction: column;
|
174 |
+
align-items: flex-start;
|
175 |
+
}
|
176 |
+
button {
|
177 |
+
width: 100%;
|
178 |
+
}
|
179 |
+
h1 {
|
180 |
+
font-size: 1.5em;
|
181 |
+
top: 10px;
|
182 |
+
left: 10px;
|
183 |
+
}
|
184 |
+
h2 {
|
185 |
+
margin-top: 20px;
|
186 |
+
font-size: 1.4em;
|
187 |
+
}
|
188 |
+
.input-section {
|
189 |
+
margin-top: 40px;
|
190 |
+
}
|
191 |
+
.loading {
|
192 |
+
font-size: 2.5em;
|
193 |
+
}
|
194 |
+
}
|
195 |
+
</style>
|
196 |
+
</head>
|
197 |
+
<body>
|
198 |
+
<div class="container">
|
199 |
+
<h1>TexClarity</h1>
|
200 |
+
<h2>Make your text analysis clearer</h2>
|
201 |
+
<div class="input-section">
|
202 |
+
<textarea id="document" placeholder="Enter your text here...">The fog hung low over the cobblestone streets of Victorian London, a shroud of mystery that seemed to whisper secrets in every shadowed corner. Eleanor, a young seamstress with a keen eye for detail, hurried through the damp alleys, her heart pounding as she clutched the letter in her trembling hands. It was no ordinary letter—it bore the seal of the elusive Lord Ashbourne, a man rumored to have ties to both the royal court and the city’s darkest underbelly. She had been summoned to his estate at midnight, an invitation that felt more like a command. The air grew colder as she approached the towering iron gates, their creak echoing like a warning. Inside, the grand hall was dimly lit, with portraits of stern ancestors glaring down as if they knew her every sin. Lord Ashbourne awaited her, his piercing gaze cutting through the gloom. "You have something I need," he said, his voice a low growl, "and I have something you want—your brother’s freedom." Eleanor’s breath caught in her throat. Her brother, Thomas, had vanished weeks ago, and whispers of his involvement in a smuggling ring had reached her ears. Could she trust this man, or was she stepping into a trap?</textarea>
|
203 |
+
<div class="labels-section">
|
204 |
+
<label>Labels:</label>
|
205 |
+
<input type="text" id="labels" placeholder="Enter labels (comma-separated)" value="mystery, drama, fantasy, history">
|
206 |
+
<button onclick="classifyText()">Classify</button>
|
207 |
+
</div>
|
208 |
+
</div>
|
209 |
+
<div class="results">
|
210 |
+
<table id="results-table">
|
211 |
+
<thead>
|
212 |
+
<tr>
|
213 |
+
<th>Label</th>
|
214 |
+
<th>Score</th>
|
215 |
+
</tr>
|
216 |
+
</thead>
|
217 |
+
<tbody></tbody>
|
218 |
+
</table>
|
219 |
+
<div class="chart-container">
|
220 |
+
<canvas id="resultsChart"></canvas>
|
221 |
+
</div>
|
222 |
+
</div>
|
223 |
+
</div>
|
224 |
+
<div id="loading" class="loading"></div>
|
225 |
+
|
226 |
+
<script>
|
227 |
+
let chartInstance = null;
|
228 |
+
|
229 |
+
const textarea = document.getElementById('document');
|
230 |
+
textarea.addEventListener('focus', function() {
|
231 |
+
if (this.value === this.getAttribute('placeholder')) {
|
232 |
+
this.value = '';
|
233 |
+
}
|
234 |
+
});
|
235 |
+
textarea.addEventListener('blur', function() {
|
236 |
+
if (!this.value) {
|
237 |
+
this.value = this.getAttribute('placeholder');
|
238 |
+
}
|
239 |
+
});
|
240 |
+
|
241 |
+
async function classifyText() {
|
242 |
+
const textInput = document.getElementById('document').value;
|
243 |
+
const labelsInput = document.getElementById('labels').value;
|
244 |
+
const labels = labelsInput.split(',').map(l => l.trim()).filter(l => l);
|
245 |
+
const loadingElement = document.getElementById('loading');
|
246 |
+
const chartContainer = document.querySelector('.chart-container');
|
247 |
+
|
248 |
+
if (!textInput || textInput === textarea.getAttribute('placeholder') || !labels.length) {
|
249 |
+
alert('Please enter both text and labels');
|
250 |
+
return;
|
251 |
+
}
|
252 |
+
|
253 |
+
loadingElement.style.display = 'block';
|
254 |
+
chartContainer.style.display = 'none';
|
255 |
+
|
256 |
+
try {
|
257 |
+
const response = await fetch('/classify', { // Updated to relative path for FastAPI
|
258 |
+
method: 'POST',
|
259 |
+
headers: { 'Content-Type': 'application/json' },
|
260 |
+
body: JSON.stringify({ document: textInput, labels })
|
261 |
+
});
|
262 |
+
|
263 |
+
if (!response.ok) throw new Error('Classification failed');
|
264 |
+
|
265 |
+
const results = await response.json();
|
266 |
+
updateTable(results);
|
267 |
+
updateChart(results);
|
268 |
+
chartContainer.style.display = 'block';
|
269 |
+
} catch (error) {
|
270 |
+
alert('Error: ' + error.message);
|
271 |
+
} finally {
|
272 |
+
loadingElement.style.display = 'none';
|
273 |
+
}
|
274 |
+
}
|
275 |
+
|
276 |
+
function updateTable(results) {
|
277 |
+
const tbody = document.querySelector('#results-table tbody');
|
278 |
+
tbody.innerHTML = '';
|
279 |
+
results.labels.forEach((label, index) => {
|
280 |
+
const row = document.createElement('tr');
|
281 |
+
row.innerHTML = `<td>${label}</td><td>${results.scores[index].toFixed(3)}</td>`;
|
282 |
+
tbody.appendChild(row);
|
283 |
+
});
|
284 |
+
}
|
285 |
+
|
286 |
+
function updateChart(results) {
|
287 |
+
const ctx = document.getElementById('resultsChart').getContext('2d');
|
288 |
+
if (chartInstance) chartInstance.destroy();
|
289 |
+
chartInstance = new Chart(ctx, {
|
290 |
+
type: 'bar',
|
291 |
+
data: {
|
292 |
+
labels: results.labels,
|
293 |
+
datasets: [{
|
294 |
+
label: 'Scores',
|
295 |
+
data: results.scores,
|
296 |
+
backgroundColor: '#4e79e6',
|
297 |
+
borderColor: '#6a8de9',
|
298 |
+
borderWidth: 1
|
299 |
+
}]
|
300 |
+
},
|
301 |
+
options: {
|
302 |
+
scales: { y: { beginAtZero: true, max: 1 } },
|
303 |
+
plugins: { legend: { display: false } }
|
304 |
+
}
|
305 |
+
});
|
306 |
+
}
|
307 |
+
</script>
|
308 |
+
</body>
|
309 |
+
</html>
|