-
Notifications
You must be signed in to change notification settings - Fork 2
feat: file truncation and empty PDF warnings for user testing #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -84,6 +84,8 @@ const t = { | |||||||
| attach: "Attach file", | ||||||||
| parsing: "Reading files...", | ||||||||
| unsupported: "Supported: PDF, DOCX, Excel, and text files", | ||||||||
| truncated: (name: string) => `"${name}" is too long - only the first ~20 pages were included`, | ||||||||
| empty: (name: string) => `"${name}" appears to be scanned/empty - no text could be extracted`, | ||||||||
| settings: "Settings", | ||||||||
| signOut: "Sign Out", | ||||||||
| tooltips: { | ||||||||
|
|
@@ -118,6 +120,8 @@ const t = { | |||||||
| attach: "파일 첨부", | ||||||||
| parsing: "파일 읽는 중...", | ||||||||
| unsupported: "지원 형식: PDF, DOCX, Excel, 텍스트 파일", | ||||||||
| truncated: (name: string) => `"${name}" 파일이 너무 길어 앞부분만 포함되었습니다`, | ||||||||
| empty: (name: string) => `"${name}" 파일에서 텍스트를 추출할 수 없습니다 (스캔 문서일 수 있음)`, | ||||||||
| settings: "설정", | ||||||||
| signOut: "로그아웃", | ||||||||
| tooltips: { | ||||||||
|
|
@@ -315,19 +319,27 @@ export default function Home() { | |||||||
| // Auto-dismiss file error | ||||||||
| useEffect(() => { | ||||||||
| if (!fileError) return | ||||||||
| const timer = setTimeout(() => setFileError(null), 3000) | ||||||||
| const timer = setTimeout(() => setFileError(null), 5000) | ||||||||
| return () => clearTimeout(timer) | ||||||||
| }, [fileError]) | ||||||||
|
|
||||||||
| const buildPromptWithFiles = async (): Promise<string | null> => { | ||||||||
| const buildPromptWithFiles = async (): Promise<{ text: string | null; fileWarnings: string[] }> => { | ||||||||
| let messageText = prompt.trim() | ||||||||
| const fileWarnings: string[] = [] | ||||||||
|
|
||||||||
| if (files.length > 0) { | ||||||||
| const results = await Promise.allSettled( | ||||||||
| files.map(async (af) => { | ||||||||
| const content = await parseFile(af.file) | ||||||||
| if (content && !content.startsWith("[Unsupported")) { | ||||||||
| return `--- File: ${af.file.name} ---\n${content}` | ||||||||
| const parsed = await parseFile(af.file) | ||||||||
|
Comment on lines
+328
to
+333
|
||||||||
| if (parsed.warning === "empty") { | ||||||||
| fileWarnings.push(t[locale].empty(af.file.name)) | ||||||||
| return null | ||||||||
| } | ||||||||
| if (parsed.warning === "truncated") { | ||||||||
| fileWarnings.push(t[locale].truncated(af.file.name)) | ||||||||
| } | ||||||||
| if (parsed.text && !parsed.text.startsWith("[Unsupported")) { | ||||||||
| return `--- File: ${af.file.name} ---\n${parsed.text}` | ||||||||
| } | ||||||||
| return null | ||||||||
| }) | ||||||||
|
|
@@ -351,15 +363,15 @@ export default function Home() { | |||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| return messageText || null | ||||||||
| return { text: messageText || null, fileWarnings } | ||||||||
| } | ||||||||
|
|
||||||||
| const handleSubmit = async () => { | ||||||||
| if (isParsing || (!prompt.trim() && files.length === 0)) return | ||||||||
|
|
||||||||
| setIsParsing(true) | ||||||||
| try { | ||||||||
| const messageText = await buildPromptWithFiles() | ||||||||
| const { text: messageText, fileWarnings } = await buildPromptWithFiles() | ||||||||
| if (!messageText) return | ||||||||
|
|
||||||||
| if (shouldShowLoginGate(!!session?.user)) { | ||||||||
|
|
@@ -382,6 +394,9 @@ export default function Home() { | |||||||
| locale, | ||||||||
| } | ||||||||
| sessionStorage.setItem("quorum_config", JSON.stringify(config)) | ||||||||
|
||||||||
| sessionStorage.setItem("quorum_config", JSON.stringify(config)) | |
| sessionStorage.setItem("quorum_config", JSON.stringify(config)) | |
| sessionStorage.removeItem("quorum_file_warnings") |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buildPromptWithFiles now collects fileWarnings, but handleSubmit never displays them on the homepage when submission is blocked (e.g., !messageText early return or the login gate path). This means users can upload a scanned/empty PDF and get no warning, and warnings are also lost when savePendingDebate is used. Consider setting fileError from fileWarnings before any early return, and persisting fileWarnings alongside the pending debate (or otherwise ensuring the chat page can still show them after login).
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,10 +6,11 @@ | |||||
| import { Send, Square, Paperclip, X, FileText, File, Loader2 } from "lucide-react" | ||||||
| import { cn } from "@/lib/utils" | ||||||
| import { parseFile, SUPPORTED_EXTENSIONS } from "@/lib/file-parser" | ||||||
| import type { ParseWarning } from "@/lib/file-parser" | ||||||
|
Check warning on line 9 in src/components/MessageInput.tsx
|
||||||
|
||||||
| import type { ParseWarning } from "@/lib/file-parser" |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warnings is being mutated (via push) from within the async callbacks passed to Promise.allSettled. This makes the warning order dependent on parse completion timing, and the subsequent warnings.join("\n") will not render line breaks in HTML unless the toast uses white-space: pre-line (otherwise newlines collapse). Prefer returning warnings alongside each parse result (then flatMap in the original file order), and render as a list / join with a visible delimiter or apply a whitespace-pre-line class.
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warnings.join('\n') will not render as multiple lines in the toast because the warning is displayed as plain text in a <div> (HTML collapses newlines). If multiple files trigger warnings, they’ll appear as a single wrapped line. Consider either rendering warnings as a list / inserting <br />s, or adding a whitespace-pre-line (or similar) style to the warning container, or joining with a visible delimiter (e.g., •).
| if (warnings.length > 0) setFileError(warnings.join('\n')) | |
| if (warnings.length > 0) setFileError(warnings.join(' • ')) |
Check warning on line 225 in src/components/MessageInput.tsx
GitHub Actions / ci
Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
Check warning on line 225 in src/components/MessageInput.tsx
GitHub Actions / ci
Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,20 +7,31 @@ const MAX_FILE_CHARS = 50000 | |
|
|
||
| export const SUPPORTED_EXTENSIONS = new Set(["pdf", "docx", "xlsx", "xls", "txt", "md", "csv"]) | ||
|
|
||
| export async function parseFile(file: File): Promise<string> { | ||
| export type ParseWarning = "truncated" | "empty" | ||
|
|
||
| export interface ParseResult { | ||
| text: string | ||
| warning?: ParseWarning | ||
| } | ||
|
|
||
| export async function parseFile(file: File): Promise<ParseResult> { | ||
| const ext = file.name.split('.').pop()?.toLowerCase() ?? '' | ||
|
|
||
| let result: string | ||
| if (ext === 'pdf') result = await parsePDF(file) | ||
| else if (ext === 'docx') result = await parseDOCX(file) | ||
| else if (ext === 'xlsx' || ext === 'xls') result = await parseExcel(file) | ||
| else if (ext === 'txt' || ext === 'md' || ext === 'csv') result = await parseText(file) | ||
| else return `[Unsupported file type: ${file.name}]` | ||
| else return { text: `[Unsupported file type: ${file.name}]` } | ||
|
|
||
| if (!result.trim()) { | ||
| return { text: '', warning: 'empty' } | ||
| } | ||
|
|
||
| if (result.length > MAX_FILE_CHARS) { | ||
| return result.slice(0, MAX_FILE_CHARS) + '\n[...file truncated]' | ||
| return { text: result.slice(0, MAX_FILE_CHARS) + '\n[...file truncated]', warning: 'truncated' } | ||
| } | ||
|
Comment on lines
+27
to
33
|
||
| return result | ||
| return { text: result } | ||
| } | ||
|
|
||
| async function parseText(file: File): Promise<string> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON.parse(warningsRaw).join("\n")assumes the stored value parses to an array. If it parses to a non-array (or array contains non-strings), this will throw and warnings will be silently dropped. Consider validating withArray.isArray(...)and string-coercing/filtering elements before joining. Also note that\nwon’t render as line breaks in the toast unless you applywhite-space: pre-line(or render as a list).