fh / src /components /AnswerReview.svelte
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
21.3 kB
<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>