Spaces:
Sleeping
Sleeping
| <script lang="ts"> | |
| import { rfpData, nextStep, prevStep, isLoading } from '../stores/appStore' | |
| import { suggestionEngine } from '../utils/suggestionEngine' | |
| import type { RFPAnswer } from '../stores/appStore' | |
| let editingAnswer: string | null = null | |
| let editContent: string = '' | |
| let suggestions: any = null | |
| let showSuggestions: Record<string, boolean> = {} | |
| $: answers = $rfpData.answers | |
| $: finalizedCount = answers.filter(a => a.isFinalized).length | |
| function startEdit(answerId: string) { | |
| editingAnswer = answerId | |
| const answer = answers.find(a => a.id === answerId) | |
| editContent = answer?.finalAnswer || '' | |
| // Generate suggestions for this answer | |
| if (answer) { | |
| suggestions = suggestionEngine.analyzeAnswer(answer.question, answer.finalAnswer); | |
| } | |
| } | |
| function saveEdit() { | |
| if (!editingAnswer) return | |
| rfpData.update(data => ({ | |
| ...data, | |
| answers: data.answers.map(answer => | |
| answer.id === editingAnswer | |
| ? { ...answer, finalAnswer: editContent } | |
| : answer | |
| ) | |
| })) | |
| editingAnswer = null | |
| editContent = '' | |
| suggestions = null | |
| } | |
| function cancelEdit() { | |
| editingAnswer = null | |
| editContent = '' | |
| suggestions = null | |
| } | |
| function toggleSuggestions(answerId: string) { | |
| showSuggestions[answerId] = !showSuggestions[answerId]; | |
| if (showSuggestions[answerId] && !suggestions) { | |
| const answer = answers.find(a => a.id === answerId); | |
| if (answer) { | |
| suggestions = suggestionEngine.analyzeAnswer(answer.question, answer.finalAnswer); | |
| } | |
| } | |
| } | |
| function applySuggestion(suggestion: string) { | |
| if (editContent && !editContent.includes(suggestion)) { | |
| editContent = editContent + '\n\n' + suggestion; | |
| } | |
| } | |
| function getQualityColor(score: number): string { | |
| if (score >= 80) return 'text-green-400'; | |
| if (score >= 60) return 'text-yellow-400'; | |
| return 'text-red-400'; | |
| } | |
| function getQualityIcon(score: number): string { | |
| if (score >= 80) return '✅'; | |
| if (score >= 60) return '⚠️'; | |
| return '❌'; | |
| } | |
| function toggleFinalized(answerId: string) { | |
| rfpData.update(data => ({ | |
| ...data, | |
| answers: data.answers.map(answer => | |
| answer.id === answerId | |
| ? { ...answer, isFinalized: !answer.isFinalized } | |
| : answer | |
| ) | |
| })) | |
| } | |
| function canProceed(): boolean { | |
| return answers.length > 0 && answers.every(a => a.isFinalized) | |
| } | |
| function getAnswerQuality(answer: RFPAnswer): { color: string; label: string } { | |
| const hasCitations = answer.citations && answer.citations.length > 0 | |
| const hasContent = answer.finalAnswer && answer.finalAnswer.length > 100 | |
| if (hasCitations && hasContent) { | |
| return { color: 'green', label: 'High Quality' } | |
| } else if (hasContent) { | |
| return { color: 'yellow', label: 'Good' } | |
| } else { | |
| return { color: 'red', label: 'Needs Improvement' } | |
| } | |
| } | |
| </script> | |
| <div class="max-w-7xl mx-auto"> | |
| <div class="bg-gray-800 border border-gray-600 rounded-lg shadow-lg p-8"> | |
| <h2 class="text-3xl font-bold text-white mb-8 text-center"> | |
| Review and Edit AI-Generated Answers | |
| </h2> | |
| <!-- Progress Summary --> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8"> | |
| <div class="bg-blue-900/20 p-4 rounded-lg border border-blue-800"> | |
| <div class="text-2xl font-bold text-blue-400">{answers.length}</div> | |
| <div class="text-blue-200">Total Answers</div> | |
| </div> | |
| <div class="bg-green-900/20 p-4 rounded-lg border border-green-800"> | |
| <div class="text-2xl font-bold text-green-400">{finalizedCount}</div> | |
| <div class="text-green-200">Finalized</div> | |
| </div> | |
| <div class="bg-orange-900/20 p-4 rounded-lg border border-orange-800"> | |
| <div class="text-2xl font-bold text-orange-400">{answers.length - finalizedCount}</div> | |
| <div class="text-orange-200">Pending Review</div> | |
| </div> | |
| <div class="bg-purple-900/20 p-4 rounded-lg border border-purple-800"> | |
| <div class="text-2xl font-bold text-purple-400"> | |
| {Math.round((finalizedCount / answers.length) * 100) || 0}% | |
| </div> | |
| <div class="text-purple-200">Complete</div> | |
| </div> | |
| </div> | |
| <!-- Answers List --> | |
| <div class="space-y-6"> | |
| {#each answers as answer, index} | |
| {@const quality = getAnswerQuality(answer)} | |
| <div class="border border-gray-600 rounded-lg overflow-hidden"> | |
| <!-- Answer Header --> | |
| <div class="bg-gray-700 p-4 border-b border-gray-600"> | |
| <div class="flex items-start justify-between"> | |
| <div class="flex-1"> | |
| <h3 class="text-lg font-semibold text-white mb-2"> | |
| Question {index + 1} | |
| </h3> | |
| <p class="text-gray-300 mb-3"> | |
| {answer.question} | |
| </p> | |
| <div class="flex items-center space-x-4"> | |
| <!-- Quality Indicator --> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 rounded-full mr-2 { | |
| quality.color === 'green' ? 'bg-green-500' : | |
| quality.color === 'yellow' ? 'bg-yellow-500' : 'bg-red-500' | |
| }"></div> | |
| <span class="text-sm font-medium { | |
| quality.color === 'green' ? 'text-green-700 dark:text-green-300' : | |
| quality.color === 'yellow' ? 'text-yellow-700 dark:text-yellow-300' : 'text-red-700 dark:text-red-300' | |
| }"> | |
| {quality.label} | |
| </span> | |
| </div> | |
| <!-- Citations Count --> | |
| {#if answer.citations && answer.citations.length > 0} | |
| <div class="flex items-center text-sm text-gray-600 dark:text-gray-400"> | |
| <svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.746 0 3.332.477 4.5 1.253v13C19.832 18.477 18.246 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" /> | |
| </svg> | |
| {answer.citations.length} citation{answer.citations.length !== 1 ? 's' : ''} | |
| </div> | |
| {/if} | |
| </div> | |
| </div> | |
| <!-- Finalized Toggle --> | |
| <div class="flex items-center space-x-3"> | |
| <label class="flex items-center cursor-pointer"> | |
| <input | |
| type="checkbox" | |
| class="sr-only" | |
| checked={answer.isFinalized} | |
| on:change={() => toggleFinalized(answer.id)} | |
| /> | |
| <div class="relative"> | |
| <div class="w-10 h-6 bg-gray-200 dark:bg-gray-600 rounded-full shadow-inner transition-colors { | |
| answer.isFinalized ? 'bg-green-400' : '' | |
| }"></div> | |
| <div class="absolute w-4 h-4 bg-white rounded-full shadow -left-0 -top-0 transition-transform { | |
| answer.isFinalized ? 'transform translate-x-full bg-green-600' : '' | |
| }"></div> | |
| </div> | |
| <span class="ml-3 text-sm font-medium { | |
| answer.isFinalized ? 'text-green-400' : 'text-gray-400' | |
| }"> | |
| {answer.isFinalized ? 'Finalized' : 'Draft'} | |
| </span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Answer Content --> | |
| <div class="p-6"> | |
| {#if editingAnswer === answer.id} | |
| <!-- Edit Mode --> | |
| <div class="space-y-4"> | |
| <textarea | |
| bind:value={editContent} | |
| class="w-full h-64 p-4 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-gray-700 text-white resize-none" | |
| placeholder="Edit your answer here..." | |
| ></textarea> | |
| <div class="flex justify-end space-x-3"> | |
| <button | |
| class="px-4 py-2 text-gray-400 hover:text-gray-200 transition-colors" | |
| on:click={cancelEdit} | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors" | |
| on:click={saveEdit} | |
| > | |
| Save Changes | |
| </button> | |
| </div> | |
| </div> | |
| {:else} | |
| <!-- Display Mode --> | |
| <div class="space-y-4"> | |
| <div class="prose prose-invert max-w-none"> | |
| <div class="text-gray-200 whitespace-pre-wrap leading-relaxed"> | |
| {answer.finalAnswer || answer.aiGeneratedAnswer} | |
| </div> | |
| </div> | |
| <!-- Suggestions Panel --> | |
| <div class="suggestions-panel"> | |
| <button | |
| class="suggestions-toggle" | |
| on:click={() => toggleSuggestions(answer.id)} | |
| > | |
| AI Suggestions for Improvement | |
| <span class="toggle-icon" class:expanded={showSuggestions[answer.id]}>▼</span> | |
| </button> | |
| {#if showSuggestions[answer.id] && suggestions} | |
| <div class="suggestions-content"> | |
| <!-- Quality Score --> | |
| <div class="quality-section"> | |
| <div class="quality-score"> | |
| <span class="score-label">Quality Score:</span> | |
| <span class="score-value {getQualityColor(suggestions.qualityScore)}"> | |
| {getQualityIcon(suggestions.qualityScore)} {suggestions.qualityScore}/100 | |
| </span> | |
| </div> | |
| </div> | |
| <!-- Suggestions --> | |
| {#if suggestions.suggestions.length > 0} | |
| <div class="suggestion-section"> | |
| <h5 class="section-title">💡 Improvement Suggestions</h5> | |
| <div class="suggestion-list"> | |
| {#each suggestions.suggestions as suggestion} | |
| <div class="suggestion-item"> | |
| <span class="suggestion-text">{suggestion}</span> | |
| <button | |
| class="apply-btn" | |
| on:click={() => applySuggestion(suggestion)} | |
| title="Apply this suggestion" | |
| > | |
| Apply | |
| </button> | |
| </div> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| <!-- SEDNA Enhancements --> | |
| {#if suggestions.sednaEnhancements.length > 0} | |
| <div class="suggestion-section"> | |
| <h5 class="section-title">🏢 SEDNA-Specific Enhancements</h5> | |
| <div class="suggestion-list"> | |
| {#each suggestions.sednaEnhancements as enhancement} | |
| <div class="suggestion-item sedna-enhancement"> | |
| <span class="suggestion-text">{enhancement}</span> | |
| <button | |
| class="apply-btn" | |
| on:click={() => applySuggestion(enhancement)} | |
| title="Apply this enhancement" | |
| > | |
| Apply | |
| </button> | |
| </div> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| <!-- Strengths --> | |
| {#if suggestions.strengths.length > 0} | |
| <div class="suggestion-section"> | |
| <h5 class="section-title">✅ Strengths Identified</h5> | |
| <div class="strengths-list"> | |
| {#each suggestions.strengths as strength} | |
| <span class="strength-tag">{strength}</span> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| <!-- Missing Elements --> | |
| {#if suggestions.missingElements.length > 0} | |
| <div class="suggestion-section"> | |
| <h5 class="section-title">⚠️ Missing Elements</h5> | |
| <div class="missing-list"> | |
| {#each suggestions.missingElements as element} | |
| <span class="missing-tag">{element}</span> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| {/if} | |
| </div> | |
| <!-- Citations --> | |
| {#if answer.citations && answer.citations.length > 0} | |
| <div class="mt-6 p-4 bg-blue-900/20 rounded-lg border border-blue-800"> | |
| <h4 class="text-sm font-semibold text-blue-100 mb-2"> | |
| References: | |
| </h4> | |
| <div class="space-y-1"> | |
| {#each answer.citations as citation, citIndex} | |
| <div class="text-sm text-blue-200"> | |
| [{citIndex + 1}] {citation} | |
| </div> | |
| {/each} | |
| </div> | |
| </div> | |
| {/if} | |
| <div class="flex justify-end"> | |
| <button | |
| class="px-4 py-2 text-blue-400 hover:text-blue-200 transition-colors" | |
| on:click={() => startEdit(answer.id)} | |
| > | |
| <svg class="w-4 h-4 mr-2 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /> | |
| </svg> | |
| Edit Answer | |
| </button> | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| </div> | |
| {/each} | |
| </div> | |
| <!-- Final Review Notice --> | |
| {#if !canProceed()} | |
| <div class="mt-8 p-6 bg-yellow-900/20 rounded-lg border border-yellow-800"> | |
| <div class="flex items-center"> | |
| <svg class="w-6 h-6 text-yellow-400 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z" /> | |
| </svg> | |
| <div> | |
| <h4 class="text-lg font-semibold text-yellow-100"> | |
| Review Required | |
| </h4> | |
| <p class="text-yellow-200"> | |
| Please review and finalize all answers before proceeding to the final step. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| {/if} | |
| <!-- Navigation Buttons --> | |
| <div class="flex justify-between mt-8"> | |
| <button | |
| class="px-6 py-3 bg-gray-700 text-gray-300 rounded-lg font-medium hover:bg-gray-600 transition-colors" | |
| on:click={prevStep} | |
| disabled={$isLoading} | |
| > | |
| <svg class="w-5 h-5 mr-2 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> | |
| </svg> | |
| Back to Questions | |
| </button> | |
| <button | |
| class="px-6 py-3 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" | |
| disabled={!canProceed() || $isLoading} | |
| on:click={nextStep} | |
| > | |
| Proceed to Download | |
| <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> | |
| <style> | |
| .suggestions-panel { | |
| margin: 1rem 0; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| } | |
| .suggestions-toggle { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| width: 100%; | |
| padding: 0.75rem 1rem; | |
| background: #f9fafb; | |
| border: none; | |
| cursor: pointer; | |
| font-weight: 500; | |
| color: #374151; | |
| transition: background-color 0.2s; | |
| } | |
| .suggestions-toggle:hover { | |
| background: #f3f4f6; | |
| } | |
| .toggle-icon { | |
| transition: transform 0.2s; | |
| } | |
| .toggle-icon.expanded { | |
| transform: rotate(180deg); | |
| } | |
| .suggestions-content { | |
| padding: 1rem; | |
| background: #ffffff; | |
| border-top: 1px solid #e5e7eb; | |
| } | |
| .quality-section { | |
| margin-bottom: 1.5rem; | |
| } | |
| .quality-score { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .score-label { | |
| font-weight: 500; | |
| color: #374151; | |
| } | |
| .score-value { | |
| font-weight: 600; | |
| font-size: 1.1rem; | |
| } | |
| .suggestion-section { | |
| margin-bottom: 1.5rem; | |
| } | |
| .section-title { | |
| font-weight: 600; | |
| color: #1f2937; | |
| margin-bottom: 0.75rem; | |
| font-size: 0.9rem; | |
| } | |
| .suggestion-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .suggestion-item { | |
| display: flex; | |
| align-items: flex-start; | |
| justify-content: space-between; | |
| padding: 0.75rem; | |
| background: #f8fafc; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 6px; | |
| transition: background-color 0.2s; | |
| } | |
| .suggestion-item:hover { | |
| background: #f1f5f9; | |
| } | |
| .suggestion-item.sedna-enhancement { | |
| background: #eff6ff; | |
| border-color: #dbeafe; | |
| } | |
| .suggestion-item.sedna-enhancement:hover { | |
| background: #e0f2fe; | |
| } | |
| .suggestion-text { | |
| flex: 1; | |
| color: #374151; | |
| line-height: 1.4; | |
| margin-right: 1rem; | |
| } | |
| .apply-btn { | |
| background: #3b82f6; | |
| color: white; | |
| border: none; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 4px; | |
| font-size: 0.8rem; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| white-space: nowrap; | |
| } | |
| .apply-btn:hover { | |
| background: #2563eb; | |
| } | |
| .strengths-list, .missing-list { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.5rem; | |
| } | |
| .strength-tag { | |
| background: #d1fae5; | |
| color: #065f46; | |
| padding: 0.25rem 0.5rem; | |
| border-radius: 4px; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| } | |
| .missing-tag { | |
| background: #fee2e2; | |
| color: #991b1b; | |
| padding: 0.25rem 0.5rem; | |
| border-radius: 4px; | |
| font-size: 0.8rem; | |
| font-weight: 500; | |
| } | |
| /* Dark mode styles */ | |
| :global(.dark) .suggestions-toggle { | |
| background: #374151; | |
| color: #f9fafb; | |
| } | |
| :global(.dark) .suggestions-toggle:hover { | |
| background: #4b5563; | |
| } | |
| :global(.dark) .suggestions-content { | |
| background: #1f2937; | |
| border-color: #374151; | |
| } | |
| :global(.dark) .suggestion-item { | |
| background: #374151; | |
| border-color: #4b5563; | |
| color: #f9fafb; | |
| } | |
| :global(.dark) .suggestion-item:hover { | |
| background: #4b5563; | |
| } | |
| :global(.dark) .suggestion-item.sedna-enhancement { | |
| background: #1e3a8a; | |
| border-color: #1e40af; | |
| } | |
| :global(.dark) .suggestion-item.sedna-enhancement:hover { | |
| background: #1e40af; | |
| } | |
| :global(.dark) .suggestion-text { | |
| color: #f3f4f6; | |
| } | |
| :global(.dark) .score-label { | |
| color: #f3f4f6; | |
| } | |
| :global(.dark) .section-title { | |
| color: #f9fafb; | |
| } | |
| :global(.dark) .suggestions-panel { | |
| border-color: #374151; | |
| } | |
| </style> | |