fh / src /components /SidebarClean.svelte
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
14.6 kB
<script lang="ts">
import {
currentStep,
nextStep,
prevStep,
referenceDocuments,
loadReferenceDocuments,
uploadReferenceDocument,
deleteReferenceDocument,
getDocumentContent,
isLoading,
error
} from '../stores/appStore'
import { onMount, onDestroy } from 'svelte'
let hasStoredRfps = false
let selectedCategory = 'it-staffing'
let showCategoryDropdown = false
let isUploading = false
const categories = [
{ value: 'it-staffing', label: 'IT Staffing' },
{ value: 'ai-rfps', label: 'AI RFPs' }
]
onMount(async () => {
// Load reference documents from backend on component mount
try {
await loadReferenceDocuments()
hasStoredRfps = $referenceDocuments.length > 0
console.log(`βœ… Loaded ${$referenceDocuments.length} documents from backend on page load`)
} catch (err) {
console.warn('Failed to load reference documents from backend:', err)
// Continue without backend - app should still work
hasStoredRfps = false
}
})
// Auto-refresh documents every 30 seconds to keep in sync
let refreshInterval: number;
onMount(() => {
refreshInterval = setInterval(async () => {
try {
await loadReferenceDocuments()
} catch (err) {
console.warn('Background refresh failed:', err)
}
}, 30000) // 30 seconds
})
onDestroy(() => {
if (refreshInterval) clearInterval(refreshInterval)
})
// Subscribe to reference documents changes
$: hasStoredRfps = $referenceDocuments.length > 0
async function handleReferenceUpload(event: Event) {
const target = event.target as HTMLInputElement
if (target.files) {
const files = Array.from(target.files)
isUploading = true
try {
// Upload each file to backend
for (const file of files) {
await uploadReferenceDocument(file, selectedCategory)
}
// Immediately refresh the list to show new documents
await loadReferenceDocuments()
// Reset file input
target.value = ''
console.log(`βœ… Successfully uploaded ${files.length} file(s) to backend storage`)
} catch (err) {
console.error('❌ Upload failed:', err)
} finally {
isUploading = false
}
}
}
async function handleDeleteDocument(id: string) {
if (confirm('Are you sure you want to delete this document?')) {
try {
await deleteReferenceDocument(id)
console.log('βœ… Document deleted successfully')
} catch (err) {
console.error('❌ Delete failed:', err)
}
}
}
async function viewDocumentContent(id: string) {
try {
const docWithContent = await getDocumentContent(id)
console.log('πŸ“„ Document Content:', docWithContent)
alert(`Document: ${docWithContent.name}\n\nContent Preview:\n${docWithContent.content.substring(0, 500)}${docWithContent.content.length > 500 ? '...' : ''}`)
} catch (err) {
console.error('❌ Failed to load content:', err)
}
}
function clearAllReference() {
if (confirm('Clear all reference documents from backend storage?')) {
// Delete all documents one by one
$referenceDocuments.forEach(async (doc) => {
try {
await deleteReferenceDocument(doc.id)
} catch (err) {
console.error('Failed to delete document:', err)
}
})
}
}
function getDocumentsByCategory(category: string) {
return $referenceDocuments.filter(doc => doc.category === category)
}
function formatDate(dateString: string): string {
if (!dateString) return 'Unknown date';
try {
const date = new Date(dateString);
if (isNaN(date.getTime())) return 'Unknown date';
return date.toLocaleDateString();
} catch {
return 'Unknown date';
}
}
const steps = [
{
id: 1,
title: 'Upload Current RFP',
description: 'Upload the RFP document you are responding to'
},
{
id: 2,
title: 'Add Questions',
description: 'Add questions manually or upload document'
},
{
id: 3,
title: 'AI Processing',
description: 'AI creates responses with citations'
},
{
id: 4,
title: 'Review & Edit',
description: 'Review and edit AI-generated answers'
},
{
id: 5,
title: 'Export Document',
description: 'Export professional Word document'
}
]
function goToStep(stepId: number) {
if (stepId <= $currentStep + 1) {
currentStep.set(stepId)
}
}
</script>
<div class="w-80 bg-gray-900 border-r border-gray-700 flex flex-col">
<!-- Header -->
<div class="p-6 border-b border-gray-700">
<h1 class="text-xl font-semibold text-white mb-1">RFP Management</h1>
<p class="text-sm text-gray-400">AI-Powered Response Generation</p>
</div>
<!-- Steps Navigation -->
<div class="flex-1 p-6">
<h3 class="text-sm font-medium text-gray-300 mb-4">Workflow Steps</h3>
<div class="space-y-2">
{#each steps as step}
<button
on:click={() => goToStep(step.id)}
class="w-full text-left p-4 rounded-lg border transition-colors {
$currentStep === step.id
? 'bg-blue-500/20 border-blue-500/40 text-blue-300'
: $currentStep > step.id
? 'bg-green-500/20 border-green-500/40 text-green-300 hover:bg-green-500/30'
: 'bg-gray-800 border-gray-700 text-gray-400 hover:bg-gray-700'
}"
disabled={step.id > $currentStep + 1}
>
<div class="flex items-start justify-between">
<div class="flex-1 min-w-0">
<div class="flex items-center space-x-3 mb-1">
<div class="flex items-center justify-center w-6 h-6 rounded-full text-xs font-medium {
$currentStep === step.id
? 'bg-blue-500 text-white'
: $currentStep > step.id
? 'bg-green-500 text-white'
: 'bg-gray-600 text-gray-400'
}">
{#if $currentStep > step.id}
βœ“
{:else}
{step.id}
{/if}
</div>
<h4 class="font-medium">
{step.title}
</h4>
</div>
<p class="text-sm {
$currentStep === step.id
? 'text-blue-200'
: $currentStep > step.id
? 'text-green-200'
: 'text-gray-500'
}">
{step.description}
</p>
</div>
</div>
</button>
{/each}
</div>
</div>
<!-- Progress -->
<div class="p-6 border-t border-gray-700">
<div class="flex justify-between text-sm mb-2">
<span class="text-gray-400">Progress</span>
<span class="font-medium text-gray-300">{Math.round(($currentStep / steps.length) * 100)}%</span>
</div>
<div class="w-full bg-gray-700 rounded-full h-2">
<div
class="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full transition-all duration-300"
style="width: {($currentStep / steps.length) * 100}%"
></div>
</div>
<p class="text-xs text-gray-500 mt-2">
Complete each step to build your professional RFP response.
</p>
</div>
<!-- Reference Documents Section -->
<div class="p-6 border-t border-gray-700 bg-gray-900/50">
<div class="mb-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-medium text-gray-300">Reference Documents</h3>
<div class="flex items-center space-x-2">
{#if hasStoredRfps}
<span class="text-xs text-blue-400">{$referenceDocuments.length} stored</span>
{/if}
<button
on:click={loadReferenceDocuments}
class="text-xs text-gray-400 hover:text-blue-400 px-2 py-1 rounded border border-gray-600 hover:border-blue-500 transition-colors"
disabled={$isLoading}
title="Refresh documents"
>
{#if $isLoading}
⟳
{:else}
πŸ”„
{/if}
</button>
</div>
</div>
<p class="text-xs text-gray-500 mb-3">
Upload previous RFPs for reference (stored in backend)
</p>
</div>
<!-- Backend Status -->
{#if $error}
<div class="bg-red-500/20 border border-red-500/40 rounded px-2 py-1 mb-3">
<p class="text-xs text-red-300">⚠️ {$error}</p>
</div>
{/if}
<!-- Category Selection -->
<div class="mb-3">
<label class="text-xs text-gray-400 mb-1 block">Category:</label>
<div class="relative">
<select
bind:value={selectedCategory}
class="w-full bg-gray-800 border border-gray-600 rounded px-2 py-1 text-xs text-gray-300 focus:border-blue-500 focus:outline-none"
disabled={isUploading || $isLoading}
>
{#each categories as category}
<option value={category.value}>{category.label}</option>
{/each}
</select>
</div>
</div>
<!-- Upload Area -->
<div class="relative">
<input
type="file"
multiple
accept=".pdf,.docx,.doc,.txt"
on:change={handleReferenceUpload}
disabled={isUploading || $isLoading}
class="absolute inset-0 opacity-0 cursor-pointer w-full h-full"
/>
<div class="border-2 border-dashed border-gray-600 rounded-lg p-3 text-center hover:border-blue-500/50 transition-colors {isUploading || $isLoading ? 'opacity-50' : ''}">
<div class="text-gray-400 text-xs">
{#if isUploading || $isLoading}
πŸ”„ Uploading to backend...
{:else}
πŸ“ Upload to {categories.find(c => c.value === selectedCategory)?.label}
{/if}
</div>
</div>
</div>
<!-- Reference Documents List by Category -->
{#if $referenceDocuments.length > 0}
<div class="space-y-3 max-h-48 overflow-y-auto">
{#each categories as category}
{@const categoryDocs = getDocumentsByCategory(category.value)}
{#if categoryDocs.length > 0}
<div class="bg-gray-800/50 rounded-lg p-2">
<div class="flex items-center justify-between mb-2">
<h4 class="text-xs font-medium text-gray-300">{category.label}</h4>
<span class="text-xs text-gray-500">{categoryDocs.length}</span>
</div>
<div class="space-y-1">
{#each categoryDocs as doc}
<div class="bg-gray-800 rounded p-2 text-xs">
<div class="flex items-center justify-between">
<div class="flex-1 min-w-0 cursor-pointer" on:click={() => viewDocumentContent(doc.id)}>
<div class="text-gray-300 font-medium truncate hover:text-blue-300">{doc.name}</div>
<div class="flex items-center space-x-2 text-gray-500">
<span>{(doc.size / 1024 / 1024).toFixed(1)} MB</span>
<span>β€’</span>
{#if doc.contentLength}
<span>{doc.contentLength} chars</span>
<span>β€’</span>
{/if}
<span class="text-xs px-1 py-0.5 bg-blue-600/20 text-blue-300 rounded">
{doc.fileType === '.pdf' ? 'PDF' :
doc.fileType === '.docx' || doc.fileType === '.doc' ? 'DOC' :
doc.fileType === '.txt' ? 'TXT' : 'FILE'}
</span>
<span>β€’</span>
{#if doc.processed}
<span class="text-green-400">βœ“ Processed</span>
{:else}
<span class="text-yellow-400">⏳ Processing</span>
{/if}
</div>
<div class="text-xs text-gray-600 mt-1">
Uploaded: {formatDate(doc.uploadDate)}
{#if doc.contentLength}
<span class="text-green-400">β€’ Content Available ({doc.contentLength} chars)</span>
{/if}
</div>
</div>
<button
on:click={() => handleDeleteDocument(doc.id)}
class="ml-2 w-4 h-4 bg-red-500/20 hover:bg-red-500/30 rounded text-red-400 flex items-center justify-center text-xs"
title="Delete from backend"
disabled={$isLoading}
>
Γ—
</button>
</div>
</div>
{/each}
</div>
</div>
{/if}
{/each}
<button
on:click={clearAllReference}
class="w-full text-xs text-gray-500 hover:text-gray-400 py-1"
disabled={$isLoading}
>
Clear all reference documents
</button>
<!-- Backend Info -->
{#if $referenceDocuments.length > 0}
<div class="mt-2 p-2 bg-gray-900/50 rounded text-xs text-gray-500">
πŸ“ Backend Storage: {$referenceDocuments.length} documents |
Categories: {categories.map(c => `${c.label} (${getDocumentsByCategory(c.value).length})`).filter(s => !s.includes('(0)')).join(', ')}
</div>
{:else}
<div class="mt-2 p-2 bg-gray-900/50 rounded text-xs text-gray-500">
πŸ’‘ Upload reference documents to improve AI responses
</div>
{/if}
</div>
{/if}
</div>
</div>