diff --git a/app/components/entrelineas/EntrelineaDetail.vue b/app/components/entrelineas/EntrelineaDetail.vue
index e817449..bcc1a31 100644
--- a/app/components/entrelineas/EntrelineaDetail.vue
+++ b/app/components/entrelineas/EntrelineaDetail.vue
@@ -47,8 +47,38 @@ const title = computed(() =>
origin.value || (props.document?.id as string) || 'Entrelínea'
)
+function formatEntrelineaText(html: string): string {
+ if (!html) return ''
+
+ const sPrefix = "font-family:'Times New Roman',serif;font-style:italic;color:#c00000"
+ const sBracket = "font-family:'Times New Roman',serif;font-style:italic;color:#c00000"
+ const sInner = "font-family:'Times New Roman',serif;font-style:italic;color:#c00000;text-decoration:underline"
+
+ const wrapLine = (content: string, isFirst: boolean): string =>
+ (isFirst ? `[WSS] ` : '') +
+ `«` +
+ `${content}` +
+ `»`
+
+ if (/
]/i.test(html)) {
+ const lines = html
+ .split(/
]*)>/i)
+ .map(part => part.replace(/<\/p>/gi, '').trim())
+ .filter(Boolean)
+ if (lines.length) return lines.map((l, i) => wrapLine(l, i === 0)).join('
')
+ return html
+ }
+
+ if (/
/i).map(p => p.trim()).filter(Boolean)
+ if (parts.length) return parts.map((l, i) => wrapLine(l, i === 0)).join('
')
+ }
+
+ return wrapLine(html.trim(), true)
+}
+
const bodyHtml = computed(() =>
- props.highlightedText || props.document?.text || ''
+ formatEntrelineaText(props.highlightedText || props.document?.text || '')
)
/* -------------------------------------------------------------------------- */
diff --git a/app/pages/conferencias-typensense.vue b/app/pages/conferencias-typensense.vue
index 79d1642..64effea 100644
--- a/app/pages/conferencias-typensense.vue
+++ b/app/pages/conferencias-typensense.vue
@@ -79,9 +79,20 @@ export interface TypesenseParagraphHit {
text_match?: number
}
+interface TypesenseGroupedHit {
+ group_key: string[]
+ hits: TypesenseParagraphHit[]
+}
+
interface TypesenseSearchResponse {
found: number
- hits?: TypesenseParagraphHit[]
+ grouped_hits?: TypesenseGroupedHit[]
+}
+
+interface SearchGroup {
+ docId: string
+ firstHit: TypesenseParagraphHit
+ allHits: TypesenseParagraphHit[]
}
interface BrowseItem {
@@ -97,31 +108,18 @@ interface DisplayGroup {
// ---- State ----------------------------------------------------------------
-// Modo búsqueda (con query): hits de párrafos agrupados por documento
-const hits = ref([])
+// Modo búsqueda (con query): grupos devueltos por Typesense
+const groupedHits = ref([])
const total = ref(0)
const currentPage = ref(1)
const hasMore = computed(() =>
- settings.paginationType === 'infinite_scroll' ? hits.value.length < total.value : false
+ settings.paginationType === 'infinite_scroll' ? groupedHits.value.length < total.value : false
)
// Progressive display (sólo en scroll infinito)
const visibleGroupCount = ref(10)
-const groupedHits = computed(() => {
- const map = new Map()
- for (const hit of hits.value) {
- const id = hit.document.document_id
- if (!map.has(id)) map.set(id, [])
- map.get(id)!.push(hit)
- }
- return [...map.entries()].map(([docId, docHits]) => ({
- docId,
- firstHit: docHits[0]!
- }))
-})
-
const visibleGroups = computed(() =>
settings.paginationType === 'infinite_scroll'
? groupedHits.value.slice(0, visibleGroupCount.value)
@@ -243,9 +241,12 @@ async function runSearch(q: string, page = 1, append = false) {
page: typePage,
highlightFullFields: QUERY_BY,
highlightFields: QUERY_BY,
+ highlightFullFields: QUERY_BY,
+ highlightFields: QUERY_BY,
highlightStartTag: '',
highlightEndTag: '',
- snippetThreshold: 100
+ highlightAffixNumTokens: 30,
+ groupBy: 'document_id'
}]
}
})
@@ -253,16 +254,21 @@ async function runSearch(q: string, page = 1, append = false) {
if (seq !== searchSeq) return
const res = (multi?.results?.[0] ?? {}) as TypesenseSearchResponse
- const newHits = res?.hits ?? []
+ const rawGroups = res?.grouped_hits ?? []
+ const newGroups: SearchGroup[] = rawGroups.map(g => ({
+ docId: g.group_key[0]!,
+ firstHit: g.hits[0]!,
+ allHits: g.hits
+ }))
if (!append) docCache.value = {}
- const newDocIds = [...new Set(newHits.map(h => h.document.document_id).filter(Boolean))]
+ const newDocIds = newGroups.map(g => g.docId).filter(Boolean)
await fetchDocumentMeta(newDocIds)
if (seq !== searchSeq) return
- hits.value = append ? hits.value.concat(newHits) : newHits
- total.value = res?.found ?? hits.value.length
+ groupedHits.value = append ? groupedHits.value.concat(newGroups) : newGroups
+ total.value = res?.found ?? groupedHits.value.length
currentPage.value = typePage
if (!append) activePage.value = page
} catch (err: unknown) {
@@ -270,7 +276,7 @@ async function runSearch(q: string, page = 1, append = false) {
console.error('Typesense error', err)
errorMsg.value = (err as Error)?.message || 'Error al buscar.'
if (!append) {
- hits.value = []
+ groupedHits.value = []
total.value = 0
}
} finally {
@@ -360,7 +366,7 @@ function goToPage(p: number) {
browseItems.value = []
runBrowse(p, false)
} else {
- hits.value = []
+ groupedHits.value = []
runSearch(query.value, p, false)
}
}
@@ -399,7 +405,7 @@ onBeforeUnmount(() => { if (timeoutId) clearTimeout(timeoutId) })
watch(debouncedQuery, (q) => {
activePage.value = 1
if (!q.trim()) {
- hits.value = []
+ groupedHits.value = []
total.value = 0
currentPage.value = 1
visibleGroupCount.value = 10
@@ -411,7 +417,7 @@ watch(debouncedQuery, (q) => {
browseItems.value = []
browseTotal.value = 0
browsePage.value = 1
- hits.value = []
+ groupedHits.value = []
total.value = 0
currentPage.value = 1
visibleGroupCount.value = 10
@@ -498,9 +504,8 @@ async function fetchDocumentParagraphs(docId: string) {
async function selectGroup(group: DisplayGroup) {
selectedDocId.value = group.docId
selectedHit.value = group.firstHit
- selectedMatchingHits.value = group.firstHit
- ? hits.value.filter(h => h.document.document_id === group.docId)
- : []
+ const searchGroup = groupedHits.value.find(g => g.docId === group.docId)
+ selectedMatchingHits.value = searchGroup?.allHits ?? []
fetchFullDocument(group.docId)
fetchDocumentParagraphs(group.docId)
}
diff --git a/app/pages/estudios-typensense.vue b/app/pages/estudios-typensense.vue
index d809c88..4968a06 100644
--- a/app/pages/estudios-typensense.vue
+++ b/app/pages/estudios-typensense.vue
@@ -79,9 +79,20 @@ export interface TypesenseParagraphHit {
text_match?: number
}
+interface TypesenseGroupedHit {
+ groupKey: string[]
+ hits: TypesenseParagraphHit[]
+}
+
interface TypesenseSearchResponse {
found: number
- hits?: TypesenseParagraphHit[]
+ groupedHits?: TypesenseGroupedHit[]
+}
+
+interface SearchGroup {
+ docId: string
+ firstHit: TypesenseParagraphHit
+ allHits: TypesenseParagraphHit[]
}
interface BrowseItem {
@@ -97,31 +108,18 @@ interface DisplayGroup {
// ---- State ----------------------------------------------------------------
-// Modo búsqueda (con query): hits de párrafos agrupados por documento
-const hits = ref([])
+// Modo búsqueda (con query): grupos devueltos por Typesense
+const groupedHits = ref([])
const total = ref(0)
const currentPage = ref(1)
const hasMore = computed(() =>
- settings.paginationType === 'infinite_scroll' ? hits.value.length < total.value : false
+ settings.paginationType === 'infinite_scroll' ? groupedHits.value.length < total.value : false
)
// Progressive display (sólo en scroll infinito)
const visibleGroupCount = ref(10)
-const groupedHits = computed(() => {
- const map = new Map()
- for (const hit of hits.value) {
- const id = hit.document.document_id
- if (!map.has(id)) map.set(id, [])
- map.get(id)!.push(hit)
- }
- return [...map.entries()].map(([docId, docHits]) => ({
- docId,
- firstHit: docHits[0]!
- }))
-})
-
const visibleGroups = computed(() =>
settings.paginationType === 'infinite_scroll'
? groupedHits.value.slice(0, visibleGroupCount.value)
@@ -245,24 +243,32 @@ async function runSearch(q: string, page = 1, append = false) {
highlightFields: QUERY_BY,
highlightStartTag: '',
highlightEndTag: '',
- highlightAffixNumTokens: 30
+ highlightAffixNumTokens: 30,
+ groupBy: 'document_id'
}]
}
})
-
+ console.log('Search response', multi)
if (seq !== searchSeq) return
const res = (multi?.results?.[0] ?? {}) as TypesenseSearchResponse
- const newHits = res?.hits ?? []
+ const rawGroups = res?.groupedHits ?? []
+
+ console.log('Raw groups', rawGroups)
+ const newGroups: SearchGroup[] = rawGroups.map(g => ({
+ docId: g.groupKey[0]!,
+ firstHit: g.hits[0]!,
+ allHits: g.hits
+ }))
if (!append) docCache.value = {}
- const newDocIds = [...new Set(newHits.map(h => h.document.document_id).filter(Boolean))]
+ const newDocIds = newGroups.map(g => g.docId).filter(Boolean)
await fetchDocumentMeta(newDocIds)
if (seq !== searchSeq) return
- hits.value = append ? hits.value.concat(newHits) : newHits
- total.value = res?.found ?? hits.value.length
+ groupedHits.value = append ? groupedHits.value.concat(newGroups) : newGroups
+ total.value = res?.found ?? groupedHits.value.length
currentPage.value = typePage
if (!append) activePage.value = page
} catch (err: unknown) {
@@ -270,7 +276,7 @@ async function runSearch(q: string, page = 1, append = false) {
console.error('Typesense error', err)
errorMsg.value = (err as Error)?.message || 'Error al buscar.'
if (!append) {
- hits.value = []
+ groupedHits.value = []
total.value = 0
}
} finally {
@@ -320,7 +326,6 @@ async function runBrowse(page = 1, append = false) {
})
if (seq !== searchSeq) return
-
const result = (multi?.results?.[0] as { found?: number, hits?: Array<{ document: DocMeta }> } | undefined)
const newItems = (result?.hits ?? []).map(h => ({
docId: h.document.id!,
@@ -360,7 +365,7 @@ function goToPage(p: number) {
browseItems.value = []
runBrowse(p, false)
} else {
- hits.value = []
+ groupedHits.value = []
runSearch(query.value, p, false)
}
}
@@ -399,7 +404,7 @@ onBeforeUnmount(() => { if (timeoutId) clearTimeout(timeoutId) })
watch(debouncedQuery, (q) => {
activePage.value = 1
if (!q.trim()) {
- hits.value = []
+ groupedHits.value = []
total.value = 0
currentPage.value = 1
visibleGroupCount.value = 10
@@ -411,7 +416,7 @@ watch(debouncedQuery, (q) => {
browseItems.value = []
browseTotal.value = 0
browsePage.value = 1
- hits.value = []
+ groupedHits.value = []
total.value = 0
currentPage.value = 1
visibleGroupCount.value = 10
@@ -498,9 +503,8 @@ async function fetchDocumentParagraphs(docId: string) {
async function selectGroup(group: DisplayGroup) {
selectedDocId.value = group.docId
selectedHit.value = group.firstHit
- selectedMatchingHits.value = group.firstHit
- ? hits.value.filter(h => h.document.document_id === group.docId)
- : []
+ const searchGroup = groupedHits.value.find(g => g.docId === group.docId)
+ selectedMatchingHits.value = searchGroup?.allHits ?? []
fetchFullDocument(group.docId)
fetchDocumentParagraphs(group.docId)
}