Spaces:
Running
Running
Commit
·
e5b1920
1
Parent(s):
bb50e4c
feat: enhance App.jsx with file attachment and PDF/text parsing
Browse files- Added support for attaching .txt and .pdf files in the chat interface
- Implemented PDF parsing using pdfjs-dist for extracting text content
- Updated UI to show attached file name and restrict file types
- Improved input resizing and message handling logic
- Updated dependencies in package.json for new features
- .gitignore +1 -0
- package-lock.json +0 -0
- package.json +1 -0
- src/App.jsx +54 -6
.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
node_modules
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
| 13 |
"@huggingface/transformers": "3.7.5",
|
| 14 |
"dompurify": "^3.1.2",
|
| 15 |
"marked": "^12.0.2",
|
|
|
|
| 16 |
"react": "^18.3.1",
|
| 17 |
"react-dom": "^18.3.1"
|
| 18 |
},
|
|
|
|
| 13 |
"@huggingface/transformers": "3.7.5",
|
| 14 |
"dompurify": "^3.1.2",
|
| 15 |
"marked": "^12.0.2",
|
| 16 |
+
"pdfjs-dist": "^5.4.296",
|
| 17 |
"react": "^18.3.1",
|
| 18 |
"react-dom": "^18.3.1"
|
| 19 |
},
|
src/App.jsx
CHANGED
|
@@ -32,12 +32,39 @@ function App() {
|
|
| 32 |
const [messages, setMessages] = useState([]);
|
| 33 |
const [tps, setTps] = useState(null);
|
| 34 |
const [numTokens, setNumTokens] = useState(null);
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
setTps(null);
|
| 39 |
setIsRunning(true);
|
| 40 |
setInput("");
|
|
|
|
| 41 |
}
|
| 42 |
|
| 43 |
function onInterrupt() {
|
|
@@ -326,10 +353,10 @@ function App() {
|
|
| 326 |
</div>
|
| 327 |
)}
|
| 328 |
|
| 329 |
-
<div className="mt-2 border dark:bg-gray-700 rounded-lg w-[600px] max-w-[80%] max-h-[200px] mx-auto relative mb-3 flex">
|
| 330 |
<textarea
|
| 331 |
ref={textareaRef}
|
| 332 |
-
className="scrollbar-thin w-[
|
| 333 |
placeholder="Type your message..."
|
| 334 |
type="text"
|
| 335 |
rows={1}
|
|
@@ -349,11 +376,29 @@ function App() {
|
|
| 349 |
}}
|
| 350 |
onInput={(e) => setInput(e.target.value)}
|
| 351 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
{isRunning ? (
|
| 353 |
<div className="cursor-pointer" onClick={onInterrupt}>
|
| 354 |
<StopIcon className="h-8 w-8 p-1 rounded-md text-gray-800 dark:text-gray-100 absolute right-3 bottom-3" />
|
| 355 |
</div>
|
| 356 |
-
) : input.length > 0 ? (
|
| 357 |
<div className="cursor-pointer" onClick={() => onEnter(input)}>
|
| 358 |
<ArrowRightIcon
|
| 359 |
className={`h-8 w-8 p-1 bg-gray-800 dark:bg-gray-100 text-white dark:text-black rounded-md absolute right-3 bottom-3`}
|
|
@@ -366,6 +411,9 @@ function App() {
|
|
| 366 |
/>
|
| 367 |
</div>
|
| 368 |
)}
|
|
|
|
|
|
|
|
|
|
| 369 |
</div>
|
| 370 |
|
| 371 |
<p className="text-xs text-gray-400 text-center mb-3">
|
|
|
|
| 32 |
const [messages, setMessages] = useState([]);
|
| 33 |
const [tps, setTps] = useState(null);
|
| 34 |
const [numTokens, setNumTokens] = useState(null);
|
| 35 |
+
const [attachedFile, setAttachedFile] = useState(null);
|
| 36 |
+
|
| 37 |
+
async function onEnter(message) {
|
| 38 |
+
let fileText = "";
|
| 39 |
+
if (attachedFile) {
|
| 40 |
+
if (attachedFile.name.endsWith(".txt")) {
|
| 41 |
+
fileText = await attachedFile.text();
|
| 42 |
+
} else if (attachedFile.name.endsWith(".pdf")) {
|
| 43 |
+
// Dynamically import pdfjs-dist
|
| 44 |
+
const pdfjsLib = await import("pdfjs-dist/build/pdf");
|
| 45 |
+
const workerSrc = (await import("pdfjs-dist/build/pdf.worker?url")).default;
|
| 46 |
+
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
|
| 47 |
+
const arrayBuffer = await attachedFile.arrayBuffer();
|
| 48 |
+
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
| 49 |
+
let pdfText = "";
|
| 50 |
+
for (let i = 1; i <= pdf.numPages; i++) {
|
| 51 |
+
const page = await pdf.getPage(i);
|
| 52 |
+
const content = await page.getTextContent();
|
| 53 |
+
pdfText += content.items.map(item => item.str).join(" ") + "\n";
|
| 54 |
+
}
|
| 55 |
+
fileText = pdfText;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
let fullPrompt = message;
|
| 59 |
+
if (fileText) {
|
| 60 |
+
fullPrompt += "\n\n--- File Content ---\n" + fileText;
|
| 61 |
+
}
|
| 62 |
+
let userMsg = { role: "user", content: fullPrompt };
|
| 63 |
+
setMessages((prev) => [...prev, userMsg]);
|
| 64 |
setTps(null);
|
| 65 |
setIsRunning(true);
|
| 66 |
setInput("");
|
| 67 |
+
setAttachedFile(null);
|
| 68 |
}
|
| 69 |
|
| 70 |
function onInterrupt() {
|
|
|
|
| 353 |
</div>
|
| 354 |
)}
|
| 355 |
|
| 356 |
+
<div className="mt-2 border dark:bg-gray-700 rounded-lg w-[600px] max-w-[80%] max-h-[200px] mx-auto relative mb-3 flex items-center gap-2">
|
| 357 |
<textarea
|
| 358 |
ref={textareaRef}
|
| 359 |
+
className="scrollbar-thin w-[420px] dark:bg-gray-700 px-3 py-4 rounded-lg bg-transparent border-none outline-none text-gray-800 disabled:text-gray-400 dark:text-gray-200 placeholder-gray-500 dark:placeholder-gray-400 disabled:placeholder-gray-200 resize-none disabled:cursor-not-allowed"
|
| 360 |
placeholder="Type your message..."
|
| 361 |
type="text"
|
| 362 |
rows={1}
|
|
|
|
| 376 |
}}
|
| 377 |
onInput={(e) => setInput(e.target.value)}
|
| 378 |
/>
|
| 379 |
+
<label className="flex items-center px-2 py-2 bg-blue-100 text-blue-700 rounded cursor-pointer hover:bg-blue-200">
|
| 380 |
+
📎 Attach
|
| 381 |
+
<input
|
| 382 |
+
type="file"
|
| 383 |
+
accept=".txt,.pdf"
|
| 384 |
+
className="hidden"
|
| 385 |
+
onChange={e => {
|
| 386 |
+
const file = e.target.files[0];
|
| 387 |
+
if (file && ![".txt", ".pdf"].some(ext => file.name.toLowerCase().endsWith(ext))) {
|
| 388 |
+
alert("Only .txt and .pdf files are allowed.");
|
| 389 |
+
e.target.value = "";
|
| 390 |
+
return;
|
| 391 |
+
}
|
| 392 |
+
setAttachedFile(file);
|
| 393 |
+
}}
|
| 394 |
+
disabled={status !== "ready"}
|
| 395 |
+
/>
|
| 396 |
+
</label>
|
| 397 |
{isRunning ? (
|
| 398 |
<div className="cursor-pointer" onClick={onInterrupt}>
|
| 399 |
<StopIcon className="h-8 w-8 p-1 rounded-md text-gray-800 dark:text-gray-100 absolute right-3 bottom-3" />
|
| 400 |
</div>
|
| 401 |
+
) : input.length > 0 || attachedFile ? (
|
| 402 |
<div className="cursor-pointer" onClick={() => onEnter(input)}>
|
| 403 |
<ArrowRightIcon
|
| 404 |
className={`h-8 w-8 p-1 bg-gray-800 dark:bg-gray-100 text-white dark:text-black rounded-md absolute right-3 bottom-3`}
|
|
|
|
| 411 |
/>
|
| 412 |
</div>
|
| 413 |
)}
|
| 414 |
+
{attachedFile && (
|
| 415 |
+
<span className="ml-2 text-xs text-gray-600">{attachedFile.name}</span>
|
| 416 |
+
)}
|
| 417 |
</div>
|
| 418 |
|
| 419 |
<p className="text-xs text-gray-400 text-center mb-3">
|