fh / src /components /UploadSection.svelte
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
27.2 kB
<script lang="ts">
import { rfpData, nextStep, isLoading } from '../stores/appStore'
import { parseDocument } from '../utils/documentParser'
let previousRFPFiles: FileList | null = null
let newQuestionsFile: FileList | null = null
let dragOver = false
async function handlePreviousRFPUpload() {
if (!previousRFPFiles || previousRFPFiles.length === 0) return
isLoading.set(true)
try {
const files = Array.from(previousRFPFiles)
rfpData.update(data => ({ ...data, previousRFPs: files }))
// Parse documents to extract previous answers
const extractedData = []
for (const file of files) {
const parsed = await parseDocument(file)
extractedData.push(parsed)
}
rfpData.update(data => ({ ...data, extractedData }))
} catch (error) {
console.error('Error parsing previous RFPs:', error)
} finally {
isLoading.set(false)
}
}
async function handleNewQuestionsUpload() {
if (!newQuestionsFile || newQuestionsFile.length === 0) return
isLoading.set(true)
try {
const file = newQuestionsFile[0]
rfpData.update(data => ({ ...data, newQuestions: file }))
// Parse questions from the uploaded file
const parsed = await parseDocument(file)
const questions = extractQuestions(parsed.content)
rfpData.update(data => ({ ...data, questions }))
} catch (error) {
console.error('Error parsing questions:', error)
} finally {
isLoading.set(false)
}
}
function extractQuestions(content: string): string[] {
// Simple question extraction - split by numbers or question marks
const lines = content.split('\n').filter(line => line.trim().length > 0)
return lines.filter(line =>
/^\d+\./.test(line.trim()) ||
line.includes('?') ||
/^Q\d+/.test(line.trim())
)
}
function handleDrop(event: DragEvent, type: 'previous' | 'questions') {
event.preventDefault()
dragOver = false
const files = event.dataTransfer?.files
if (files) {
if (type === 'previous') {
previousRFPFiles = files
handlePreviousRFPUpload()
} else {
newQuestionsFile = files
handleNewQuestionsUpload()
}
}
}
function handleDragOver(event: DragEvent) {
event.preventDefault()
dragOver = true
}
function handleDragLeave() {
dragOver = false
}
function canProceed(): boolean {
const data = $rfpData
return data.previousRFPs.length > 0 && data.newQuestions !== null && data.questions.length > 0
}
</script>
<script lang="ts">
import { rfpData, nextStep, isLoading } from '../stores/appStore'
import { parseDocument } from '../utils/documentParser'
let previousRFPFiles: FileList | null = null
let newQuestionsFile: FileList | null = null
let dragOver = false
async function handlePreviousRFPUpload() {
if (!previousRFPFiles || previousRFPFiles.length === 0) return
isLoading.set(true)
try {
const files = Array.from(previousRFPFiles)
rfpData.update(data => ({ ...data, previousRFPs: files }))
// Parse documents to extract previous answers
const extractedData = []
for (const file of files) {
const parsed = await parseDocument(file)
extractedData.push(parsed)
}
rfpData.update(data => ({ ...data, extractedData }))
} catch (error) {
console.error('Error parsing previous RFPs:', error)
} finally {
isLoading.set(false)
}
}
async function handleNewQuestionsUpload() {
if (!newQuestionsFile || newQuestionsFile.length === 0) return
isLoading.set(true)
try {
const file = newQuestionsFile[0]
rfpData.update(data => ({ ...data, newQuestions: file }))
// Parse questions from the uploaded file
const parsed = await parseDocument(file)
const questions = extractQuestions(parsed.content)
rfpData.update(data => ({ ...data, questions }))
} catch (error) {
console.error('Error parsing questions:', error)
} finally {
isLoading.set(false)
}
}
function extractQuestions(content: string): string[] {
// Simple question extraction - split by numbers or question marks
const lines = content.split('\n').filter(line => line.trim().length > 0)
return lines.filter(line =>
/^\d+\./.test(line.trim()) ||
line.includes('?') ||
/^Q\d+/.test(line.trim())
)
}
function handleDrop(event: DragEvent, type: 'previous' | 'questions') {
event.preventDefault()
dragOver = false
const files = event.dataTransfer?.files
if (files) {
if (type === 'previous') {
previousRFPFiles = files
handlePreviousRFPUpload()
} else {
newQuestionsFile = files
handleNewQuestionsUpload()
}
}
}
function handleDragOver(event: DragEvent) {
event.preventDefault()
dragOver = true
}
function handleDragLeave() {
dragOver = false
}
function canProceed(): boolean {
const data = $rfpData
return data.previousRFPs.length > 0 && data.newQuestions !== null && data.questions.length > 0
}
</script>
<div class="h-full flex flex-col bg-white dark:bg-slate-800">
<!-- Hero Section -->
<div class="bg-gradient-to-r from-blue-600 via-indigo-600 to-purple-600 text-white p-8">
<div class="max-w-4xl mx-auto text-center">
<h1 class="text-4xl font-bold mb-4">Upload Your RFP Documents</h1>
<p class="text-xl text-blue-100 mb-6">
Start by uploading your previous RFP responses and new questions to leverage AI-powered answer generation
</p>
<div class="flex justify-center space-x-8 text-sm">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-blue-200 rounded-full"></div>
<span>PDF & Word Support</span>
</div>
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-blue-200 rounded-full"></div>
<span>Automatic Processing</span>
</div>
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-blue-200 rounded-full"></div>
<span>Citation Tracking</span>
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="flex-1 p-8 overflow-auto">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Previous RFPs Upload -->
<div class="space-y-6">
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-2xl p-6 border border-blue-200 dark:border-blue-800">
<div class="flex items-center space-x-3 mb-4">
<div class="w-12 h-12 bg-blue-600 rounded-xl flex items-center justify-center">
<span class="text-2xl">📁</span>
</div>
<div>
<h3 class="text-xl font-bold text-slate-900 dark:text-white">Previous RFP Responses</h3>
<p class="text-blue-600 dark:text-blue-400 text-sm font-medium">Reference Documents</p>
</div>
</div>
<p class="text-slate-600 dark:text-slate-300 mb-6 leading-relaxed">
Upload your historical RFP responses (PDF or Word documents) to use as intelligent reference material for generating new answers.
</p>
<div
class="border-2 border-dashed rounded-xl p-8 text-center transition-all duration-300 {
dragOver
? 'border-blue-400 bg-blue-50 dark:bg-blue-900/20 scale-105'
: 'border-slate-300 dark:border-slate-600 hover:border-blue-400 hover:bg-slate-50 dark:hover:bg-slate-700'
}"
role="button"
tabindex="0"
on:drop={(e) => handleDrop(e, 'previous')}
on:dragover={handleDragOver}
on:dragleave={handleDragLeave}
>
<div class="space-y-4">
<div class="w-16 h-16 mx-auto bg-slate-100 dark:bg-slate-700 rounded-xl flex items-center justify-center">
<svg class="w-8 h-8 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
</div>
<div>
<p class="text-lg font-medium text-slate-900 dark:text-white mb-2">
Drop files here or browse to upload
</p>
<label class="cursor-pointer">
<span class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Select Files
</span>
<input
type="file"
class="hidden"
multiple
accept=".pdf,.doc,.docx"
bind:files={previousRFPFiles}
on:change={handlePreviousRFPUpload}
/>
</label>
<p class="text-sm text-slate-500 dark:text-slate-400 mt-2">
PDF, DOC, DOCX • Multiple files supported
</p>
</div>
</div>
</div>
{#if $rfpData.previousRFPs.length > 0}
<div class="mt-6 space-y-3">
<h4 class="font-semibold text-slate-900 dark:text-white flex items-center">
<svg class="w-4 h-4 mr-2 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Uploaded Files ({$rfpData.previousRFPs.length})
</h4>
<div class="space-y-2 max-h-32 overflow-y-auto">
{#each $rfpData.previousRFPs as file}
<div class="flex items-center justify-between p-3 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-green-100 dark:bg-green-800 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<div class="font-medium text-green-800 dark:text-green-200 text-sm">{file.name}</div>
<div class="text-green-600 dark:text-green-400 text-xs">
{(file.size / 1024 / 1024).toFixed(2)} MB
</div>
</div>
</div>
<div class="text-green-600">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
{/each}
</div>
</div>
{/if}
</div>
</div>
<!-- New Questions Upload -->
<div class="space-y-6">
<div class="bg-gradient-to-br from-indigo-50 to-purple-50 dark:from-indigo-900/20 dark:to-purple-900/20 rounded-2xl p-6 border border-indigo-200 dark:border-indigo-800">
<div class="flex items-center space-x-3 mb-4">
<div class="w-12 h-12 bg-indigo-600 rounded-xl flex items-center justify-center">
<span class="text-2xl">📋</span>
</div>
<div>
<h3 class="text-xl font-bold text-slate-900 dark:text-white">New RFP Questions</h3>
<p class="text-indigo-600 dark:text-indigo-400 text-sm font-medium">Questions to Answer</p>
</div>
</div>
<p class="text-slate-600 dark:text-slate-300 mb-6 leading-relaxed">
Upload the new RFP document containing questions that need to be answered using AI-powered analysis.
</p>
<div
class="border-2 border-dashed rounded-xl p-8 text-center transition-all duration-300 {
dragOver
? 'border-indigo-400 bg-indigo-50 dark:bg-indigo-900/20 scale-105'
: 'border-slate-300 dark:border-slate-600 hover:border-indigo-400 hover:bg-slate-50 dark:hover:bg-slate-700'
}"
role="button"
tabindex="0"
on:drop={(e) => handleDrop(e, 'questions')}
on:dragover={handleDragOver}
on:dragleave={handleDragLeave}
>
<div class="space-y-4">
<div class="w-16 h-16 mx-auto bg-slate-100 dark:bg-slate-700 rounded-xl flex items-center justify-center">
<svg class="w-8 h-8 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<p class="text-lg font-medium text-slate-900 dark:text-white mb-2">
Drop RFP document here
</p>
<label class="cursor-pointer">
<span class="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors font-medium">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Browse File
</span>
<input
type="file"
class="hidden"
accept=".pdf,.doc,.docx"
bind:files={newQuestionsFile}
on:change={handleNewQuestionsUpload}
/>
</label>
<p class="text-sm text-slate-500 dark:text-slate-400 mt-2">
PDF, DOC, DOCX • Single file
</p>
</div>
</div>
</div>
{#if $rfpData.newQuestions}
<div class="mt-6">
<div class="flex items-center justify-between p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-green-100 dark:bg-green-800 rounded-lg flex items-center justify-center">
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<div class="font-medium text-green-800 dark:text-green-200 text-sm">{$rfpData.newQuestions.name}</div>
<div class="text-green-600 dark:text-green-400 text-xs">
{($rfpData.newQuestions.size / 1024 / 1024).toFixed(2)} MB
</div>
</div>
</div>
<div class="text-green-600">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
{#if $rfpData.questions.length > 0}
<div class="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h5 class="font-semibold text-blue-900 dark:text-blue-100 mb-3 flex items-center">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
Detected Questions ({$rfpData.questions.length})
</h5>
<div class="space-y-2 max-h-32 overflow-y-auto">
{#each $rfpData.questions.slice(0, 3) as question}
<div class="text-sm text-blue-800 dark:text-blue-200 p-2 bg-white dark:bg-slate-800 rounded border border-blue-100 dark:border-blue-800">
{question}
</div>
{/each}
{#if $rfpData.questions.length > 3}
<div class="text-sm text-blue-600 dark:text-blue-400 italic text-center p-2">
+{$rfpData.questions.length - 3} more questions detected...
</div>
{/if}
</div>
</div>
{/if}
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
<!-- Loading State -->
{#if $isLoading}
<div class="absolute inset-0 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm flex items-center justify-center z-50">
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl p-8 max-w-md w-full mx-4">
<div class="text-center">
<div class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 dark:bg-blue-900/20 rounded-full mb-4">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-2">Processing Documents</h3>
<p class="text-slate-600 dark:text-slate-300 mb-4">Extracting content and analyzing structure...</p>
<div class="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2">
<div class="bg-gradient-to-r from-blue-500 to-indigo-500 h-2 rounded-full animate-pulse" style="width: 60%"></div>
</div>
</div>
</div>
</div>
{/if}
<!-- Action Bar -->
<div class="border-t border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800 p-6">
<div class="max-w-7xl mx-auto flex items-center justify-between">
<div class="flex items-center space-x-4">
<div class="flex items-center space-x-2 text-sm text-slate-600 dark:text-slate-400">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{#if canProceed()}
<span class="text-green-600 dark:text-green-400 font-medium">Ready to proceed</span>
{:else}
<span>Upload both previous RFPs and new questions to continue</span>
{/if}
</div>
</div>
<button
class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white rounded-xl font-semibold hover:from-blue-700 hover:to-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl"
disabled={!canProceed() || $isLoading}
on:click={nextStep}
>
Continue to Questions
<svg class="w-5 h-5 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
</button>
</div>
</div>
</div>
{#if $rfpData.previousRFPs.length > 0}
<div class="mt-4">
<h4 class="font-medium text-gray-900 dark:text-white mb-2">Uploaded Files:</h4>
<div class="space-y-2">
{#each $rfpData.previousRFPs as file}
<div class="flex items-center p-3 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<svg class="w-5 h-5 text-green-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="text-green-800 dark:text-green-200 font-medium">{file.name}</span>
<span class="text-green-600 dark:text-green-400 text-sm ml-auto">
{(file.size / 1024 / 1024).toFixed(2)} MB
</span>
</div>
{/each}
</div>
</div>
{/if}
</div>
<!-- New Questions Upload -->
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
2. Upload New RFP Questions
</h3>
<p class="text-gray-600 dark:text-gray-300 mb-4">
Upload the new RFP document containing questions you need to answer.
</p>
<div
class="border-2 border-dashed rounded-lg p-8 text-center transition-colors {
dragOver
? 'border-blue-400 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500'
}"
role="button"
tabindex="0"
on:drop={(e) => handleDrop(e, 'questions')}
on:dragover={handleDragOver}
on:dragleave={handleDragLeave}
>
<svg class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500 mb-4" stroke="currentColor" fill="none" viewBox="0 0 48 48">
<path d="M9 12h6m6 0h6m-6 6h6m-12 6h12m-12 6h6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="text-lg font-medium text-gray-900 dark:text-white mb-2">
Drop your RFP questions file here, or
</div>
<label class="cursor-pointer">
<span class="text-blue-600 hover:text-blue-500 font-medium">browse to upload</span>
<input
type="file"
class="hidden"
accept=".pdf,.doc,.docx"
bind:files={newQuestionsFile}
on:change={handleNewQuestionsUpload}
/>
</label>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-2">
Supports PDF, DOC, DOCX files (single file)
</p>
</div>
{#if $rfpData.newQuestions}
<div class="mt-4">
<div class="flex items-center p-3 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<svg class="w-5 h-5 text-green-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span class="text-green-800 dark:text-green-200 font-medium">{$rfpData.newQuestions.name}</span>
<span class="text-green-600 dark:text-green-400 text-sm ml-auto">
{($rfpData.newQuestions.size / 1024 / 1024).toFixed(2)} MB
</span>
</div>
{#if $rfpData.questions.length > 0}
<div class="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h5 class="font-medium text-blue-900 dark:text-blue-100 mb-2">
Detected Questions ({$rfpData.questions.length}):
</h5>
<div class="max-h-32 overflow-y-auto space-y-1">
{#each $rfpData.questions.slice(0, 3) as question}
<div class="text-sm text-blue-800 dark:text-blue-200 truncate">
{question}
</div>
{/each}
{#if $rfpData.questions.length > 3}
<div class="text-sm text-blue-600 dark:text-blue-400 italic">
+{$rfpData.questions.length - 3} more questions...
</div>
{/if}
</div>
</div>
{/if}
</div>
{/if}
</div>
<!-- Loading State -->
{#if $isLoading}
<div class="flex items-center justify-center p-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span class="ml-3 text-gray-600 dark:text-gray-300">Processing documents...</span>
</div>
{/if}
<!-- Continue Button -->
<div class="flex justify-end">
<button
class="px-6 py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
disabled={!canProceed() || $isLoading}
on:click={nextStep}
>
Continue to Question Review
<svg class="w-5 h-5 ml-2 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</div>
</div>
</div>