gni commited on
Commit
15f5e73
·
1 Parent(s): d4f8d42

feat: Initial release of Redac - PII moderation MVP

Browse files

- Consolidated project structure (API + UI).
- Removed redundant CLI tools.
- Updated Docker configuration for dev/prod environments.
- Added MIT License and updated documentation.
- Renamed project to Redac.

.gitignore CHANGED
@@ -33,14 +33,12 @@ coverage.xml
33
  # --- Node.js ---
34
  node_modules/
35
  ui/node_modules/
36
- cli-ts/node_modules/
37
  npm-debug.log*
38
  yarn-debug.log*
39
  yarn-error.log*
40
  .pnpm-debug.log*
41
  .npm
42
  ui/dist/
43
- cli-ts/dist/
44
  .vite/
45
 
46
  # --- Docker ---
 
33
  # --- Node.js ---
34
  node_modules/
35
  ui/node_modules/
 
36
  npm-debug.log*
37
  yarn-debug.log*
38
  yarn-error.log*
39
  .pnpm-debug.log*
40
  .npm
41
  ui/dist/
 
42
  .vite/
43
 
44
  # --- Docker ---
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Redac_Scan Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🛡️ Redac_Scan
2
 
3
  A lightweight PII (Personally Identifiable Information) moderation MVP designed to sanitize sensitive data before it reaches LLM APIs.
4
 
@@ -10,7 +10,6 @@ A lightweight PII (Personally Identifiable Information) moderation MVP designed
10
  - **Balanced Anonymization**: Preserves job titles and document structure to keep texts readable.
11
  - **Minimal Dashboard**: React-based UI with Risk Assessment visualization.
12
  - **Custom Theme UI**: Switch between **Premium**, **Minimal Light**, and **Deep Midnight** modes.
13
- - **Dual CLI**: Tooling available in both **Python** and **TypeScript**.
14
 
15
  ---
16
 
@@ -18,9 +17,6 @@ A lightweight PII (Personally Identifiable Information) moderation MVP designed
18
 
19
  1. **Core API (`/api`)**: FastAPI server powered by **Microsoft Presidio**.
20
  2. **Web Dashboard (`/ui`)**: React + Vite + Tailwind CSS.
21
- 3. **CLI Tools**:
22
- - **Python (`/cli`)**
23
- - **TypeScript (`/cli-ts`)**
24
 
25
  ---
26
 
@@ -33,18 +29,6 @@ docker compose up --build
33
  - **API**: `http://localhost:8000`
34
  - **UI Dashboard**: `http://localhost:5173`
35
 
36
- ### Usage Examples:
37
-
38
- **CLI (Python):**
39
- ```bash
40
- docker compose run cli redact "Hello, my name is John Doe"
41
- ```
42
-
43
- **CLI (TypeScript):**
44
- ```bash
45
- docker compose run cli-ts redact "Contact me at 06 12 34 56 78"
46
- ```
47
-
48
  ---
49
 
50
  ## 🧪 Quality Assurance
 
1
+ # 🛡️ Redac
2
 
3
  A lightweight PII (Personally Identifiable Information) moderation MVP designed to sanitize sensitive data before it reaches LLM APIs.
4
 
 
10
  - **Balanced Anonymization**: Preserves job titles and document structure to keep texts readable.
11
  - **Minimal Dashboard**: React-based UI with Risk Assessment visualization.
12
  - **Custom Theme UI**: Switch between **Premium**, **Minimal Light**, and **Deep Midnight** modes.
 
13
 
14
  ---
15
 
 
17
 
18
  1. **Core API (`/api`)**: FastAPI server powered by **Microsoft Presidio**.
19
  2. **Web Dashboard (`/ui`)**: React + Vite + Tailwind CSS.
 
 
 
20
 
21
  ---
22
 
 
29
  - **API**: `http://localhost:8000`
30
  - **UI Dashboard**: `http://localhost:5173`
31
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  ---
33
 
34
  ## 🧪 Quality Assurance
api/Dockerfile CHANGED
@@ -13,9 +13,8 @@ RUN apt-get update && apt-get install -y \
13
  COPY requirements.txt .
14
  RUN pip install --no-cache-dir -r requirements.txt
15
 
16
- # Copy logic and setup script
17
- COPY main.py .
18
- COPY setup_models.py .
19
 
20
  EXPOSE 8000
21
 
 
13
  COPY requirements.txt .
14
  RUN pip install --no-cache-dir -r requirements.txt
15
 
16
+ # Copy all project files
17
+ COPY . .
 
18
 
19
  EXPOSE 8000
20
 
