fix(app): harden brief extraction and default to fastest model
- Default chatCollapsed to true so PromptIndex shows on load - Default AI model to claude-haiku-4.5 for faster responses - Fix extractMagazineSections to handle numbered/bullet bold lists without markdown headings (e.g. "1. **Title** — content") - Fix "For deeper coverage" stripping for bold-formatted text - Expand hasMagazine detection with more financial/news keywords Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
721e915a48
commit
10e12a329f
@@ -161,7 +161,7 @@ export function extractMagazineSections(text: string): MagazineSection[] {
|
||||
|
||||
let cleanedText = text
|
||||
.replace(/\n+(?:Sources|References|Links):?\s*\n[\s\S]*$/i, '')
|
||||
.replace(/\n+For deeper[^:]*:[\s\S]*$/im, '')
|
||||
.replace(/\n+\*{0,2}For deeper[^:]*:[\s\S]*$/im, '')
|
||||
|
||||
const headingPositions: { title: string; start: number; contentStart: number }[] = []
|
||||
const headingLineRe = /^#{2,3}\s+(.+)$/gm
|
||||
@@ -223,6 +223,104 @@ export function extractMagazineSections(text: string): MagazineSection[] {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Fallback: numbered/bullet lists with bold titles ──────────────
|
||||
// Handles responses like: "1. **Strong price recovery** — BTC climbed..."
|
||||
if (sections.length === 0) {
|
||||
// Detect bold pseudo-headings on their own line: "**Key developments today:**"
|
||||
const boldHeadingRe = /(?:^|\n)\s*\*\*([^*]+?)\*\*\s*:?\s*(?:\n|$)/g
|
||||
const boldHeadings: { title: string; start: number; end: number }[] = []
|
||||
while ((m = boldHeadingRe.exec(cleanedText)) !== null) {
|
||||
const title = m[1].trim()
|
||||
// Skip if preceded by a list marker (it's a list item, not a heading)
|
||||
const before = cleanedText.slice(Math.max(0, m.index - 10), m.index)
|
||||
if (/\d+\.\s*$/.test(before) || /[-•]\s*$/.test(before)) continue
|
||||
if (title.length > 2 && title.length < 100) {
|
||||
boldHeadings.push({ title, start: m.index, end: m.index + m[0].length })
|
||||
}
|
||||
}
|
||||
|
||||
// Try numbered items: "1. **Title** — content"
|
||||
const numberedRe = /(?:^|\n)\s*\d+\.\s*\*\*([^*]+)\*\*\s*[-–—:]+\s*/g
|
||||
const numberedItems: { title: string; start: number; contentStart: number }[] = []
|
||||
while ((m = numberedRe.exec(cleanedText)) !== null) {
|
||||
const title = m[1].trim()
|
||||
if (title.length > 1) {
|
||||
numberedItems.push({ title, start: m.index, contentStart: m.index + m[0].length })
|
||||
}
|
||||
}
|
||||
|
||||
// Find group header for numbered items
|
||||
let group: string | undefined
|
||||
if (numberedItems.length > 0 && boldHeadings.length > 0) {
|
||||
const firstItemStart = numberedItems[0].start
|
||||
const precedingHeading = boldHeadings.filter(h => h.end <= firstItemStart).pop()
|
||||
if (precedingHeading) group = precedingHeading.title
|
||||
}
|
||||
|
||||
for (let i = 0; i < numberedItems.length; i++) {
|
||||
const item = numberedItems[i]
|
||||
const nextStart = i + 1 < numberedItems.length ? numberedItems[i + 1].start : cleanedText.length
|
||||
const rawContent = cleanMagazineContent(cleanedText.slice(item.contentStart, nextStart))
|
||||
if (rawContent.length >= 15) {
|
||||
addSection(sections, item.title, rawContent, seen, group)
|
||||
}
|
||||
}
|
||||
|
||||
// Also try bullet items: "- **Title** — content"
|
||||
if (sections.length === 0) {
|
||||
const bulletRe = /(?:^|\n)\s*[-•]\s*\*\*([^*]+)\*\*\s*[-–—:]+\s*/g
|
||||
const bulletItems: { title: string; start: number; contentStart: number }[] = []
|
||||
while ((m = bulletRe.exec(cleanedText)) !== null) {
|
||||
const title = m[1].trim()
|
||||
if (title.length > 1) {
|
||||
bulletItems.push({ title, start: m.index, contentStart: m.index + m[0].length })
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < bulletItems.length; i++) {
|
||||
const item = bulletItems[i]
|
||||
const nextStart = i + 1 < bulletItems.length ? bulletItems[i + 1].start : cleanedText.length
|
||||
const rawContent = cleanMagazineContent(cleanedText.slice(item.contentStart, nextStart))
|
||||
if (rawContent.length >= 15) {
|
||||
addSection(sections, item.title, rawContent, seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also try standalone bold paragraphs: "**Title** — content\n\n**Next** — ..."
|
||||
if (sections.length === 0) {
|
||||
const boldParaRe = /(?:^|\n)\s*\*\*([^*]{3,60})\*\*\s*[-–—:]+\s*([\s\S]*?)(?=\n\s*\*\*[^*]{3,60}\*\*\s*[-–—:]|\n{2,}\*\*[^*]+\*\*\s*:|\s*$)/g
|
||||
while ((m = boldParaRe.exec(cleanedText)) !== null) {
|
||||
const title = m[1].trim()
|
||||
const content = cleanMagazineContent(m[2].trim())
|
||||
if (title.length > 2 && content.length >= 15) {
|
||||
addSection(sections, title, content, seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract intro paragraph as Summary
|
||||
if (sections.length > 0) {
|
||||
const firstStructured = numberedItems.length > 0
|
||||
? numberedItems[0].start
|
||||
: cleanedText.length
|
||||
// Bold pseudo-heading before items marks the end of the intro
|
||||
const precedingBoldHeading = boldHeadings.length > 0 && boldHeadings[0].start < firstStructured
|
||||
? boldHeadings[0].start
|
||||
: firstStructured
|
||||
const fallbackIntroEnd = Math.min(firstStructured, precedingBoldHeading)
|
||||
if (fallbackIntroEnd > 30) {
|
||||
const intro = cleanedText.slice(0, fallbackIntroEnd)
|
||||
.replace(/\*\*/g, '')
|
||||
.replace(/^[#*_\s-]+/gm, '')
|
||||
.trim()
|
||||
if (intro.length > 30 && !seen.has('Summary')) {
|
||||
addSection(sections, 'Summary', intro, seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const closingMatch = /(This is being called[^.]+\.[^"]*"[^"]+"[^.]*\.)/i.exec(cleanedText)
|
||||
if (closingMatch && !seen.has('Key')) {
|
||||
addSection(sections, 'Key takeaway', closingMatch[1].trim(), seen)
|
||||
|
||||
Reference in New Issue
Block a user