K2MAR's picture
Add T5 model integration for real summary generation
c43736a
import { useState } from 'react'
import './App.css'
const API_URL = 'https://k2mar-docuresume-backend.hf.space'
function App() {
const [page, setPage] = useState('login')
const [user, setUser] = useState(null)
// Login/Register form states
const [email, setEmail] = useState('')
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [message, setMessage] = useState('')
// Upload states
const [file, setFile] = useState(null)
const [documents, setDocuments] = useState([])
// Search states
const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState([])
const [searching, setSearching] = useState(false)
// Summary states
const [summarizing, setSummarizing] = useState(false)
const [summarizingAll, setSummarizingAll] = useState(false)
const [summaries, setSummaries] = useState({})
const handleRegister = async (e) => {
e.preventDefault()
setMessage('')
try {
const response = await fetch(`${API_URL}/users/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, username, password })
})
const data = await response.json()
if (response.ok) {
setMessage('✓ Inscription réussie! Connectez-vous.')
setPage('login')
} else {
setMessage('✗ ' + data.detail)
}
} catch (error) {
setMessage('✗ Erreur: ' + error.message)
}
}
const handleLogin = async (e) => {
e.preventDefault()
setMessage('')
try {
const response = await fetch(`${API_URL}/users/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
const data = await response.json()
if (response.ok) {
setUser(data)
setMessage('✓ Connexion réussie!')
setPage('dashboard')
loadDocuments(data.user_id)
} else {
setMessage('✗ ' + data.detail)
}
} catch (error) {
setMessage('✗ Erreur: ' + error.message)
}
}
const loadDocuments = async (userId) => {
try {
const response = await fetch(`${API_URL}/documents/user/${userId}`)
const data = await response.json()
setDocuments(data.documents || [])
} catch (error) {
console.error('Erreur chargement documents:', error)
}
}
const handleUpload = async (e) => {
e.preventDefault()
if (!file) {
setMessage('✗ Sélectionnez un fichier')
return
}
setMessage('⏳ Upload en cours...')
const formData = new FormData()
formData.append('file', file)
formData.append('user_id', user.user_id)
formData.append('tags', JSON.stringify([]))
try {
const response = await fetch(`${API_URL}/documents/upload`, {
method: 'POST',
body: formData
})
const data = await response.json()
if (response.ok) {
setMessage('✓ Document uploadé!')
setFile(null)
loadDocuments(user.user_id)
} else {
setMessage('✗ ' + data.detail)
}
} catch (error) {
setMessage('✗ Erreur: ' + error.message)
}
}
const logout = () => {
setUser(null)
setPage('login')
setEmail('')
setPassword('')
setUsername('')
setMessage('')
setSearchQuery('')
setSearchResults([])
setSummaries({})
}
const handleSummarizeDocument = async (documentId, filename) => {
setSummarizing(documentId)
setMessage(`📝 Résumé de "${filename}" en cours...`)
try {
// 1. Récupérer le contenu du document
const contentResponse = await fetch(`${API_URL}/documents/${documentId}/content`)
const contentData = await contentResponse.json()
if (!contentResponse.ok) {
throw new Error(contentData.detail || 'Erreur récupération contenu')
}
// 2. Générer le résumé avec le modèle T5 sur le backend
const generateResponse = await fetch(`${API_URL}/generate/summary`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: contentData.content,
max_length: 150,
min_length: 30
})
})
const generateData = await generateResponse.json()
if (!generateResponse.ok) {
throw new Error(generateData.detail || 'Erreur génération résumé')
}
const summary = generateData.summary
const generationTime = generateData.generation_time
// 3. Envoyer le résumé au serveur pour sauvegarde
const summaryResponse = await fetch(`${API_URL}/documents/${documentId}/summaries`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: summary,
summary_type: 'abstractive',
summary_level: 'medium',
model_name: 't5-small-finetuned'
})
})
const summaryData = await summaryResponse.json()
if (summaryResponse.ok) {
setMessage(`✓ Résumé de "${filename}" créé en ${generationTime}s!`)
setSummaries(prev => ({ ...prev, [documentId]: summary }))
} else {
throw new Error(summaryData.detail || 'Erreur création résumé')
}
} catch (error) {
setMessage(`✗ Erreur résumé: ${error.message}`)
} finally {
setSummarizing(false)
}
}
const handleSummarizeAll = async () => {
if (documents.length === 0) {
setMessage('✗ Aucun document à résumer')
return
}
setSummarizingAll(true)
setMessage(`📝 Résumé de ${documents.length} documents en cours...`)
try {
// 1. Récupérer tous les contenus
const response = await fetch(`${API_URL}/documents/user/${user.user_id}/contents?include_content=true`)
const data = await response.json()
if (!response.ok) {
throw new Error(data.detail || 'Erreur récupération contenus')
}
// 2. Générer les résumés avec le modèle T5 pour chaque document
const summariesBatch = []
for (const doc of data.documents) {
try {
const generateResponse = await fetch(`${API_URL}/generate/summary`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: doc.content,
max_length: 150,
min_length: 30
})
})
const generateData = await generateResponse.json()
if (generateResponse.ok) {
summariesBatch.push({
document_id: doc.document_id,
content: generateData.summary,
summary_type: 'abstractive',
summary_level: 'medium',
model_name: 't5-small-finetuned'
})
}
} catch (err) {
console.error(`Erreur résumé ${doc.filename}:`, err)
}
}
if (summariesBatch.length === 0) {
throw new Error('Aucun résumé généré')
}
// 3. Envoyer tous les résumés en batch
const batchResponse = await fetch(`${API_URL}/summaries/batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(summariesBatch)
})
const batchData = await batchResponse.json()
if (batchResponse.ok) {
setMessage(`✓ ${batchData.count} résumés créés avec succès!`)
// Mettre à jour les résumés localement
const newSummaries = {}
summariesBatch.forEach(s => {
newSummaries[s.document_id] = s.content
})
setSummaries(newSummaries)
} else {
throw new Error(batchData.detail || 'Erreur création résumés')
}
} catch (error) {
setMessage(`✗ Erreur résumé batch: ${error.message}`)
} finally {
setSummarizingAll(false)
}
}
const handleSearch = async (e) => {
e.preventDefault()
if (!searchQuery.trim()) {
setMessage('✗ Entrez une requête de recherche')
return
}
setSearching(true)
setMessage('🔍 Recherche en cours...')
try {
const response = await fetch(
`${API_URL}/search/semantic/multi?user_id=${user.user_id}&query=${encodeURIComponent(searchQuery)}&k=5`,
{ method: 'POST' }
)
const data = await response.json()
if (response.ok) {
setSearchResults(data.results || [])
setMessage(`✓ ${data.count} résultats trouvés dans ${data.searched_documents} documents`)
} else {
setMessage('✗ ' + data.detail)
setSearchResults([])
}
} catch (error) {
setMessage('✗ Erreur: ' + error.message)
setSearchResults([])
} finally {
setSearching(false)
}
}
if (page === 'register') {
return (
<div className="container">
<div className="card">
<h1>📝 Inscription</h1>
<form onSubmit={handleRegister}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
<input
type="password"
placeholder="Mot de passe"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">S'inscrire</button>
</form>
{message && <div className="message">{message}</div>}
<p className="link" onClick={() => setPage('login')}>
Déjà un compte? Se connecter
</p>
</div>
</div>
)
}
if (page === 'login') {
return (
<div className="container">
<div className="card">
<h1>🔐 Connexion</h1>
<form onSubmit={handleLogin}>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="Mot de passe"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">Se connecter</button>
</form>
{message && <div className="message">{message}</div>}
<p className="link" onClick={() => setPage('register')}>
Pas de compte? S'inscrire
</p>
</div>
</div>
)
}
if (page === 'dashboard' && user) {
return (
<div className="container">
<div className="card large">
<div className="header">
<div>
<h1>📄 DocuResume Pro</h1>
<p>Bienvenue {user.username} ({user.email})</p>
</div>
<button onClick={logout} className="btn-secondary">Déconnexion</button>
</div>
<div className="section">
<h2>📤 Upload Document</h2>
<form onSubmit={handleUpload}>
<input
type="file"
accept=".pdf"
onChange={(e) => setFile(e.target.files[0])}
/>
<button type="submit">Uploader PDF</button>
</form>
</div>
{message && <div className="message">{message}</div>}
<div className="section">
<h2>� Recherche Sémantique</h2>
<form onSubmit={handleSearch}>
<input
type="text"
placeholder="Posez une question sur vos documents..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<button type="submit" disabled={searching}>
{searching ? '⏳ Recherche...' : '🔍 Rechercher'}
</button>
</form>
{searchResults.length > 0 && (
<div className="search-results">
<h3>Résultats ({searchResults.length})</h3>
{searchResults.map((result, idx) => (
<div key={idx} className="search-result">
<div className="result-header">
<strong>Score: {(result.score * 100).toFixed(1)}%</strong>
<small>Document ID: {result.document_id.slice(0, 8)}...</small>
</div>
<p className="result-text">{result.text}</p>
</div>
))}
</div>
)}
</div>
<div className="section">
<div className="header">
<h2>📚 Mes Documents ({documents.length})</h2>
{documents.length > 0 && (
<button
onClick={handleSummarizeAll}
disabled={summarizingAll}
className="btn-primary"
>
{summarizingAll ? '⏳ Résumé en cours...' : '📝 Résumer tous les documents'}
</button>
)}
</div>
<div className="documents">
{documents.length === 0 ? (
<p className="empty">Aucun document uploadé</p>
) : (
documents.map((doc) => (
<div key={doc.id} className="document">
<div className="doc-info">
<strong>{doc.original_filename}</strong>
<small>{doc.processing_status}</small>
</div>
<div className="doc-actions">
<div className="doc-meta">
<span>{(doc.file_size / 1024).toFixed(1)} KB</span>
<span>{doc.page_count || '?'} pages</span>
</div>
<button
onClick={() => handleSummarizeDocument(doc.id, doc.original_filename)}
disabled={summarizing === doc.id}
className="btn-summary"
>
{summarizing === doc.id ? '⏳' : '📝 Résumer'}
</button>
<a
href={`${API_URL}/documents/${doc.id}/download`}
download={doc.original_filename}
className="btn-download"
>
⬇️ Télécharger
</a>
</div>
{summaries[doc.id] && (
<div className="summary-box">
<strong>📄 Résumé:</strong>
<p>{summaries[doc.id]}</p>
</div>
)}
</div>
))
)}
</div>
</div>
</div>
</div>
)
}
return null
}
export default App