cli-ts/Dockerfile DELETED
@@ -1,16 +0,0 @@
1
- # CLI TS Dockerfile
2
- FROM node:25-slim
3
-
4
- WORKDIR /app
5
-
6
- COPY package*.json ./
7
- RUN npm install
8
-
9
- COPY . .
10
- RUN npm run build
11
-
12
- # Set executable permissions
13
- RUN chmod +x dist/index.js
14
-
15
- # Remove ENTRYPOINT to allow docker-compose command to run sh
16
- CMD ["node", "--no-warnings", "dist/index.js"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cli-ts/index.ts DELETED
@@ -1,66 +0,0 @@
1
- import { Command } from 'commander';
2
- import axios from 'axios';
3
- import chalk from 'chalk';
4
- import * as fs from 'fs';
5
-
6
- const program = new Command();
7
- const API_URL = process.env.API_URL || 'http://localhost:8000';
8
-
9
- program
10
- .name('pii-ts')
11
- .description('TypeScript PII Moderator CLI')
12
- .version('1.0.0');
13
-
14
- program
15
- .command('redact')
16
- .description('Redact PII from a string')
17
- .argument('<text>', 'The text to redact')
18
- .action(async (text: string) => {
19
- try {
20
- const response = await axios.post(`${API_URL}/redact`, { text });
21
- const { original_text, redacted_text, detected_entities } = response.data;
22
-
23
- console.log(chalk.cyan('\nOriginal:'));
24
- console.log(original_text);
25
-
26
- console.log(chalk.green('\nRedacted:'));
27
- console.log(redacted_text);
28
-
29
- if (detected_entities && detected_entities.length > 0) {
30
- console.log(chalk.yellow('\nDetected Entities:'));
31
- detected_entities.forEach((ent: any) => {
32
- console.log(`- ${ent.entity_type} (Score: ${ent.score.toFixed(2)})`);
33
- });
34
- }
35
- } catch (error: any) {
36
- console.error(chalk.red(`Error: ${error.message}`));
37
- process.exit(1);
38
- }
39
- });
40
-
41
- program
42
- .command('redact-file')
43
- .description('Redact PII from a file')
44
- .argument('<path>', 'Path to the file')
45
- .action(async (path: string) => {
46
- try {
47
- if (!fs.existsSync(path)) {
48
- console.error(chalk.red(`Error: File not found at ${path}`));
49
- process.exit(1);
50
- }
51
- const content = fs.readFileSync(path, 'utf-8');
52
- const response = await axios.post(`${API_URL}/redact`, { text: content });
53
- console.log(response.data.redacted_text);
54
- } catch (error: any) {
55
- console.error(chalk.red(`Error: ${error.message}`));
56
- process.exit(1);
57
- }
58
- });
59
-
60
- // Handle empty args without error
61
- if (process.argv.length <= 2) {
62
- program.outputHelp();
63
- process.exit(0);
64
- }
65
-
66
- program.parse(process.argv);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cli-ts/package-lock.json DELETED
@@ -1,560 +0,0 @@
1
- {
2
- "name": "cli-ts",
3
- "version": "1.0.0",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "cli-ts",
9
- "version": "1.0.0",
10
- "license": "ISC",
11
- "dependencies": {
12
- "axios": "^1.13.6",
13
- "chalk": "^5.6.2",
14
- "commander": "^14.0.3"
15
- },
16
- "devDependencies": {
17
- "@types/axios": "^0.9.36",
18
- "@types/commander": "^2.12.0",
19
- "@types/node": "^25.5.0",
20
- "ts-node": "^10.9.2",
21
- "typescript": "^5.9.3"
22
- }
23
- },
24
- "node_modules/@cspotcode/source-map-support": {
25
- "version": "0.8.1",
26
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
27
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
28
- "dev": true,
29
- "license": "MIT",
30
- "dependencies": {
31
- "@jridgewell/trace-mapping": "0.3.9"
32
- },
33
- "engines": {
34
- "node": ">=12"
35
- }
36
- },
37
- "node_modules/@jridgewell/resolve-uri": {
38
- "version": "3.1.2",
39
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
40
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
41
- "dev": true,
42
- "license": "MIT",
43
- "engines": {
44
- "node": ">=6.0.0"
45
- }
46
- },
47
- "node_modules/@jridgewell/sourcemap-codec": {
48
- "version": "1.5.5",
49
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
50
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
51
- "dev": true,
52
- "license": "MIT"
53
- },
54
- "node_modules/@jridgewell/trace-mapping": {
55
- "version": "0.3.9",
56
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
57
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
58
- "dev": true,
59
- "license": "MIT",
60
- "dependencies": {
61
- "@jridgewell/resolve-uri": "^3.0.3",
62
- "@jridgewell/sourcemap-codec": "^1.4.10"
63
- }
64
- },
65
- "node_modules/@tsconfig/node10": {
66
- "version": "1.0.12",
67
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
68
- "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
69
- "dev": true,
70
- "license": "MIT"
71
- },
72
- "node_modules/@tsconfig/node12": {
73
- "version": "1.0.11",
74
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
75
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
76
- "dev": true,
77
- "license": "MIT"
78
- },
79
- "node_modules/@tsconfig/node14": {
80
- "version": "1.0.3",
81
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
82
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
83
- "dev": true,
84
- "license": "MIT"
85
- },
86
- "node_modules/@tsconfig/node16": {
87
- "version": "1.0.4",
88
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
89
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
90
- "dev": true,
91
- "license": "MIT"
92
- },
93
- "node_modules/@types/axios": {
94
- "version": "0.9.36",
95
- "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.9.36.tgz",
96
- "integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==",
97
- "dev": true,
98
- "license": "MIT"
99
- },
100
- "node_modules/@types/commander": {
101
- "version": "2.12.0",
102
- "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.0.tgz",
103
- "integrity": "sha512-DDmRkovH7jPjnx7HcbSnqKg2JeNANyxNZeUvB0iE+qKBLN+vzN5iSIwt+J2PFSmBuYEut4mgQvI/fTX9YQH/vw==",
104
- "dev": true,
105
- "license": "MIT",
106
- "dependencies": {
107
- "commander": "*"
108
- }
109
- },
110
- "node_modules/@types/node": {
111
- "version": "25.5.0",
112
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
113
- "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
114
- "dev": true,
115
- "license": "MIT",
116
- "dependencies": {
117
- "undici-types": "~7.18.0"
118
- }
119
- },
120
- "node_modules/acorn": {
121
- "version": "8.16.0",
122
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
123
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
124
- "dev": true,
125
- "license": "MIT",
126
- "bin": {
127
- "acorn": "bin/acorn"
128
- },
129
- "engines": {
130
- "node": ">=0.4.0"
131
- }
132
- },
133
- "node_modules/acorn-walk": {
134
- "version": "8.3.5",
135
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
136
- "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
137
- "dev": true,
138
- "license": "MIT",
139
- "dependencies": {
140
- "acorn": "^8.11.0"
141
- },
142
- "engines": {
143
- "node": ">=0.4.0"
144
- }
145
- },
146
- "node_modules/arg": {
147
- "version": "4.1.3",
148
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
149
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
150
- "dev": true,
151
- "license": "MIT"
152
- },
153
- "node_modules/asynckit": {
154
- "version": "0.4.0",
155
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
156
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
157
- "license": "MIT"
158
- },
159
- "node_modules/axios": {
160
- "version": "1.13.6",
161
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
162
- "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
163
- "license": "MIT",
164
- "dependencies": {
165
- "follow-redirects": "^1.15.11",
166
- "form-data": "^4.0.5",
167
- "proxy-from-env": "^1.1.0"
168
- }
169
- },
170
- "node_modules/call-bind-apply-helpers": {
171
- "version": "1.0.2",
172
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
173
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
174
- "license": "MIT",
175
- "dependencies": {
176
- "es-errors": "^1.3.0",
177
- "function-bind": "^1.1.2"
178
- },
179
- "engines": {
180
- "node": ">= 0.4"
181
- }
182
- },
183
- "node_modules/chalk": {
184
- "version": "5.6.2",
185
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
186
- "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
187
- "license": "MIT",
188
- "engines": {
189
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
190
- },
191
- "funding": {
192
- "url": "https://github.com/chalk/chalk?sponsor=1"
193
- }
194
- },
195
- "node_modules/combined-stream": {
196
- "version": "1.0.8",
197
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
198
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
199
- "license": "MIT",
200
- "dependencies": {
201
- "delayed-stream": "~1.0.0"
202
- },
203
- "engines": {
204
- "node": ">= 0.8"
205
- }
206
- },
207
- "node_modules/commander": {
208
- "version": "14.0.3",
209
- "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
210
- "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
211
- "license": "MIT",
212
- "engines": {
213
- "node": ">=20"
214
- }
215
- },
216
- "node_modules/create-require": {
217
- "version": "1.1.1",
218
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
219
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
220
- "dev": true,
221
- "license": "MIT"
222
- },
223
- "node_modules/delayed-stream": {
224
- "version": "1.0.0",
225
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
226
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
227
- "license": "MIT",
228
- "engines": {
229
- "node": ">=0.4.0"
230
- }
231
- },
232
- "node_modules/diff": {
233
- "version": "4.0.4",
234
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
235
- "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
236
- "dev": true,
237
- "license": "BSD-3-Clause",
238
- "engines": {
239
- "node": ">=0.3.1"
240
- }
241
- },
242
- "node_modules/dunder-proto": {
243
- "version": "1.0.1",
244
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
245
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
246
- "license": "MIT",
247
- "dependencies": {
248
- "call-bind-apply-helpers": "^1.0.1",
249
- "es-errors": "^1.3.0",
250
- "gopd": "^1.2.0"
251
- },
252
- "engines": {
253
- "node": ">= 0.4"
254
- }
255
- },
256
- "node_modules/es-define-property": {
257
- "version": "1.0.1",
258
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
259
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
260
- "license": "MIT",
261
- "engines": {
262
- "node": ">= 0.4"
263
- }
264
- },
265
- "node_modules/es-errors": {
266
- "version": "1.3.0",
267
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
268
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
269
- "license": "MIT",
270
- "engines": {
271
- "node": ">= 0.4"
272
- }
273
- },
274
- "node_modules/es-object-atoms": {
275
- "version": "1.1.1",
276
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
277
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
278
- "license": "MIT",
279
- "dependencies": {
280
- "es-errors": "^1.3.0"
281
- },
282
- "engines": {
283
- "node": ">= 0.4"
284
- }
285
- },
286
- "node_modules/es-set-tostringtag": {
287
- "version": "2.1.0",
288
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
289
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
290
- "license": "MIT",
291
- "dependencies": {
292
- "es-errors": "^1.3.0",
293
- "get-intrinsic": "^1.2.6",
294
- "has-tostringtag": "^1.0.2",
295
- "hasown": "^2.0.2"
296
- },
297
- "engines": {
298
- "node": ">= 0.4"
299
- }
300
- },
301
- "node_modules/follow-redirects": {
302
- "version": "1.15.11",
303
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
304
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
305
- "funding": [
306
- {
307
- "type": "individual",
308
- "url": "https://github.com/sponsors/RubenVerborgh"
309
- }
310
- ],
311
- "license": "MIT",
312
- "engines": {
313
- "node": ">=4.0"
314
- },
315
- "peerDependenciesMeta": {
316
- "debug": {
317
- "optional": true
318
- }
319
- }
320
- },
321
- "node_modules/form-data": {
322
- "version": "4.0.5",
323
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
324
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
325
- "license": "MIT",
326
- "dependencies": {
327
- "asynckit": "^0.4.0",
328
- "combined-stream": "^1.0.8",
329
- "es-set-tostringtag": "^2.1.0",
330
- "hasown": "^2.0.2",
331
- "mime-types": "^2.1.12"
332
- },
333
- "engines": {
334
- "node": ">= 6"
335
- }
336
- },
337
- "node_modules/function-bind": {
338
- "version": "1.1.2",
339
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
340
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
341
- "license": "MIT",
342
- "funding": {
343
- "url": "https://github.com/sponsors/ljharb"
344
- }
345
- },
346
- "node_modules/get-intrinsic": {
347
- "version": "1.3.0",
348
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
349
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
350
- "license": "MIT",
351
- "dependencies": {
352
- "call-bind-apply-helpers": "^1.0.2",
353
- "es-define-property": "^1.0.1",
354
- "es-errors": "^1.3.0",
355
- "es-object-atoms": "^1.1.1",
356
- "function-bind": "^1.1.2",
357
- "get-proto": "^1.0.1",
358
- "gopd": "^1.2.0",
359
- "has-symbols": "^1.1.0",
360
- "hasown": "^2.0.2",
361
- "math-intrinsics": "^1.1.0"
362
- },
363
- "engines": {
364
- "node": ">= 0.4"
365
- },
366
- "funding": {
367
- "url": "https://github.com/sponsors/ljharb"
368
- }
369
- },
370
- "node_modules/get-proto": {
371
- "version": "1.0.1",
372
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
373
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
374
- "license": "MIT",
375
- "dependencies": {
376
- "dunder-proto": "^1.0.1",
377
- "es-object-atoms": "^1.0.0"
378
- },
379
- "engines": {
380
- "node": ">= 0.4"
381
- }
382
- },
383
- "node_modules/gopd": {
384
- "version": "1.2.0",
385
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
386
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
387
- "license": "MIT",
388
- "engines": {
389
- "node": ">= 0.4"
390
- },
391
- "funding": {
392
- "url": "https://github.com/sponsors/ljharb"
393
- }
394
- },
395
- "node_modules/has-symbols": {
396
- "version": "1.1.0",
397
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
398
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
399
- "license": "MIT",
400
- "engines": {
401
- "node": ">= 0.4"
402
- },
403
- "funding": {
404
- "url": "https://github.com/sponsors/ljharb"
405
- }
406
- },
407
- "node_modules/has-tostringtag": {
408
- "version": "1.0.2",
409
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
410
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
411
- "license": "MIT",
412
- "dependencies": {
413
- "has-symbols": "^1.0.3"
414
- },
415
- "engines": {
416
- "node": ">= 0.4"
417
- },
418
- "funding": {
419
- "url": "https://github.com/sponsors/ljharb"
420
- }
421
- },
422
- "node_modules/hasown": {
423
- "version": "2.0.2",
424
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
425
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
426
- "license": "MIT",
427
- "dependencies": {
428
- "function-bind": "^1.1.2"
429
- },
430
- "engines": {
431
- "node": ">= 0.4"
432
- }
433
- },
434
- "node_modules/make-error": {
435
- "version": "1.3.6",
436
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
437
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
438
- "dev": true,
439
- "license": "ISC"
440
- },
441
- "node_modules/math-intrinsics": {
442
- "version": "1.1.0",
443
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
444
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
445
- "license": "MIT",
446
- "engines": {
447
- "node": ">= 0.4"
448
- }
449
- },
450
- "node_modules/mime-db": {
451
- "version": "1.52.0",
452
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
453
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
454
- "license": "MIT",
455
- "engines": {
456
- "node": ">= 0.6"
457
- }
458
- },
459
- "node_modules/mime-types": {
460
- "version": "2.1.35",
461
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
462
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
463
- "license": "MIT",
464
- "dependencies": {
465
- "mime-db": "1.52.0"
466
- },
467
- "engines": {
468
- "node": ">= 0.6"
469
- }
470
- },
471
- "node_modules/proxy-from-env": {
472
- "version": "1.1.0",
473
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
474
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
475
- "license": "MIT"
476
- },
477
- "node_modules/ts-node": {
478
- "version": "10.9.2",
479
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
480
- "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
481
- "dev": true,
482
- "license": "MIT",
483
- "dependencies": {
484
- "@cspotcode/source-map-support": "^0.8.0",
485
- "@tsconfig/node10": "^1.0.7",
486
- "@tsconfig/node12": "^1.0.7",
487
- "@tsconfig/node14": "^1.0.0",
488
- "@tsconfig/node16": "^1.0.2",
489
- "acorn": "^8.4.1",
490
- "acorn-walk": "^8.1.1",
491
- "arg": "^4.1.0",
492
- "create-require": "^1.1.0",
493
- "diff": "^4.0.1",
494
- "make-error": "^1.1.1",
495
- "v8-compile-cache-lib": "^3.0.1",
496
- "yn": "3.1.1"
497
- },
498
- "bin": {
499
- "ts-node": "dist/bin.js",
500
- "ts-node-cwd": "dist/bin-cwd.js",
501
- "ts-node-esm": "dist/bin-esm.js",
502
- "ts-node-script": "dist/bin-script.js",
503
- "ts-node-transpile-only": "dist/bin-transpile.js",
504
- "ts-script": "dist/bin-script-deprecated.js"
505
- },
506
- "peerDependencies": {
507
- "@swc/core": ">=1.2.50",
508
- "@swc/wasm": ">=1.2.50",
509
- "@types/node": "*",
510
- "typescript": ">=2.7"
511
- },
512
- "peerDependenciesMeta": {
513
- "@swc/core": {
514
- "optional": true
515
- },
516
- "@swc/wasm": {
517
- "optional": true
518
- }
519
- }
520
- },
521
- "node_modules/typescript": {
522
- "version": "5.9.3",
523
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
524
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
525
- "dev": true,
526
- "license": "Apache-2.0",
527
- "bin": {
528
- "tsc": "bin/tsc",
529
- "tsserver": "bin/tsserver"
530
- },
531
- "engines": {
532
- "node": ">=14.17"
533
- }
534
- },
535
- "node_modules/undici-types": {
536
- "version": "7.18.2",
537
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
538
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
539
- "dev": true,
540
- "license": "MIT"
541
- },
542
- "node_modules/v8-compile-cache-lib": {
543
- "version": "3.0.1",
544
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
545
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
546
- "dev": true,
547
- "license": "MIT"
548
- },
549
- "node_modules/yn": {
550
- "version": "3.1.1",
551
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
552
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
553
- "dev": true,
554
- "license": "MIT",
555
- "engines": {
556
- "node": ">=6"
557
- }
558
- }
559
- }
560
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cli-ts/package.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "name": "pii-ts-cli",
3
- "version": "1.0.0",
4
- "description": "TypeScript PII Moderator CLI",
5
- "type": "module",
6
- "bin": {
7
- "pii-ts": "./dist/index.js"
8
- },
9
- "scripts": {
10
- "build": "tsc",
11
- "start": "node --no-warnings dist/index.js",
12
- "dev": "ts-node --no-warnings index.ts"
13
- },
14
- "dependencies": {
15
- "axios": "^1.8.1",
16
- "chalk": "^5.4.1",
17
- "commander": "^13.1.0"
18
- },
19
- "devDependencies": {
20
- "@types/node": "^24.12.0",
21
- "ts-node": "^10.9.2",
22
- "typescript": "^5.8.2"
23
- }
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cli-ts/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "node",
6
- "outDir": "./dist",
7
- "rootDir": "./",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true
12
- },
13
- "include": ["index.ts"]
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cli/Dockerfile DELETED
@@ -1,13 +0,0 @@
1
- # CLI Dockerfile
2
- FROM python:3.12-slim
3
-
4
- WORKDIR /app
5
-
6
- # Install dependencies
7
- RUN pip install --no-cache-dir click requests
8
-
9
- # Copy CLI script
10
- COPY pii_mod.py .
11
-
12
- # Use CMD instead of ENTRYPOINT to allow docker-compose command
13
- CMD ["python", "pii_mod.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cli/pii_mod.py DELETED
@@ -1,57 +0,0 @@
1
- import click
2
- import requests
3
- import json
4
- import sys
5
-
6
- API_URL = "http://localhost:8000"
7
-
8
- @click.group()
9
- def cli():
10
- """PII Moderator CLI - Protect your data before sending it to LLMs."""
11
- pass
12
-
13
- @cli.command()
14
- @click.argument('text')
15
- @click.option('--entities', '-e', multiple=True, help="Specific entities to redact (e.g. -e PERSON -e EMAIL_ADDRESS)")
16
- def redact(text, entities):
17
- """Redact PII from a given string."""
18
- try:
19
- payload = {"text": text}
20
- if entities:
21
- payload["entities"] = list(entities)
22
-
23
- response = requests.post(f"{API_URL}/redact", json=payload)
24
- response.raise_for_status()
25
-
26
- result = response.json()
27
- click.echo(click.style("\nOriginal:", fg="cyan"))
28
- click.echo(result["original_text"])
29
- click.echo(click.style("\nRedacted:", fg="green"))
30
- click.echo(result["redacted_text"])
31
-
32
- if result["detected_entities"]:
33
- click.echo(click.style("\nDetected Entities:", fg="yellow"))
34
- for ent in result["detected_entities"]:
35
- click.echo(f"- {ent['entity_type']} (Score: {ent['score']:.2f})")
36
-
37
- except Exception as e:
38
- click.echo(click.style(f"Error: {e}", fg="red"), err=True)
39
- sys.exit(1)
40
-
41
- @cli.command()
42
- @click.argument('file', type=click.File('r'))
43
- def redact_file(file):
44
- """Redact PII from a file."""
45
- content = file.read()
46
- try:
47
- payload = {"text": content}
48
- response = requests.post(f"{API_URL}/redact", json=payload)
49
- response.raise_for_status()
50
- result = response.json()
51
- click.echo(result["redacted_text"])
52
- except Exception as e:
53
- click.echo(click.style(f"Error: {e}", fg="red"), err=True)
54
- sys.exit(1)
55
-
56
- if __name__ == "__main__":
57
- cli()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
docker-compose.yml CHANGED
@@ -29,30 +29,5 @@ services:
29
  - VITE_API_URL=http://localhost:8000
30
  command: sh -c "npm install && npm run dev -- --host"
31
 
32
- cli:
33
- build:
34
- context: ./cli
35
- dockerfile: Dockerfile
36
- volumes:
37
- - ./cli:/app
38
- environment:
39
- - API_URL=http://api:8000
40
- depends_on:
41
- - api
42
-
43
- cli-ts:
44
- build:
45
- context: ./cli-ts
46
- dockerfile: Dockerfile
47
- volumes:
48
- - ./cli-ts:/app
49
- - /app/node_modules
50
- - /app/dist
51
- environment:
52
- - API_URL=http://api:8000
53
- depends_on:
54
- - api
55
- command: sh -c "npm run build && node --no-warnings dist/index.js"
56
-
57
  volumes:
58
  spacy_data: # Ce volume conservera les modèles et les librairies installées
 
29
  - VITE_API_URL=http://localhost:8000
30
  command: sh -c "npm install && npm run dev -- --host"
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  volumes:
33
  spacy_data: # Ce volume conservera les modèles et les librairies installées
ui/index.html CHANGED
@@ -3,6 +3,9 @@
3
  <head>
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
 
 
 
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>Redac_Scan</title>
8
  </head>
 
3
  <head>
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <link rel="preconnect" href="https://fonts.googleapis.com">
7
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet">
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
  <title>Redac_Scan</title>
11
  </head>
ui/src/App.tsx CHANGED
@@ -4,7 +4,7 @@ import {
4
  Shield, Eye, Lock, CheckCircle2, Copy,
5
  Database, Languages, Fingerprint, Zap, Activity,
6
  Palette, ChevronDown, Check, Radio, Target, Terminal,
7
- Upload, Trash2, AlertTriangle
8
  } from 'lucide-react';
9
 
10
  interface EntityMeta {
@@ -25,8 +25,8 @@ interface RedactResponse {
25
  type Theme = 'premium' | 'light' | 'dark';
26
 
27
  const EXAMPLES = [
28
- { id: "PV-01", label: "FR - Procès Verbal", lang: "fr", text: `PROCÈS-VERBAL DE RÉUNION DE CHANTIER - RÉNOVATION COMPLEXE HÔTELIER\n\nDate : 20 Mars 2026\nLieu : 142 Avenue des Champs-Élysées, 75008 Paris.\n\nPRÉSENTS :\n- M. Alexandre de La Rochefoucauld (Directeur de projet, Groupe Immobilier "Lux-Horizon" - SIRET 321 654 987 00054).\n- Mme Valérie Marchand (Architecte, Cabinet "Marchand & Associés").\n- M. Thomas Dubois (Ingénieur sécurité, joignable au 06.45.12.89.33).\n\nORDRE DU JOUR ET DÉCISIONS :\n1. Validation des acomptes : La facture n°2026-04 d'un montant de 45 000€ a été réglée par virement sur le compte IBAN FR76 3000 1000 2000 3000 4000 500.` },
29
- { id: "MED-02", label: "EN - Clinical", lang: "en", text: `CLINICAL DISCHARGE SUMMARY - PATIENT ID: #XP-99021\n\nPATIENT INFORMATION:\nName: Sarah-Jane Montgomery\nDOB: 12/05/1982\nAddress: 1244 North Oak Street, San Francisco, CA 94102\nEmergency Contact: Robert Montgomery (Husband) - Phone: (415) 555-0198\n\nADMISSION DIAGNOSIS:\nAcute respiratory distress. SSN: 123-45-6789.` }
30
  ];
31
 
32
  function App() {
@@ -71,56 +71,84 @@ function App() {
71
  const response = await axios.post(`${API_URL}/redact`, { text, language });
72
  setResult(response.data);
73
  } catch (err: any) { console.error(err); }
74
- finally { setTimeout(() => setLoading(false), 500); }
75
  };
76
 
77
  const themeClasses = {
78
- premium: { body: 'bg-[#050a15]', card: 'bg-slate-950/50 border-white/10', text: 'text-cyan-400', inputBg: 'bg-transparent', inputText: 'text-white' },
79
- light: { body: 'bg-white', card: 'bg-white border-black', text: 'text-black', inputBg: 'bg-white', inputText: 'text-black font-semibold' },
80
- dark: { body: 'bg-[#020617]', card: 'bg-slate-950 border-slate-800', text: 'text-cyan-500', inputBg: 'bg-transparent', inputText: 'text-slate-200' }
81
- };
82
-
83
- const cur = themeClasses[theme];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  return (
86
- <div className={`h-screen flex flex-col transition-all duration-500 font-mono ${cur.body} ${cur.text} overflow-hidden`}>
87
- {theme !== 'light' && <div className="absolute inset-0 bg-grid pointer-events-none opacity-20" />}
88
 
89
  {/* HEADER */}
90
- <header className={`flex-none flex items-center justify-between px-6 py-3 border-b ${theme === 'light' ? 'border-black' : 'border-cyan-500/20'} z-50 bg-inherit shadow-sm`}>
91
- <div className="flex items-center gap-4">
92
- <div className={`border-2 ${theme === 'light' ? 'border-black' : 'border-cyan-500'} p-1.5 rounded-lg shadow-[0_0_10px_rgba(0,242,255,0.2)]`}><Shield className="w-5 h-5" /></div>
93
- <h1 className={`text-xl font-black italic tracking-tighter uppercase ${theme === 'light' ? 'text-black' : 'text-white'}`}>REDAC<span className="text-cyan-500">_SCAN</span></h1>
94
- <div className="hidden md:flex items-center gap-2 ml-6 px-3 py-1 bg-black/20 rounded-full border border-white/5">
95
- <Radio className={`w-2.5 h-2.5 ${apiStatus === 'online' ? 'text-emerald-500' : 'text-rose-500'}`} />
96
- <span className="text-[7px] font-black tracking-widest opacity-70 uppercase">UPLINK_{apiStatus}</span>
 
 
 
 
97
  </div>
98
  </div>
99
 
100
- <div className="flex items-center gap-2">
101
- {/* Theme Dropdown */}
102
  <div className="relative" ref={themeRef}>
103
- <button onClick={() => setIsThemeOpen(!isThemeOpen)} className={`flex items-center gap-2 px-3 py-1.5 border ${cur.card} rounded-lg text-[9px] font-black uppercase tracking-widest hover:border-cyan-500 transition-all`}>
104
- <Palette className="w-3 h-3" /> <span>{theme}</span>
105
  </button>
106
  {isThemeOpen && (
107
- <div className={`absolute right-0 mt-2 w-48 ${theme === 'light' ? 'bg-white border-black' : 'bg-slate-900 border-cyan-500/50'} border rounded-lg shadow-2xl z-[100] overflow-hidden`}>
108
  {['premium', 'light', 'dark'].map((t) => (
109
- <button key={t} onClick={() => { setTheme(t as Theme); setIsThemeOpen(false); }} className={`w-full text-left px-4 py-2.5 text-[9px] font-bold uppercase tracking-widest hover:bg-cyan-500/10 ${theme === t ? 'text-cyan-500' : 'text-slate-500'}`}>{t}</button>
110
  ))}
111
  </div>
112
  )}
113
  </div>
114
-
115
- {/* Lang Dropdown */}
116
  <div className="relative" ref={langRef}>
117
- <button onClick={() => setIsLangOpen(!isLangOpen)} className={`flex items-center gap-2 px-3 py-1.5 border ${cur.card} rounded-lg text-[9px] font-black uppercase tracking-widest hover:border-cyan-500 transition-all`}>
118
- <Languages className="w-3 h-3" /> <span>{language}</span>
119
  </button>
120
  {isLangOpen && (
121
- <div className={`absolute right-0 mt-2 w-48 ${theme === 'light' ? 'bg-white border-black' : 'bg-slate-900 border-cyan-500/50'} border rounded-lg shadow-2xl z-[100] overflow-hidden`}>
122
  {['auto', 'en', 'fr'].map((l) => (
123
- <button key={l} onClick={() => { setLanguage(l); setIsLangOpen(false); }} className={`w-full text-left px-4 py-2.5 text-[9px] font-bold uppercase tracking-widest hover:bg-cyan-500/10 ${language === l ? 'text-cyan-500' : 'text-slate-500'}`}>{l}</button>
124
  ))}
125
  </div>
126
  )}
@@ -128,82 +156,78 @@ function App() {
128
  </div>
129
  </header>
130
 
131
- {/* COMPACT PRESETS BAR */}
132
- <div className={`flex-none flex items-center gap-4 px-6 py-2 bg-black/5 border-b ${theme === 'light' ? 'border-black' : 'border-cyan-500/10'}`}>
133
- <span className="text-[8px] font-black uppercase tracking-[0.3em] opacity-40">Load_Protocol:</span>
134
- <div className="flex gap-2">
135
- {EXAMPLES.map((ex, i) => (
136
- <button key={i} onClick={() => { setText(ex.text); setLanguage(ex.lang); setResult(null); }} className={`px-3 py-1 border ${theme === 'light' ? 'border-black hover:bg-black hover:text-white' : 'border-cyan-500/20 hover:border-cyan-500'} rounded text-[9px] font-bold transition-all uppercase`}>{ex.label}</button>
137
- ))}
138
- </div>
139
- </div>
140
-
141
- {/* MAIN WORKSPACE - TRIPLE COLUMN HUD */}
142
  <main className="flex-grow flex overflow-hidden">
143
 
144
- {/* COLUMN 1: INGEST */}
145
- <div className={`w-1/3 flex flex-col border-r ${theme === 'light' ? 'border-black' : 'border-cyan-500/20'}`}>
146
- <div className="flex-none flex items-center justify-between px-6 py-3 border-b border-inherit bg-black/5">
147
- <div className="flex items-center gap-3 font-black text-[10px] uppercase tracking-widest text-inherit"><Database className="w-4 h-4 text-cyan-500" /> Ingest_Station</div>
148
- <div className="flex gap-2">
149
- <button onClick={() => fileInputRef.current?.click()} className="p-1.5 hover:text-cyan-500 transition-colors" title="Import File"><Upload className="w-4 h-4" /></button>
150
- <button onClick={() => {setText(''); setResult(null);}} className="p-1.5 hover:text-rose-500 transition-colors" title="Clear Stream"><Trash2 className="w-4 h-4" /></button>
151
- <input type="file" ref={fileInputRef} onChange={(e) => { const f = e.target.files?.[0]; if(f){const r=new FileReader(); r.onload=(ev)=>setText(ev.target?.result as string); r.readAsText(f);}}} className="hidden" />
152
  </div>
153
  </div>
154
- <textarea
155
- className={`flex-grow w-full ${cur.inputBg} ${cur.inputText} p-8 border-none outline-none text-sm leading-relaxed resize-none custom-scrollbar`}
156
- placeholder="AWAITING SECURE DATA INPUT..."
157
- value={text}
158
- onChange={(e) => setText(e.target.value)}
159
- />
160
- <div className="flex-none p-6 border-t border-inherit bg-black/5">
161
- <button onClick={handleRedact} disabled={loading || !text.trim()} className={`w-full py-4 rounded-xl font-black text-xs tracking-[0.5em] uppercase transition-all ${loading || !text.trim() ? 'bg-slate-200 text-slate-400' : 'bg-cyan-500 text-black hover:bg-cyan-400 shadow-xl shadow-cyan-500/20 active:scale-95'}`}>
162
- {loading ? 'ANALYZING_STREAM...' : 'EXECUTE_SCRUB'}
163
- </button>
164
  </div>
165
- </div>
166
 
167
- {/* COLUMN 2: SECURED OUTPUT */}
168
- <div className={`w-1/3 flex flex-col border-r ${theme === 'light' ? 'border-black' : 'border-cyan-500/20'} bg-black/10`}>
169
- <div className="flex-none flex items-center justify-between px-6 py-3 border-b border-inherit bg-black/20">
170
- <div className="flex items-center gap-3 font-black text-[10px] uppercase tracking-widest text-emerald-500"><Terminal className="w-4 h-4" /> Secured_Stream</div>
171
- {result && <button onClick={() => {navigator.clipboard.writeText(result.redacted_text); setCopied(true); setTimeout(()=>setCopied(false), 2000)}} className="text-[9px] font-black underline decoration-emerald-500/50 underline-offset-4">{copied ? 'COPIED' : 'COPY_OUTPUT'}</button>}
172
- </div>
173
- <div className={`flex-grow p-8 ${theme === 'light' ? 'text-black' : 'text-emerald-400'} text-[13px] leading-relaxed whitespace-pre-wrap overflow-y-auto custom-scrollbar italic font-medium`}>
174
- {!result ? <div className="h-full flex flex-col items-center justify-center opacity-10"><Lock className="w-12 h-12 mb-4" /><p className="text-[9px] font-black tracking-[0.2em]">LINK_STBY</p></div> : result.redacted_text}
 
175
  </div>
176
  </div>
177
 
178
- {/* COLUMN 3: RISK MATRIX */}
179
- <div className="w-1/3 flex flex-col bg-black/30">
180
- <div className="flex-none flex items-center gap-3 px-6 py-3 border-b ${theme === 'light' ? 'border-black' : 'border-cyan-500/20'} bg-black/40">
181
- <Fingerprint className="w-4 h-4 text-cyan-500" />
182
- <span className="text-[10px] font-black uppercase tracking-widest">Risk_Matrix_Analysis</span>
183
  </div>
184
- <div className="flex-grow p-6 overflow-y-auto custom-scrollbar space-y-3">
 
185
  {!result ? (
186
- <div className="h-full flex items-center justify-center opacity-10 text-[9px] uppercase tracking-widest">Waiting for detection...</div>
187
- ) : result.entities.length === 0 ? (
188
- <div className="p-4 border border-emerald-500/20 bg-emerald-500/5 rounded-xl text-center"><p className="text-[10px] font-bold text-emerald-500 uppercase">Clear Environment: No PII Detected</p></div>
 
189
  ) : (
190
- result.entities.map((ent, idx) => (
191
- <div key={idx} className={`p-4 rounded-xl border ${theme === 'light' ? 'border-black' : 'border-white/10 bg-white/5 shadow-lg'} flex flex-col gap-2 hover:border-cyan-500/50 transition-all`}>
192
- <div className="flex items-center justify-between">
193
- <span className="text-[8px] font-black text-cyan-500 uppercase">{ent.type}</span>
194
- <span className="text-[10px] font-black text-cyan-500">{ent.score}%</span>
195
- </div>
196
- <p className={`text-[11px] font-bold truncate ${theme === 'light' ? 'text-black' : 'text-white'}`}>"{ent.text}"</p>
197
- <div className={`w-full h-1 ${theme === 'dark' ? 'bg-slate-800' : 'bg-slate-100'} rounded-full overflow-hidden`}><div className="h-full bg-cyan-500 shadow-[0_0_10px_#00f2ff]" style={{ width: `${ent.score}%` }} /></div>
198
- </div>
199
- ))
200
  )}
201
  </div>
 
 
202
  {result && (
203
- <div className="flex-none p-4 border-t border-white/5 bg-black/40">
204
- <div className="flex items-center justify-between px-2 text-[9px] font-black text-slate-500 uppercase">
205
- <span>Threats: {result.entities.length}</span>
206
- <span>Lang: {result.detected_language}</span>
 
 
 
 
 
 
207
  </div>
208
  </div>
209
  )}
 
4
  Shield, Eye, Lock, CheckCircle2, Copy,
5
  Database, Languages, Fingerprint, Zap, Activity,
6
  Palette, ChevronDown, Check, Radio, Target, Terminal,
7
+ Upload, Trash2
8
  } from 'lucide-react';
9
 
10
  interface EntityMeta {
 
25
  type Theme = 'premium' | 'light' | 'dark';
26
 
27
  const EXAMPLES = [
28
+ { id: "PRO-01", label: "Procès Verbal", lang: "fr", text: `PROCÈS-VERBAL DE RÉUNION DE CHANTIER - RÉNOVATION COMPLEXE HÔTELIER\n\nDate : 20 Mars 2026\nLieu : 142 Avenue des Champs-Élysées, 75008 Paris.\n\nPRÉSENTS :\n- M. Alexandre de La Rochefoucauld (Directeur de projet, Groupe Immobilier "Lux-Horizon" - SIRET 321 654 987 00054).\n- Mme Valérie Marchand (Architecte, Cabinet "Marchand & Associés").\n- M. Thomas Dubois (Ingénieur sécurité, joignable au 06.45.12.89.33).\n\nORDRE DU JOUR :\nValidation des acomptes sur l'IBAN FR76 3000 1000 2000 3000 4000 500.` },
29
+ { id: "MED-02", label: "Medical Record", lang: "en", text: `CLINICAL DISCHARGE SUMMARY - PATIENT ID: #XP-99021\n\nPATIENT INFORMATION:\nName: Sarah-Jane Montgomery\nDOB: 12/05/1982\nAddress: 1244 North Oak Street, San Francisco, CA 94102\nSSN : 123-45-6789. Email: sj.montgomery@provider.net.` }
30
  ];
31
 
32
  function App() {
 
71
  const response = await axios.post(`${API_URL}/redact`, { text, language });
72
  setResult(response.data);
73
  } catch (err: any) { console.error(err); }
74
+ finally { setTimeout(() => setLoading(false), 400); }
75
  };
76
 
77
  const themeClasses = {
78
+ premium: {
79
+ body: 'bg-slate-50',
80
+ header: 'bg-white border-slate-200 text-slate-900',
81
+ panel: 'bg-white border-slate-200',
82
+ panelHeader: 'bg-slate-50 text-slate-500',
83
+ input: 'bg-white text-slate-900 placeholder-slate-300',
84
+ output: 'bg-slate-50 text-slate-800',
85
+ tag: 'bg-blue-600 text-white shadow-sm',
86
+ footer: 'bg-slate-100/50 border-slate-200',
87
+ btn: 'bg-slate-900 hover:bg-black text-white'
88
+ },
89
+ light: {
90
+ body: 'bg-white',
91
+ header: 'bg-white border-black text-black',
92
+ panel: 'bg-white border-black',
93
+ panelHeader: 'bg-white border-b-black text-black',
94
+ input: 'bg-white text-black placeholder-gray-300',
95
+ output: 'bg-white text-black',
96
+ tag: 'bg-black text-white rounded-none border border-white',
97
+ footer: 'bg-white border-black',
98
+ btn: 'bg-black hover:bg-zinc-800 text-white'
99
+ },
100
+ dark: {
101
+ body: 'bg-[#020617]',
102
+ header: 'bg-slate-900/50 border-slate-800 text-white',
103
+ panel: 'bg-slate-900/30 border-slate-800',
104
+ panelHeader: 'bg-slate-900/50 text-slate-400',
105
+ input: 'bg-transparent text-white placeholder-slate-700',
106
+ output: 'bg-black/20 text-blue-400',
107
+ tag: 'bg-blue-500 text-black font-black',
108
+ footer: 'bg-black/40 border-slate-800',
109
+ btn: 'bg-blue-600 hover:bg-blue-500 text-white shadow-blue-500/20'
110
+ }
111
+ }[theme];
112
 
113
  return (
114
+ <div className={`h-screen flex flex-col font-sans transition-colors duration-300 ${themeClasses.body} ${themeClasses.header.split(' ')[2]} overflow-hidden`}>
 
115
 
116
  {/* HEADER */}
117
+ <header className={`flex-none flex items-center justify-between px-8 py-4 border-b ${themeClasses.header} z-50`}>
118
+ <div className="flex items-center gap-6">
119
+ <div className="flex items-center gap-3">
120
+ <div className={`p-2 rounded-xl ${theme === 'dark' ? 'bg-blue-600' : 'bg-black'} text-white shadow-lg`}>
121
+ <Shield className="w-5 h-5" />
122
+ </div>
123
+ <h1 className="text-xl font-bold tracking-tight">RedacScan</h1>
124
+ </div>
125
+ <div className="hidden sm:flex items-center gap-2 ml-4 opacity-50">
126
+ <div className={`w-2 h-2 rounded-full ${apiStatus === 'online' ? 'bg-emerald-500' : 'bg-rose-500'}`} />
127
+ <span className="text-[10px] font-bold uppercase tracking-widest">{apiStatus}</span>
128
  </div>
129
  </div>
130
 
131
+ <div className="flex items-center gap-3">
 
132
  <div className="relative" ref={themeRef}>
133
+ <button onClick={() => setIsThemeOpen(!isThemeOpen)} className={`flex items-center gap-2 px-4 py-2 border rounded-full text-xs font-semibold transition-all ${themeClasses.panel} hover:border-blue-500`}>
134
+ <Palette className="w-4 h-4" /> <span>{theme}</span>
135
  </button>
136
  {isThemeOpen && (
137
+ <div className={`absolute right-0 mt-2 w-48 p-2 rounded-2xl shadow-2xl z-[100] border ${themeClasses.panel} ${theme === 'dark' ? 'bg-slate-900' : 'bg-white'}`}>
138
  {['premium', 'light', 'dark'].map((t) => (
139
+ <button key={t} onClick={() => { setTheme(t as Theme); setIsThemeOpen(false); }} className={`w-full text-left px-4 py-2.5 text-xs font-medium rounded-xl transition-colors ${theme === t ? 'bg-blue-600 text-white' : 'hover:bg-slate-100 dark:hover:bg-slate-800'}`}>{t}</button>
140
  ))}
141
  </div>
142
  )}
143
  </div>
 
 
144
  <div className="relative" ref={langRef}>
145
+ <button onClick={() => setIsLangOpen(!isLangOpen)} className={`flex items-center gap-2 px-4 py-2 border rounded-full text-xs font-semibold transition-all ${themeClasses.panel} hover:border-blue-500`}>
146
+ <Languages className="w-4 h-4" /> <span>{language}</span>
147
  </button>
148
  {isLangOpen && (
149
+ <div className={`absolute right-0 mt-2 w-48 p-2 rounded-2xl shadow-2xl z-[100] border ${themeClasses.panel} ${theme === 'dark' ? 'bg-slate-900' : 'bg-white'}`}>
150
  {['auto', 'en', 'fr'].map((l) => (
151
+ <button key={l} onClick={() => { setLanguage(l); setIsLangOpen(false); }} className={`w-full text-left px-4 py-2.5 text-xs font-medium rounded-xl transition-colors ${language === l ? 'bg-blue-600 text-white' : 'hover:bg-slate-100 dark:hover:bg-slate-800'}`}>{l}</button>
152
  ))}
153
  </div>
154
  )}
 
156
  </div>
157
  </header>
158
 
159
+ {/* MAIN WORKSPACE */}
 
 
 
 
 
 
 
 
 
 
160
  <main className="flex-grow flex overflow-hidden">
161
 
162
+ {/* PANEL 1: SOURCE */}
163
+ <div className={`w-1/2 flex flex-col border-r ${themeClasses.panel.split(' ')[1]}`}>
164
+ <div className={`flex-none flex items-center justify-between px-8 py-4 border-b ${themeClasses.panelHeader}`}>
165
+ <div className="flex items-center gap-3 text-xs font-bold uppercase tracking-widest"><Database className="w-4 h-4 text-blue-600" /> Source Document</div>
166
+ <div className="flex gap-3">
167
+ <button onClick={() => fileInputRef.current?.click()} className="p-1.5 rounded-lg hover:bg-black/10 transition-colors"><Upload className="w-4 h-4" /></button>
168
+ <button onClick={() => {setText(''); setResult(null);}} className="p-1.5 rounded-lg hover:bg-black/10 transition-colors text-rose-500"><Trash2 className="w-4 h-4" /></button>
169
+ <input type="file" ref={fileInputRef} onChange={(e) => {const f=e.target.files?.[0]; if(f){const r=new FileReader(); r.onload=(ev)=>setText(ev.target?.result as string); r.readAsText(f);}}} className="hidden" />
170
  </div>
171
  </div>
172
+
173
+ <div className="flex-grow relative overflow-hidden bg-inherit">
174
+ {loading && <div className="loading-progress"><div className="loading-progress-bar" /></div>}
175
+ <textarea
176
+ className={`w-full h-full p-10 bg-transparent border-none outline-none text-[16px] leading-[1.8] resize-none font-sans custom-scrollbar ${themeClasses.input}`}
177
+ placeholder="Enter your document content here..."
178
+ value={text}
179
+ onChange={(e) => setText(e.target.value)}
180
+ />
 
181
  </div>
 
182
 
183
+ <div className={`flex-none p-8 border-t ${themeClasses.footer}`}>
184
+ <div className="flex gap-4 mb-4 overflow-x-auto pb-2 scrollbar-hide">
185
+ {EXAMPLES.map((ex, i) => (
186
+ <button key={i} onClick={() => { setText(ex.text); setLanguage(ex.lang); setResult(null); }} className={`px-4 py-2 border rounded-xl text-xs font-bold transition-all whitespace-nowrap ${theme === 'light' ? 'border-black hover:bg-black hover:text-white' : 'border-slate-200/20 hover:border-slate-400 bg-white/5'}`}>{ex.label}</button>
187
+ ))}
188
+ </div>
189
+ <button onClick={handleRedact} disabled={loading || !text.trim()} className={`w-full py-5 rounded-2xl font-bold text-sm tracking-widest uppercase transition-all flex items-center justify-center gap-4 ${loading || !text.trim() ? 'bg-slate-200 text-slate-400 cursor-not-allowed' : `${themeClasses.btn} shadow-xl active:scale-[0.98]`}`}>
190
+ {loading ? 'ANALYZING DOCUMENT...' : <><Zap className="w-4 h-4 fill-white" /> SANITIZE CONTENT</>}
191
+ </button>
192
  </div>
193
  </div>
194
 
195
+ {/* PANEL 2: RESULT */}
196
+ <div className={`w-1/2 flex flex-col ${themeClasses.output.split(' ')[0]}`}>
197
+ <div className={`flex-none flex items-center justify-between px-8 py-4 border-b ${themeClasses.panelHeader}`}>
198
+ <div className="flex items-center gap-3 text-xs font-bold uppercase tracking-widest text-blue-600"><CheckCircle2 className="w-4 h-4" /> Secured View</div>
199
+ {result && <button onClick={() => {navigator.clipboard.writeText(result.redacted_text); setCopied(true); setTimeout(()=>setCopied(false), 2000)}} className={`px-4 py-1.5 rounded-full ${themeClasses.btn} text-[10px] font-bold transition-all`}>{copied ? 'COPIED' : 'COPY RESULT'}</button>}
200
  </div>
201
+
202
+ <div className={`flex-grow p-10 font-sans text-[16px] leading-[1.8] whitespace-pre-wrap overflow-y-auto custom-scrollbar ${themeClasses.output.split(' ')[1]}`}>
203
  {!result ? (
204
+ <div className="h-full flex flex-col items-center justify-center opacity-20">
205
+ <Lock className="w-16 h-16 mb-4 stroke-1" />
206
+ <p className="font-bold tracking-widest uppercase text-xs">Waiting for Sanitization</p>
207
+ </div>
208
  ) : (
209
+ <div className="animate-in fade-in duration-500">
210
+ {result.redacted_text.split(/(<[^>]+>)/g).map((part, i) => (
211
+ part.startsWith('<') && part.endsWith('>') ? (
212
+ <span key={i} className={`inline-block px-2 py-0.5 mx-1 rounded font-bold text-[12px] uppercase tracking-tighter ${themeClasses.tag}`}>{part}</span>
213
+ ) : part
214
+ ))}
215
+ </div>
 
 
 
216
  )}
217
  </div>
218
+
219
+ {/* RISK LIST FOOTER */}
220
  {result && (
221
+ <div className={`flex-none h-48 border-t p-6 overflow-y-auto custom-scrollbar ${themeClasses.footer}`}>
222
+ <div className="flex items-center gap-3 mb-4 opacity-60 font-bold text-[10px] uppercase tracking-widest"><Fingerprint className="w-4 h-4" /> Detected Information</div>
223
+ <div className="flex flex-wrap gap-2">
224
+ {result.entities.map((ent, idx) => (
225
+ <div key={idx} className={`px-3 py-2 rounded-xl border flex items-center gap-3 bg-white shadow-sm border-slate-200 dark:bg-slate-900 dark:border-slate-800`}>
226
+ <span className="text-[9px] font-black text-blue-600 uppercase">{ent.type}</span>
227
+ <span className={`text-xs font-bold truncate max-w-[120px] ${theme === 'dark' ? 'text-white' : 'text-black'}`}>"{ent.text}"</span>
228
+ <span className="text-[10px] font-bold text-slate-400">{ent.score}%</span>
229
+ </div>
230
+ ))}
231
  </div>
232
  </div>
233
  )}
ui/src/index.css CHANGED
@@ -1,6 +1,8 @@
1
  @import "tailwindcss";
2
 
3
  @theme {
 
 
4
  --color-cyber-blue: #00f2ff;
5
  --color-cyber-pink: #ff00e5;
6
  --color-cyber-dark: #050a15;
@@ -8,20 +10,19 @@
8
 
9
  @layer base {
10
  body {
11
- @apply bg-cyber-dark text-slate-200 antialiased overflow-x-hidden;
12
  }
13
  }
14
 
15
- /* Grille futuriste en arrière-plan */
16
  .bg-grid {
17
  background-image:
18
  linear-gradient(to right, rgba(0, 242, 255, 0.05) 1px, transparent 1px),
19
  linear-gradient(to bottom, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
20
  background-size: 40px 40px;
21
- mask-image: radial-gradient(circle at center, black, transparent 80%);
22
  }
23
 
24
- /* Animation de scan laser */
25
  @keyframes scan {
26
  0% { transform: translateY(-100%); opacity: 0; }
27
  50% { opacity: 1; }
@@ -30,38 +31,12 @@
30
 
31
  .scanner-line {
32
  height: 2px;
33
- background: linear-gradient(90deg, transparent, var(--color-cyber-blue), transparent);
34
- box-shadow: 0 0 15px var(--color-cyber-blue);
35
- animation: scan 3s linear infinite;
36
- display: none;
37
  }
38
 
39
- .is-scanning .scanner-line {
40
- display: block;
41
- }
42
-
43
- /* Glassmorphism */
44
- .glass-panel {
45
- background: rgba(15, 23, 42, 0.6);
46
- backdrop-filter: blur(12px);
47
- border: 1px solid rgba(255, 255, 255, 0.1);
48
- }
49
-
50
- .neon-border {
51
- position: relative;
52
- }
53
-
54
- .neon-border::after {
55
- content: '';
56
- position: absolute;
57
- inset: -1px;
58
- background: linear-gradient(45deg, var(--color-cyber-blue), transparent, var(--color-cyber-pink));
59
- z-index: -1;
60
- border-radius: inherit;
61
- opacity: 0.3;
62
- }
63
-
64
- /* Custom Scrollbar futuriste */
65
  ::-webkit-scrollbar {
66
  width: 4px;
67
  }
@@ -69,7 +44,10 @@
69
  background: transparent;
70
  }
71
  ::-webkit-scrollbar-thumb {
72
- background: var(--color-cyber-blue);
73
  border-radius: 10px;
74
- box-shadow: 0 0 10px var(--color-cyber-blue);
 
 
 
75
  }
 
1
  @import "tailwindcss";
2
 
3
  @theme {
4
+ --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
5
+ --font-mono: 'JetBrains Mono', monospace;
6
  --color-cyber-blue: #00f2ff;
7
  --color-cyber-pink: #ff00e5;
8
  --color-cyber-dark: #050a15;
 
10
 
11
  @layer base {
12
  body {
13
+ @apply antialiased overflow-hidden h-screen;
14
  }
15
  }
16
 
17
+ /* Grille Cyber */
18
  .bg-grid {
19
  background-image:
20
  linear-gradient(to right, rgba(0, 242, 255, 0.05) 1px, transparent 1px),
21
  linear-gradient(to bottom, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
22
  background-size: 40px 40px;
 
23
  }
24
 
25
+ /* Scanner Laser */
26
  @keyframes scan {
27
  0% { transform: translateY(-100%); opacity: 0; }
28
  50% { opacity: 1; }
 
31
 
32
  .scanner-line {
33
  height: 2px;
34
+ background: linear-gradient(90deg, transparent, #00f2ff, transparent);
35
+ box-shadow: 0 0 15px #00f2ff;
36
+ animation: scan 2.5s linear infinite;
 
37
  }
38
 
39
+ /* Scrollbar Style */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  ::-webkit-scrollbar {
41
  width: 4px;
42
  }
 
44
  background: transparent;
45
  }
46
  ::-webkit-scrollbar-thumb {
47
+ background: #00f2ff;
48
  border-radius: 10px;
49
+ }
50
+
51
+ .custom-scrollbar-light::-webkit-scrollbar-thumb {
52
+ background: #000000;
53
  }