Compare commits
4 Commits
3e8b8cd011
...
78471c19d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 78471c19d9 | |||
| 06bae3ba3e | |||
| 614ef3ffaf | |||
| 2c0c9e0ba4 |
@@ -1,7 +1,14 @@
|
|||||||
import { useNavigate } from '@tanstack/react-router'
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
import CSL from 'citeproc'
|
import CSL from 'citeproc'
|
||||||
import { Globe, Loader2, Plus, RefreshCw, X } from 'lucide-react'
|
import {
|
||||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
Globe,
|
||||||
|
Link as LinkIcon,
|
||||||
|
Loader2,
|
||||||
|
Plus,
|
||||||
|
RefreshCw,
|
||||||
|
X,
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { BuscarBibliografiaRequest } from '@/data'
|
import type { BuscarBibliografiaRequest } from '@/data'
|
||||||
import type {
|
import type {
|
||||||
@@ -120,6 +127,7 @@ type BibliografiaRef = {
|
|||||||
source: BibliografiaTipoFuente
|
source: BibliografiaTipoFuente
|
||||||
raw?: GoogleBooksVolume | OpenLibraryDoc
|
raw?: GoogleBooksVolume | OpenLibraryDoc
|
||||||
title: string
|
title: string
|
||||||
|
subtitle?: string
|
||||||
authors: Array<string>
|
authors: Array<string>
|
||||||
publisher?: string
|
publisher?: string
|
||||||
year?: number
|
year?: number
|
||||||
@@ -253,17 +261,22 @@ function endpointResultToRef(result: EndpointResult): BibliografiaRef {
|
|||||||
const volume = result.item
|
const volume = result.item
|
||||||
const info = volume.volumeInfo ?? {}
|
const info = volume.volumeInfo ?? {}
|
||||||
const title = (info.title ?? '').trim() || 'Sin título'
|
const title = (info.title ?? '').trim() || 'Sin título'
|
||||||
|
const subtitle =
|
||||||
|
typeof info.subtitle === 'string' ? info.subtitle.trim() : undefined
|
||||||
const authors = Array.isArray(info.authors) ? info.authors : []
|
const authors = Array.isArray(info.authors) ? info.authors : []
|
||||||
const publisher = typeof info.publisher === 'string' ? info.publisher : undefined
|
const publisher =
|
||||||
|
typeof info.publisher === 'string' ? info.publisher : undefined
|
||||||
const year = tryParseYear(info.publishedDate)
|
const year = tryParseYear(info.publishedDate)
|
||||||
const isbn =
|
const isbn =
|
||||||
info.industryIdentifiers?.find((x) => x?.identifier)?.identifier ?? undefined
|
info.industryIdentifiers?.find((x) => x.identifier)?.identifier ??
|
||||||
|
undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: getEndpointResultId(result),
|
id: getEndpointResultId(result),
|
||||||
source: 'BIBLIOTECA',
|
source: 'BIBLIOTECA',
|
||||||
raw: volume,
|
raw: volume,
|
||||||
title,
|
title,
|
||||||
|
subtitle,
|
||||||
authors,
|
authors,
|
||||||
publisher,
|
publisher,
|
||||||
year,
|
year,
|
||||||
@@ -273,10 +286,15 @@ function endpointResultToRef(result: EndpointResult): BibliografiaRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doc = result.item
|
const doc = result.item
|
||||||
const title = (typeof doc['title'] === 'string' ? doc['title'] : '').trim() ||
|
const title =
|
||||||
|
(typeof doc['title'] === 'string' ? doc['title'] : '').trim() ||
|
||||||
'Sin título'
|
'Sin título'
|
||||||
|
const subtitle =
|
||||||
|
typeof doc['subtitle'] === 'string' ? doc['subtitle'].trim() : undefined
|
||||||
const authors = Array.isArray(doc['author_name'])
|
const authors = Array.isArray(doc['author_name'])
|
||||||
? (doc['author_name'] as Array<unknown>).filter((a): a is string => typeof a === 'string')
|
? (doc['author_name'] as Array<unknown>).filter(
|
||||||
|
(a): a is string => typeof a === 'string',
|
||||||
|
)
|
||||||
: []
|
: []
|
||||||
const publisher = Array.isArray(doc['publisher'])
|
const publisher = Array.isArray(doc['publisher'])
|
||||||
? (doc['publisher'] as Array<unknown>).find(
|
? (doc['publisher'] as Array<unknown>).find(
|
||||||
@@ -287,7 +305,9 @@ function endpointResultToRef(result: EndpointResult): BibliografiaRef {
|
|||||||
: undefined
|
: undefined
|
||||||
const year = tryParseYearFromOpenLibrary(doc)
|
const year = tryParseYearFromOpenLibrary(doc)
|
||||||
const isbn = Array.isArray(doc['isbn'])
|
const isbn = Array.isArray(doc['isbn'])
|
||||||
? (doc['isbn'] as Array<unknown>).find((x): x is string => typeof x === 'string')
|
? (doc['isbn'] as Array<unknown>).find(
|
||||||
|
(x): x is string => typeof x === 'string',
|
||||||
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -295,6 +315,7 @@ function endpointResultToRef(result: EndpointResult): BibliografiaRef {
|
|||||||
source: 'BIBLIOTECA',
|
source: 'BIBLIOTECA',
|
||||||
raw: doc,
|
raw: doc,
|
||||||
title,
|
title,
|
||||||
|
subtitle,
|
||||||
authors,
|
authors,
|
||||||
publisher,
|
publisher,
|
||||||
year,
|
year,
|
||||||
@@ -320,50 +341,6 @@ function sortResultsByMostRecent(a: EndpointResult, b: EndpointResult) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function AutoSizeTextarea({
|
|
||||||
value,
|
|
||||||
disabled,
|
|
||||||
placeholder,
|
|
||||||
className,
|
|
||||||
onChange,
|
|
||||||
}: {
|
|
||||||
value: string
|
|
||||||
disabled?: boolean
|
|
||||||
placeholder?: string
|
|
||||||
className?: string
|
|
||||||
onChange: (next: string) => void
|
|
||||||
}) {
|
|
||||||
const ref = useRef<HTMLTextAreaElement | null>(null)
|
|
||||||
|
|
||||||
const autosize = () => {
|
|
||||||
const el = ref.current
|
|
||||||
if (!el) return
|
|
||||||
el.style.height = '0px'
|
|
||||||
el.style.height = `${el.scrollHeight}px`
|
|
||||||
}
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
autosize()
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Textarea
|
|
||||||
ref={ref}
|
|
||||||
rows={1}
|
|
||||||
value={value}
|
|
||||||
disabled={disabled}
|
|
||||||
placeholder={placeholder}
|
|
||||||
className={cn('min-h-0 resize-none overflow-hidden pr-10', className)}
|
|
||||||
onChange={(e) => {
|
|
||||||
const el = e.currentTarget
|
|
||||||
el.style.height = '0px'
|
|
||||||
el.style.height = `${el.scrollHeight}px`
|
|
||||||
onChange(el.value)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function citeprocHtmlToPlainText(value: string) {
|
function citeprocHtmlToPlainText(value: string) {
|
||||||
const input = value
|
const input = value
|
||||||
if (!input) return ''
|
if (!input) return ''
|
||||||
@@ -951,6 +928,14 @@ export function NuevaBibliografiaModalContainer({
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
onChangeRef={(id, patch) =>
|
||||||
|
setWizard((w) => ({
|
||||||
|
...w,
|
||||||
|
refs: w.refs.map((r) =>
|
||||||
|
r.id === id ? { ...r, ...patch } : r,
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
onChangeTipo={(id, tipo) =>
|
onChangeTipo={(id, tipo) =>
|
||||||
setWizard((w) => ({
|
setWizard((w) => ({
|
||||||
...w,
|
...w,
|
||||||
@@ -959,19 +944,6 @@ export function NuevaBibliografiaModalContainer({
|
|||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
onChangeCita={(id, value) => {
|
|
||||||
if (!wizard.formato) return
|
|
||||||
setWizard((w) => ({
|
|
||||||
...w,
|
|
||||||
citaEdits: {
|
|
||||||
...w.citaEdits,
|
|
||||||
[wizard.formato!]: {
|
|
||||||
...w.citaEdits[wizard.formato!],
|
|
||||||
[id]: value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Wizard.Stepper.Panel>
|
</Wizard.Stepper.Panel>
|
||||||
)}
|
)}
|
||||||
@@ -1182,22 +1154,63 @@ function SugerenciasStep({
|
|||||||
{sugerencias.map((s) => {
|
{sugerencias.map((s) => {
|
||||||
const selected = s.selected
|
const selected = s.selected
|
||||||
|
|
||||||
const badgeLabel = s.endpoint === 'google' ? 'Google' : 'Open Library'
|
const badgeLabel =
|
||||||
|
s.endpoint === 'google' ? 'Google' : 'Open Library'
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
s.endpoint === 'google'
|
s.endpoint === 'google'
|
||||||
? (((s.item as GoogleBooksVolume).volumeInfo?.title ??
|
? (
|
||||||
'Sin título')).trim()
|
(s.item as GoogleBooksVolume).volumeInfo?.title ??
|
||||||
|
'Sin título'
|
||||||
|
).trim()
|
||||||
: (typeof (s.item as OpenLibraryDoc)['title'] === 'string'
|
: (typeof (s.item as OpenLibraryDoc)['title'] === 'string'
|
||||||
? ((s.item as OpenLibraryDoc)['title'] as string)
|
? ((s.item as OpenLibraryDoc)['title'] as string)
|
||||||
: 'Sin título'
|
: 'Sin título'
|
||||||
).trim()
|
).trim()
|
||||||
|
|
||||||
|
const subtitle =
|
||||||
|
s.endpoint === 'google'
|
||||||
|
? (typeof (s.item as GoogleBooksVolume).volumeInfo?.subtitle ===
|
||||||
|
'string'
|
||||||
|
? ((s.item as GoogleBooksVolume).volumeInfo
|
||||||
|
?.subtitle as string)
|
||||||
|
: ''
|
||||||
|
).trim()
|
||||||
|
: (typeof (s.item as OpenLibraryDoc)['subtitle'] === 'string'
|
||||||
|
? ((s.item as OpenLibraryDoc)['subtitle'] as string)
|
||||||
|
: ''
|
||||||
|
).trim()
|
||||||
|
|
||||||
|
const browserHref = (() => {
|
||||||
|
if (s.endpoint === 'google') {
|
||||||
|
const info = (s.item as GoogleBooksVolume).volumeInfo
|
||||||
|
const previewLink =
|
||||||
|
typeof info?.previewLink === 'string'
|
||||||
|
? info.previewLink
|
||||||
|
: undefined
|
||||||
|
const infoLink =
|
||||||
|
typeof info?.infoLink === 'string' ? info.infoLink : undefined
|
||||||
|
return previewLink || infoLink
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = (s.item as OpenLibraryDoc)['key']
|
||||||
|
if (typeof key === 'string' && key.trim()) {
|
||||||
|
return `https://openlibrary.org/${key}`
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})()
|
||||||
|
|
||||||
const authors =
|
const authors =
|
||||||
s.endpoint === 'google'
|
s.endpoint === 'google'
|
||||||
? ((s.item as GoogleBooksVolume).volumeInfo?.authors ?? []).join(', ')
|
? (
|
||||||
|
(s.item as GoogleBooksVolume).volumeInfo?.authors ?? []
|
||||||
|
).join(', ')
|
||||||
: Array.isArray((s.item as OpenLibraryDoc)['author_name'])
|
: Array.isArray((s.item as OpenLibraryDoc)['author_name'])
|
||||||
? ((s.item as OpenLibraryDoc)['author_name'] as Array<unknown>)
|
? (
|
||||||
|
(s.item as OpenLibraryDoc)[
|
||||||
|
'author_name'
|
||||||
|
] as Array<unknown>
|
||||||
|
)
|
||||||
.filter((a): a is string => typeof a === 'string')
|
.filter((a): a is string => typeof a === 'string')
|
||||||
.join(', ')
|
.join(', ')
|
||||||
: ''
|
: ''
|
||||||
@@ -1232,14 +1245,32 @@ function SugerenciasStep({
|
|||||||
<div className="min-w-0 truncate text-sm font-medium">
|
<div className="min-w-0 truncate text-sm font-medium">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="secondary" className="shrink-0">
|
{subtitle ? (
|
||||||
{badgeLabel}
|
<div className="text-muted-foreground min-w-0 truncate text-xs">
|
||||||
</Badge>
|
{subtitle}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground text-xs">
|
<div className="text-muted-foreground text-xs">
|
||||||
{authors || '—'}
|
{authors || '—'}
|
||||||
{year ? ` • ${year}` : ''}
|
{year ? ` • ${year}` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<a
|
||||||
|
href={browserHref}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground hover:text-primary inline-flex items-center gap-1 text-xs underline transition-colors visited:text-purple-500',
|
||||||
|
!browserHref && 'invisible',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Ver ficha <LinkIcon className="h-3.5 w-3.5" />
|
||||||
|
</a>
|
||||||
|
<Badge variant="secondary" className="shrink-0">
|
||||||
|
{badgeLabel}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Label>
|
</Label>
|
||||||
)
|
)
|
||||||
@@ -1401,8 +1432,8 @@ function FormatoYCitasStep({
|
|||||||
generatingIds,
|
generatingIds,
|
||||||
onChangeFormato,
|
onChangeFormato,
|
||||||
onRegenerate,
|
onRegenerate,
|
||||||
|
onChangeRef,
|
||||||
onChangeTipo,
|
onChangeTipo,
|
||||||
onChangeCita,
|
|
||||||
}: {
|
}: {
|
||||||
refs: Array<BibliografiaRef>
|
refs: Array<BibliografiaRef>
|
||||||
formato: FormatoCita | null
|
formato: FormatoCita | null
|
||||||
@@ -1410,10 +1441,38 @@ function FormatoYCitasStep({
|
|||||||
generatingIds: Set<string>
|
generatingIds: Set<string>
|
||||||
onChangeFormato: (formato: FormatoCita | null) => void
|
onChangeFormato: (formato: FormatoCita | null) => void
|
||||||
onRegenerate: () => void
|
onRegenerate: () => void
|
||||||
|
onChangeRef: (id: string, patch: Partial<BibliografiaRef>) => void
|
||||||
onChangeTipo: (id: string, tipo: BibliografiaTipo) => void
|
onChangeTipo: (id: string, tipo: BibliografiaTipo) => void
|
||||||
onChangeCita: (id: string, value: string) => void
|
|
||||||
}) {
|
}) {
|
||||||
const isGeneratingAny = generatingIds.size > 0
|
const isGeneratingAny = generatingIds.size > 0
|
||||||
|
const [authorsDraftById, setAuthorsDraftById] = useState<
|
||||||
|
Record<string, string>
|
||||||
|
>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ids = new Set(refs.map((r) => r.id))
|
||||||
|
setAuthorsDraftById((prev) => {
|
||||||
|
let next = prev
|
||||||
|
|
||||||
|
// Remove drafts for refs that no longer exist
|
||||||
|
for (const id of Object.keys(prev)) {
|
||||||
|
if (!ids.has(id)) {
|
||||||
|
if (next === prev) next = { ...prev }
|
||||||
|
delete next[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize drafts for new refs (do not override existing edits)
|
||||||
|
for (const r of refs) {
|
||||||
|
if (typeof next[r.id] !== 'string') {
|
||||||
|
if (next === prev) next = { ...prev }
|
||||||
|
next[r.id] = r.authors.join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [refs])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -1467,43 +1526,28 @@ function FormatoYCitasStep({
|
|||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{refs.map((r) => {
|
{refs.map((r) => {
|
||||||
const infoText = [
|
|
||||||
r.authors.join(', '),
|
|
||||||
r.publisher,
|
|
||||||
r.year ? String(r.year) : undefined,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' • ')
|
|
||||||
|
|
||||||
const isGenerating = generatingIds.has(r.id)
|
const isGenerating = generatingIds.has(r.id)
|
||||||
|
|
||||||
|
const authorsText = authorsDraftById[r.id] ?? r.authors.join('\n')
|
||||||
|
const yearText = typeof r.year === 'number' ? String(r.year) : ''
|
||||||
|
const isbnText = r.isbn ?? ''
|
||||||
|
const publisherText = r.publisher ?? ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={r.id} className="overflow-hidden">
|
<Card key={r.id} className="overflow-hidden">
|
||||||
<CardHeader className="bg-muted/10">
|
<CardContent className="space-y-4">
|
||||||
<CardTitle className="text-base leading-tight">
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12">
|
||||||
{r.title}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className="wrap-break-word">
|
|
||||||
{infoText}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-12">
|
|
||||||
<div className="space-y-2 sm:col-span-9">
|
<div className="space-y-2 sm:col-span-9">
|
||||||
<Label className="text-xs">Cita formateada</Label>
|
<Label className="text-xs">Título</Label>
|
||||||
<div className="relative">
|
<Input
|
||||||
<AutoSizeTextarea
|
value={r.title}
|
||||||
value={citations[r.id] ?? ''}
|
disabled={isGeneratingAny || isGenerating}
|
||||||
onChange={(next) => onChangeCita(r.id, next)}
|
onChange={(e) =>
|
||||||
disabled={isGenerating || isGeneratingAny}
|
onChangeRef(r.id, {
|
||||||
placeholder="Cita generada…"
|
title: e.currentTarget.value,
|
||||||
/>
|
})
|
||||||
{isGenerating && (
|
}
|
||||||
<div className="absolute inset-y-0 right-3 flex items-center">
|
/>
|
||||||
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex w-full flex-col items-start gap-2 sm:col-span-3 sm:items-stretch">
|
<div className="flex w-full flex-col items-start gap-2 sm:col-span-3 sm:items-stretch">
|
||||||
@@ -1525,6 +1569,101 @@ function FormatoYCitasStep({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12">
|
||||||
|
<div className="space-y-2 sm:col-span-6">
|
||||||
|
<Label className="text-xs">Autores (uno por línea)</Label>
|
||||||
|
<Textarea
|
||||||
|
value={authorsText}
|
||||||
|
disabled={isGeneratingAny || isGenerating}
|
||||||
|
className="min-h-22.5"
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextText = e.currentTarget.value
|
||||||
|
setAuthorsDraftById((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[r.id]: nextText,
|
||||||
|
}))
|
||||||
|
|
||||||
|
onChangeRef(r.id, {
|
||||||
|
authors: nextText
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((x) => x.trim())
|
||||||
|
.filter(Boolean),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 sm:col-span-6">
|
||||||
|
<Label className="text-xs">Editorial</Label>
|
||||||
|
<Input
|
||||||
|
value={publisherText}
|
||||||
|
disabled={isGeneratingAny || isGenerating}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChangeRef(r.id, {
|
||||||
|
publisher:
|
||||||
|
e.currentTarget.value.trim() || undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-12">
|
||||||
|
<div className="space-y-2 sm:col-span-3">
|
||||||
|
<Label className="text-xs">Año</Label>
|
||||||
|
<Input
|
||||||
|
value={yearText}
|
||||||
|
disabled={isGeneratingAny || isGenerating}
|
||||||
|
placeholder="2024"
|
||||||
|
onChange={(e) => {
|
||||||
|
const raw = e.currentTarget.value
|
||||||
|
const year = Number.parseInt(raw.trim(), 10)
|
||||||
|
onChangeRef(r.id, {
|
||||||
|
year: Number.isFinite(year) ? year : undefined,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 sm:col-span-9">
|
||||||
|
<Label className="text-xs">ISBN</Label>
|
||||||
|
<Input
|
||||||
|
value={isbnText}
|
||||||
|
disabled={isGeneratingAny || isGenerating}
|
||||||
|
onChange={(e) =>
|
||||||
|
onChangeRef(r.id, {
|
||||||
|
isbn: e.currentTarget.value.trim() || undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs">Cita generada</Label>
|
||||||
|
<div className="bg-muted/30 border-border rounded-md border px-3 py-2 text-sm">
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
{citations[r.id] ? (
|
||||||
|
<p className="wrap-break-word">{citations[r.id]}</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Cita generada…
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{/* {infoText ? (
|
||||||
|
<p className="text-muted-foreground mt-1 text-xs">
|
||||||
|
{infoText}
|
||||||
|
</p>
|
||||||
|
) : null} */}
|
||||||
|
</div>
|
||||||
|
{isGenerating ? (
|
||||||
|
<Loader2 className="text-muted-foreground mt-0.5 h-4 w-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
@@ -1593,7 +1732,14 @@ function ResumenStep({
|
|||||||
key={r.id}
|
key={r.id}
|
||||||
className="bg-background rounded-md border p-3 text-sm shadow-sm"
|
className="bg-background rounded-md border p-3 text-sm shadow-sm"
|
||||||
>
|
>
|
||||||
<p className="mb-1 font-medium">{r.title}</p>
|
<div className="mb-1 flex min-w-0 items-baseline gap-2">
|
||||||
|
<p className="min-w-0 truncate font-medium">{r.title}</p>
|
||||||
|
{r.subtitle ? (
|
||||||
|
<p className="text-muted-foreground min-w-0 truncate text-xs">
|
||||||
|
{r.subtitle}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{citations[r.id] ?? 'Sin cita generada'}
|
{citations[r.id] ?? 'Sin cita generada'}
|
||||||
</p>
|
</p>
|
||||||
@@ -1615,7 +1761,14 @@ function ResumenStep({
|
|||||||
key={r.id}
|
key={r.id}
|
||||||
className="bg-background rounded-md border p-3 text-sm shadow-sm"
|
className="bg-background rounded-md border p-3 text-sm shadow-sm"
|
||||||
>
|
>
|
||||||
<p className="mb-1 font-medium">{r.title}</p>
|
<div className="mb-1 flex min-w-0 items-baseline gap-2">
|
||||||
|
<p className="min-w-0 truncate font-medium">{r.title}</p>
|
||||||
|
{r.subtitle ? (
|
||||||
|
<p className="text-muted-foreground min-w-0 truncate text-xs">
|
||||||
|
{r.subtitle}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{citations[r.id] ?? 'Sin cita generada'}
|
{citations[r.id] ?? 'Sin cita generada'}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user