daily prompts

This commit is contained in:
2026-06-08 19:50:49 +10:00
parent 92e4761c35
commit df8cd67c90
7 changed files with 257 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
import { useBorgesStore } from '../../store/borgesStore'
import { PromptHero } from './PromptHero'
function daysSince(iso: string): number {
return Math.floor((Date.now() - new Date(iso).getTime()) / 86_400_000)
@@ -42,6 +43,7 @@ export function Dashboard(): JSX.Element {
return (
<div className="dashboard">
<PromptHero />
<div className="dashboard-greeting">
{stories.length === 0
? 'Welcome to Borges. Create your first story to get started.'

View File

@@ -0,0 +1,112 @@
import { useEffect, useRef, useState } from 'react'
import { useBorgesStore } from '../../store/borgesStore'
const CACHE_KEY = 'borges:dailyPrompt'
interface CachedPrompt {
date: string
text: string
}
function todayISO(): string {
return new Date().toISOString().slice(0, 10)
}
export function PromptHero(): JSX.Element {
const { stories, setStories, setActiveStory, isDirty, activeStoryPath, activeStoryContent, markSaved } = useBorgesStore()
const [prompt, setPrompt] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const abortRef = useRef(false)
const fetchPrompt = async (): Promise<void> => {
abortRef.current = false
setLoading(true)
setError(null)
setPrompt('')
try {
await window.api.generatePrompt((chunk) => {
if (!abortRef.current) setPrompt((p) => p + chunk)
})
setLoading(false)
} catch (err) {
if (!abortRef.current) {
setError(err instanceof Error ? err.message : 'Failed to generate prompt.')
setLoading(false)
}
}
}
useEffect(() => {
const cached = localStorage.getItem(CACHE_KEY)
if (cached) {
try {
const parsed: CachedPrompt = JSON.parse(cached)
if (parsed.date === todayISO() && parsed.text) {
setPrompt(parsed.text)
return
}
} catch { /* invalid cache */ }
}
fetchPrompt()
return () => { abortRef.current = true }
}, [])
// Persist completed prompt to localStorage
useEffect(() => {
if (!loading && prompt && !error) {
localStorage.setItem(CACHE_KEY, JSON.stringify({ date: todayISO(), text: prompt }))
}
}, [loading, prompt, error])
const handleRegenerate = (): void => {
localStorage.removeItem(CACHE_KEY)
fetchPrompt()
}
const handleWrite = async (): Promise<void> => {
if (isDirty && activeStoryPath) {
await window.api.writeStory(activeStoryPath, activeStoryContent)
await window.api.saveRevision(activeStoryPath, activeStoryContent)
markSaved()
}
const name = `Story ${stories.length + 1}`
const created = await window.api.createStory(name)
const refreshed = await window.api.listStories()
setStories(refreshed)
const story = refreshed.find((s) => s.path === created.path)
if (story) {
const initial = `> ${prompt}\n\n`
await window.api.writeStory(story.path, initial)
setActiveStory(story.path, story.id, initial)
}
}
return (
<div className="prompt-hero">
<div className="prompt-hero-label">Today's prompt</div>
<div className={`prompt-hero-text${loading ? ' prompt-hero-text--loading' : ''}`}>
{error
? <span className="prompt-hero-error">{error}</span>
: prompt || <span className="prompt-hero-placeholder"> </span>
}
</div>
<div className="prompt-hero-actions">
<button
className="prompt-hero-btn prompt-hero-btn--primary"
onClick={handleWrite}
disabled={loading || !prompt || !!error}
>
Write this
</button>
<button
className="prompt-hero-btn"
onClick={handleRegenerate}
disabled={loading}
>
{loading ? 'Generating' : 'New prompt'}
</button>
</div>
</div>
)
}