Skip to content

Commit 3cea1ca

Browse files
committed
fun-fact tone control feature
1 parent 4aa5b6a commit 3cea1ca

File tree

13 files changed

+570
-45
lines changed

13 files changed

+570
-45
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Thank you for your interest in contributing to the Useless Facts App! This docum
1616
1. **Fork the repository** on GitHub
1717
2. **Clone your fork** locally:
1818
```bash
19-
git clone https://github.com/YOUR_USERNAME/useless-app.git
19+
git clone https://github.com/werther41/useless-app.git
2020
cd useless-app
2121
```
2222
3. **Install dependencies**:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ This project demonstrates the power of AI vibe coding throughout the entire deve
212212
1. **Clone the repository**
213213

214214
```bash
215-
git clone https://github.com/yourusername/useless-facts-app.git
215+
git clone https://github.com/werther41/useless-app.git
216216
cd useless-facts-app
217217
```
218218

@@ -403,7 +403,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
403403
If you have questions or need help:
404404

405405
1. Check the [documentation](documents/)
406-
2. Open an [issue](https://github.com/yourusername/useless-facts-app/issues)
406+
2. Open an [issue](https://github.com/werther41/useless-app/issues)
407407
3. Review the [API documentation](documents/api-docs.md)
408408

409409
---

app/api/facts/real-time/route.ts

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google"
33
import { streamText } from "ai"
44
import { nanoid } from "nanoid"
55

6+
import { db } from "@/lib/db"
67
import { generateEmbedding } from "@/lib/embeddings"
78
import { createFact } from "@/lib/facts"
89
import { initializeDatabase } from "@/lib/init-db"
9-
import { SYSTEM_PROMPT, createUserPrompt } from "@/lib/prompts"
10+
import {
11+
buildSystemPrompt,
12+
createUserPrompt,
13+
TonePreset,
14+
} from "@/lib/prompts"
1015
import { getRandomQueryText } from "@/lib/query-texts"
1116
import {
1217
findArticlesByTopics,
@@ -28,22 +33,42 @@ export async function POST(request: NextRequest) {
2833
// Initialize database if needed
2934
await initializeDatabase()
3035

31-
// Parse request body for selected topics
36+
// Parse request body for selected topics and tone
3237
let selectedTopics: string[] = []
3338
let matchedTopics: string[] = []
39+
let tone: TonePreset | null = null
40+
let articleId: string | null = null
3441

3542
try {
3643
const body = await request.json()
3744
selectedTopics = body.selectedTopics || []
45+
tone = body.tone || null
46+
articleId = body.articleId || null
3847
} catch (error) {
3948
// If no body or invalid JSON, continue with empty topics (fallback to random)
40-
console.log("No selected topics provided, using random query")
49+
console.log("No request body provided, using random query")
4150
}
4251

4352
let article: any = null
4453

45-
// Try topic-based search first if topics are provided
46-
if (selectedTopics.length > 0) {
54+
// If articleId is provided (for regenerate), fetch that specific article
55+
if (articleId) {
56+
try {
57+
const result = await db.execute(
58+
`SELECT * FROM news_articles WHERE id = ?`,
59+
[articleId]
60+
)
61+
if (result.rows.length > 0) {
62+
article = result.rows[0]
63+
console.log(`📰 Using provided article ID: ${articleId}`)
64+
}
65+
} catch (error) {
66+
console.log(`❌ Error fetching article by ID: ${articleId}`, error)
67+
}
68+
}
69+
70+
// Try topic-based search first if topics are provided and no articleId
71+
if (!article && selectedTopics.length > 0) {
4772
console.log(
4873
`🎯 Searching for articles with topics: ${selectedTopics.join(", ")}`
4974
)
@@ -124,11 +149,19 @@ export async function POST(request: NextRequest) {
124149
})
125150

126151
// Create the prompt for generating a fun fact
127-
const systemPrompt = SYSTEM_PROMPT
128-
const userPrompt = createUserPrompt(article.title, article.content)
152+
const systemPrompt = buildSystemPrompt(tone)
153+
const userPrompt = createUserPrompt(
154+
article.title,
155+
article.content,
156+
matchedTopics.length > 0 ? matchedTopics : undefined,
157+
!!articleId // isRegenerate if articleId was provided
158+
)
129159

130-
console.log(`🤖 System Prompt: "${systemPrompt}"`)
131-
console.log(`💬 User Prompt: "${userPrompt}"`)
160+
console.log(`🤖 System Prompt: "${systemPrompt.substring(0, 100)}..."`)
161+
console.log(`💬 User Prompt: "${userPrompt.substring(0, 100)}..."`)
162+
if (tone) {
163+
console.log(`🎭 Tone: ${tone}`)
164+
}
132165

133166
// Generate the streaming response using Gemini
134167
console.log(`🚀 Starting AI generation with Gemini...`)
@@ -139,7 +172,9 @@ export async function POST(request: NextRequest) {
139172
})
140173

141174
console.log(`✅ AI generation completed, creating streaming response...`)
142-
console.log(`📝 Expected JSON format: {"funFact": "..."}`)
175+
console.log(
176+
`📝 Expected JSON format: {"funFact": "...", "whyInteresting": "...", "sourceSnippet": "..."}`
177+
)
143178

144179
// Create response with metadata
145180
const response = result.toTextStreamResponse()
@@ -164,6 +199,9 @@ export async function POST(request: NextRequest) {
164199
response.headers.set("X-Matched-Topics", matchedTopics.join(", "))
165200
}
166201

202+
// Add article ID header for regenerate functionality
203+
response.headers.set("X-Article-ID", article.id)
204+
167205
// Add cache control headers to prevent caching issues
168206
response.headers.set(
169207
"Cache-Control",

app/api/facts/regenerate/route.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { NextRequest, NextResponse } from "next/server"
2+
import { createGoogleGenerativeAI } from "@ai-sdk/google"
3+
import { streamText } from "ai"
4+
5+
import { db } from "@/lib/db"
6+
import { initializeDatabase } from "@/lib/init-db"
7+
import {
8+
buildSystemPrompt,
9+
createUserPrompt,
10+
TonePreset,
11+
} from "@/lib/prompts"
12+
13+
// Force dynamic rendering
14+
export const dynamic = "force-dynamic"
15+
export const revalidate = 0
16+
17+
// Initialize the Google Generative AI provider
18+
const google = createGoogleGenerativeAI({
19+
apiKey: process.env.GOOGLE_API_KEY!,
20+
})
21+
22+
export async function POST(request: NextRequest) {
23+
try {
24+
// Initialize database if needed
25+
await initializeDatabase()
26+
27+
// Parse request body
28+
let articleId: string | null = null
29+
let tone: TonePreset | null = null
30+
31+
try {
32+
const body = await request.json()
33+
articleId = body.articleId || null
34+
tone = body.tone || null
35+
36+
if (!articleId) {
37+
return NextResponse.json(
38+
{
39+
success: false,
40+
error: "articleId is required",
41+
},
42+
{ status: 400 }
43+
)
44+
}
45+
} catch (error) {
46+
return NextResponse.json(
47+
{
48+
success: false,
49+
error: "Invalid request body",
50+
},
51+
{ status: 400 }
52+
)
53+
}
54+
55+
// Fetch the article by ID
56+
const result = await db.execute(
57+
`SELECT * FROM news_articles WHERE id = ?`,
58+
[articleId]
59+
)
60+
61+
if (result.rows.length === 0) {
62+
return NextResponse.json(
63+
{
64+
success: false,
65+
error: "Article not found",
66+
},
67+
{ status: 404 }
68+
)
69+
}
70+
71+
const article = result.rows[0]
72+
73+
console.log(`📰 Regenerating fact for article: ${articleId}`)
74+
console.log(`🎭 Tone: ${tone || "default"}`)
75+
76+
// Create the prompt for generating a new fact with different angle
77+
const systemPrompt = buildSystemPrompt(tone)
78+
const userPrompt = createUserPrompt(
79+
article.title as string,
80+
article.content as string,
81+
undefined, // No matched topics for regenerate
82+
true // isRegenerate = true
83+
)
84+
85+
console.log(`🤖 System Prompt: "${systemPrompt.substring(0, 100)}..."`)
86+
console.log(`💬 User Prompt: "${userPrompt.substring(0, 100)}..."`)
87+
88+
// Generate the streaming response using Gemini
89+
console.log(`🚀 Starting AI generation with Gemini...`)
90+
const aiResult = await streamText({
91+
model: google("models/gemini-2.0-flash-lite"),
92+
system: systemPrompt,
93+
prompt: userPrompt,
94+
})
95+
96+
console.log(`✅ AI generation completed, creating streaming response...`)
97+
console.log(
98+
`📝 Expected JSON format: {"funFact": "...", "whyInteresting": "...", "sourceSnippet": "..."}`
99+
)
100+
101+
// Create response with metadata
102+
const response = aiResult.toTextStreamResponse()
103+
104+
// Add custom headers with article metadata (sanitize for HTTP headers)
105+
response.headers.set(
106+
"X-Article-Source",
107+
(article.source as string).replace(/[^\x00-\x7F]/g, "")
108+
)
109+
response.headers.set("X-Article-URL", article.url as string)
110+
response.headers.set(
111+
"X-Article-Title",
112+
(article.title as string).replace(/[^\x00-\x7F]/g, "")
113+
)
114+
response.headers.set(
115+
"X-Article-Date",
116+
(article.published_at as string) || (article.created_at as string)
117+
)
118+
response.headers.set("X-Article-ID", article.id as string)
119+
120+
// Add cache control headers to prevent caching issues
121+
response.headers.set(
122+
"Cache-Control",
123+
"no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0, s-maxage=0"
124+
)
125+
response.headers.set("Pragma", "no-cache")
126+
response.headers.set("Expires", "0")
127+
response.headers.set("Surrogate-Control", "no-store")
128+
response.headers.set("CDN-Cache-Control", "no-store")
129+
response.headers.set("Vercel-CDN-Cache-Control", "no-store")
130+
response.headers.set("Cloudflare-CDN-Cache-Control", "no-store")
131+
132+
// Add timestamp to prevent caching
133+
response.headers.set("X-Timestamp", Date.now().toString())
134+
135+
return response
136+
} catch (error) {
137+
console.error("Error regenerating fact:", error)
138+
return NextResponse.json(
139+
{
140+
success: false,
141+
error:
142+
error instanceof Error
143+
? error.message
144+
: "Failed to regenerate fact",
145+
},
146+
{ status: 500 }
147+
)
148+
}
149+
}
150+

app/api/facts/save-realtime/route.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ export async function POST(request: NextRequest) {
1515
await initializeDatabase()
1616

1717
const body = await request.json()
18-
const { text, source, source_url } = body
18+
const {
19+
text,
20+
source,
21+
source_url,
22+
why_interesting,
23+
source_snippet,
24+
tone,
25+
article_id,
26+
} = body
1927

2028
// Validate the input
2129
if (!text || typeof text !== "string") {
@@ -36,6 +44,10 @@ export async function POST(request: NextRequest) {
3644
source: source || null,
3745
source_url: source_url || null,
3846
fact_type: "realtime",
47+
why_interesting: why_interesting || null,
48+
source_snippet: source_snippet || null,
49+
tone: tone || null,
50+
article_id: article_id || null,
3951
})
4052

4153
console.log(`💾 Saved real-time fact to database: ${factId}`)
@@ -48,6 +60,10 @@ export async function POST(request: NextRequest) {
4860
source: savedFact.source,
4961
source_url: savedFact.source_url,
5062
fact_type: savedFact.fact_type,
63+
why_interesting: savedFact.why_interesting,
64+
source_snippet: savedFact.source_snippet,
65+
tone: savedFact.tone,
66+
article_id: savedFact.article_id,
5167
created_at: savedFact.created_at,
5268
updated_at: savedFact.updated_at,
5369
},

components/fact-page-client.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,29 @@ export function FactPageClient({ fact }: FactPageClientProps) {
4848
</CardTitle>
4949
</CardHeader>
5050
<CardContent className="space-y-6">
51+
{/* Why It's Interesting */}
52+
{currentFact.why_interesting && (
53+
<div className="rounded-lg border border-primary/20 bg-primary/5 p-4">
54+
<p className="mb-1 text-sm font-semibold text-primary">
55+
Why it&apos;s interesting:
56+
</p>
57+
<p className="text-sm text-foreground">
58+
{currentFact.why_interesting}
59+
</p>
60+
</div>
61+
)}
62+
63+
{/* Source Snippet */}
64+
{currentFact.source_snippet && (
65+
<div className="rounded-lg border border-muted bg-muted/30 p-4">
66+
<p className="mb-1 text-sm font-semibold text-muted-foreground">
67+
Source snippet:
68+
</p>
69+
<p className="text-sm italic text-muted-foreground">
70+
&quot;{currentFact.source_snippet}&quot;
71+
</p>
72+
</div>
73+
)}
5174
{/* Rating Display */}
5275
<div className="flex items-center justify-center gap-4">
5376
<div className="flex items-center gap-2">

0 commit comments

Comments
 (0)