fh / src /components /AddQuestions.svelte
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
15.1 kB
<script lang="ts">
import { questions, nextStep, currentStep } from '../stores/appStore'
import { parseDocument } from '../utils/documentParser'
import { onMount, onDestroy } from 'svelte'
let dragActive = false
let questionText = ''
let isProcessing = false
let processingStatus = ''
let uploadedFiles: File[] = []
let currentMethod: 'paste' | 'upload' = 'upload'
let extractedContent = ''
let previousStep = $currentStep
// Auto-clear functionality
onMount(() => {
// Clear questions when component mounts (page refresh)
if ($questions.length > 0) {
console.log('Clearing questions on component mount (page refresh)')
clearAllQuestions()
}
})
// Watch for step changes
$: if ($currentStep !== previousStep && previousStep !== undefined) {
if ($currentStep !== 2) { // If we're leaving step 2 (Add Questions)
console.log('Clearing questions due to step change')
clearAllQuestions()
}
previousStep = $currentStep
}
async function handleFileUpload(event: Event) {
const target = event.target as HTMLInputElement
if (target.files) {
await processFiles(Array.from(target.files))
}
}
async function processFiles(files: File[]) {
isProcessing = true
processingStatus = 'Processing RFP document...'
uploadedFiles = [...uploadedFiles, ...files]
try {
for (const file of files) {
processingStatus = `Extracting content from ${file.name}...`
const content = await parseDocument(file)
extractedContent = content
processingStatus = 'Identifying questions in document...'
// Enhanced question extraction
const extractedQuestions = extractQuestions(content)
questions.update(current => [...current, ...extractedQuestions])
processingStatus = `Found ${extractedQuestions.length} questions in RFP document`
}
} catch (error) {
console.error('Error processing files:', error)
processingStatus = 'Error processing document. Please check format and try again.'
} finally {
setTimeout(() => {
isProcessing = false
processingStatus = ''
}, 2000)
}
}
function extractQuestions(content: string): any[] {
const questions = []
console.log('Original content:', content)
// SIMPLE APPROACH: Just split by "number. " pattern
// Remove any leading/trailing whitespace and normalize
const cleanContent = content.trim()
// Split using a simple approach - find all "number. " patterns
const parts = cleanContent.split(/(?=\d+\.\s)/)
console.log('Split parts:', parts)
for (let i = 0; i < parts.length; i++) {
const part = parts[i].trim()
if (!part) continue
// Extract the question number and text
const match = part.match(/^(\d+)\.\s*(.+)$/)
if (match) {
const questionNumber = match[1]
let questionText = match[2].trim()
// Remove any trailing numbers that might be part of the next question
questionText = questionText.replace(/\s+\d+\.\s*.*$/, '')
console.log(`Extracted Q${questionNumber}: "${questionText}"`)
if (questionText.length > 3) {
questions.push({
id: `q-split-${Date.now()}-${i}`,
text: questionText,
selected: true,
priority: 'high' as const,
source: 'split'
})
}
}
}
// FALLBACK: If the above didn't work, try manual character-by-character parsing
if (questions.length <= 1 && cleanContent.includes('1.') && cleanContent.includes('2.')) {
console.log('Using fallback manual parsing...')
questions.length = 0 // Clear any previous attempts
const questionParts = []
let currentQuestion = ''
let i = 0
while (i < cleanContent.length) {
const char = cleanContent[i]
const nextFew = cleanContent.substring(i, i + 10)
// Check if we're at the start of a new question (digit followed by period and space)
const questionStart = nextFew.match(/^(\d+)\.\s/)
if (questionStart && currentQuestion.trim()) {
// Save the previous question
questionParts.push(currentQuestion.trim())
currentQuestion = ''
}
currentQuestion += char
i++
}
// Don't forget the last question
if (currentQuestion.trim()) {
questionParts.push(currentQuestion.trim())
}
console.log('Manual parsing result:', questionParts)
// Process each part
questionParts.forEach((part, index) => {
const match = part.match(/^(\d+)\.\s*(.+)$/)
if (match) {
const questionText = match[2].trim()
console.log(`Manual Q${match[1]}: "${questionText}"`)
questions.push({
id: `q-manual-${Date.now()}-${index}`,
text: questionText,
selected: true,
priority: 'high' as const,
source: 'manual'
})
}
})
}
console.log(`Final result: ${questions.length} questions extracted`)
return questions
}
function handleDrop(event: DragEvent) {
event.preventDefault()
dragActive = false
if (event.dataTransfer?.files) {
processFiles(Array.from(event.dataTransfer.files))
}
}
function handleDragOver(event: DragEvent) {
event.preventDefault()
dragActive = true
}
function handleDragLeave() {
dragActive = false
}
function addManualQuestion() {
if (questionText.trim()) {
// Use the extraction function to split multiple questions
const extractedQuestions = extractQuestions(questionText.trim())
if (extractedQuestions.length > 0) {
// Add all extracted questions
questions.update(current => [...current, ...extractedQuestions])
} else {
// Fallback: add as single question if extraction fails
questions.update(current => [...current, {
id: `manual-${Date.now()}`,
text: questionText.trim(),
selected: true,
priority: 'high' as const,
source: 'manual'
}])
}
questionText = ''
}
}
function removeFile(index: number) {
uploadedFiles = uploadedFiles.filter((_, i) => i !== index)
}
function removeQuestion(id: string) {
questions.update(current => current.filter(q => q.id !== id))
}
function clearAllQuestions() {
questions.set([])
uploadedFiles = []
extractedContent = ''
questionText = ''
}
function proceedToAnswers() {
if ($questions.length > 0) {
// Skip selection step and go directly to AI Processing (step 3)
nextStep() // This will go to step 3 (AI Processing)
}
}
</script>
<div class="max-w-4xl mx-auto space-y-6">
<!-- Method Selection -->
<div class="bg-gray-800 border border-gray-600 rounded-lg p-6">
<h2 class="text-xl font-semibold text-white mb-4">Add RFP Questions</h2>
<div class="flex gap-4 mb-6">
<button
class="px-4 py-2 rounded border {currentMethod === 'upload' ? 'bg-blue-500/20 border-blue-500 text-blue-300' : 'bg-gray-700 border-gray-600 text-gray-300'}"
on:click={() => currentMethod = 'upload'}
>
Upload Document
</button>
<button
class="px-4 py-2 rounded border {currentMethod === 'paste' ? 'bg-blue-500/20 border-blue-500 text-blue-300' : 'bg-gray-700 border-gray-600 text-gray-300'}"
on:click={() => currentMethod = 'paste'}
>
Manual Entry
</button>
</div>
{#if currentMethod === 'upload'}
<!-- File Upload Section -->
<div class="space-y-4">
<div
class="border-2 border-dashed border-gray-600 rounded-lg p-8 text-center transition-colors bg-gray-800
{dragActive ? 'border-blue-500 bg-blue-500/10' : 'hover:border-blue-500/50'}
{isProcessing ? 'opacity-60' : ''}"
on:drop={handleDrop}
on:dragover={handleDragOver}
on:dragleave={handleDragLeave}
>
{#if isProcessing}
<div class="space-y-3">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<p class="text-blue-400 font-medium">{processingStatus}</p>
{#if processingStatus.includes('Found')}
<div class="text-green-400 text-sm">✓ Processing complete</div>
{/if}
</div>
{:else}
<div class="space-y-3">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div>
<p class="text-gray-200 font-medium">Drop RFP documents here</p>
<p class="text-gray-400 text-sm">or</p>
<label class="inline-block px-4 py-2 bg-blue-600 text-white rounded cursor-pointer hover:bg-blue-700">
Browse Files
<input type="file" class="hidden" accept=".pdf,.docx,.doc" multiple on:change={handleFileUpload}>
</label>
</div>
<p class="text-xs text-gray-500">Supports PDF, DOCX, DOC files</p>
</div>
{/if}
</div>
<!-- Uploaded Files List -->
{#if uploadedFiles.length > 0}
<div class="bg-gray-700/50 rounded-lg p-4">
<h3 class="font-medium text-gray-200 mb-3">Uploaded Documents</h3>
<div class="space-y-2">
{#each uploadedFiles as file, index}
<div class="flex items-center justify-between bg-gray-800 p-3 rounded border border-gray-600">
<div class="flex items-center space-x-3">
<svg class="h-5 w-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
</svg>
<div>
<p class="font-medium text-gray-200">{file.name}</p>
<p class="text-sm text-gray-400">{(file.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
</div>
<button
class="text-red-400 hover:text-red-300"
on:click={() => removeFile(index)}
>
Remove
</button>
</div>
{/each}
</div>
</div>
{/if}
</div>
{:else}
<!-- Manual Entry Section -->
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-200 mb-2">
Enter Questions (one per line or numbered)
</label>
<textarea
bind:value={questionText}
placeholder="Example:
1. What is your company's experience with similar projects?
2. How do you ensure data security and compliance?
3. What is your implementation timeline?"
class="w-full h-32 p-3 border border-gray-600 bg-gray-800 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none placeholder-gray-500"
/>
</div>
<button
on:click={addManualQuestion}
disabled={!questionText.trim()}
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
Add Question
</button>
</div>
{/if}
</div>
<!-- Questions Preview -->
{#if $questions.length > 0}
<div class="bg-gray-800 border border-gray-600 rounded-lg p-6">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-lg font-semibold text-white">Extracted Questions</h3>
<p class="text-sm text-gray-400">{$questions.length} questions found</p>
</div>
<div class="flex items-center space-x-2">
<button
on:click={clearAllQuestions}
class="px-3 py-1 bg-red-600 text-white text-sm rounded hover:bg-red-700 transition-colors"
title="Clear all questions"
>
Clear All
</button>
<button
on:click={proceedToAnswers}
class="px-6 py-2 bg-green-600 text-white rounded hover:bg-green-700"
>
Generate AI Answers
</button>
</div>
</div>
<div class="space-y-3 max-h-96 overflow-y-auto">
{#each $questions as question, index}
<div class="border border-gray-600 bg-gray-700/50 rounded-lg p-4">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center space-x-2 mb-2">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-500/20 text-blue-300">
Q{index + 1}
</span>
{#if question.source}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-600 text-gray-300">
{question.source}
</span>
{/if}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium
{question.priority === 'high' ? 'bg-red-500/20 text-red-300' :
question.priority === 'medium' ? 'bg-blue-500/20 text-blue-300' :
'bg-green-500/20 text-green-300'}">
{question.priority}
</span>
</div>
<p class="text-gray-200">{question.text}</p>
</div>
<button
on:click={() => removeQuestion(question.id)}
class="ml-4 text-red-400 hover:text-red-300"
title="Remove question"
>
×
</button>
</div>
</div>
{/each}
</div>
</div>
{/if}
</div>