Mirror of
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: round time windows, add colors and standardize summary length

+27 -10
+23 -4
scripts/generate-digest.ts
··· 38 38 console.log("\n\x1b[1m🚀 Generating Intelligent Topic Digest\x1b[0m"); 39 39 40 40 const now = new Date(); 41 + const marks = [6, 14, 22]; 42 + 43 + const candidates: Date[] = []; 44 + [-1, 0, 1].forEach((dayOffset) => { 45 + marks.forEach((hour) => { 46 + const d = new Date(now); 47 + d.setUTCDate(d.getUTCDate() + dayOffset); 48 + d.setUTCHours(hour, 0, 0, 0); 49 + candidates.push(d); 50 + }); 51 + }); 52 + 53 + const nearestMark = candidates.reduce((prev, curr) => 54 + Math.abs(curr.getTime() - now.getTime()) < 55 + Math.abs(prev.getTime() - now.getTime()) 56 + ? curr 57 + : prev, 58 + ); 59 + 41 60 const windowSize = 8 * 60 * 60 * 1000; 42 - const startTime = new Date(now.getTime() - windowSize); 61 + const startTime = new Date(nearestMark.getTime() - windowSize); 43 62 44 63 try { 45 64 const [gh, bs] = await Promise.all([ ··· 58 77 const heroTopic = pickWeightedTopic(topics); 59 78 const catchyTitle = await generateCatchyTitle(heroTopic); 60 79 61 - const type = getPostType(now); 62 - const dateStr = now.toISOString().split("T")[0]; 80 + const type = getPostType(nearestMark); 81 + const dateStr = nearestMark.toISOString().split("T")[0]; 63 82 const slug = `${dateStr}-${type}`; 64 83 65 84 const postData = { 66 85 title: catchyTitle, 67 - date: now.toISOString(), 86 + date: nearestMark.toISOString(), 68 87 type, 69 88 topics, 70 89 };
+1 -6
src/lib/events.ts
··· 150 150 ); 151 151 152 152 const prompt = `You are a technical analyst for npmx. Group these events into 5-6 logical "Topics". 153 + Each summary should be around 50 words long. 153 154 Return ONLY JSON: { "topics": Topic[] }. 154 155 155 156 Topic Structure: ··· 173 174 response_format: { type: "json_object" }, 174 175 }); 175 176 176 - // Cast the parsed content to our interface 177 177 const parsed = JSON.parse(data.choices[0].message.content) as { 178 178 topics: Topic[]; 179 179 }; 180 180 181 - // Verify the topics array exists before processing 182 181 if (!parsed.topics || !Array.isArray(parsed.topics)) { 183 182 throw new Error("AI response missing 'topics' array"); 184 183 } 185 184 186 185 const topics = parsed.topics.map((t) => { 187 - // Step 1: Runtime validation via Zod 188 - // This is where it would have failed if the AI output was "messy" 189 186 const validated = TopicSchema.parse(t); 190 187 191 - // Step 2: Logic & Refinement 192 188 const hasBluesky = validated.sources.some( 193 189 (s) => s.platform === "bluesky", 194 190 ); ··· 208 204 ); 209 205 return topics.sort((a, b) => b.relevanceScore - a.relevanceScore); 210 206 } catch (err: any) { 211 - // If Zod fails, err.errors will contain exactly which field was missing/wrong 212 207 LOG.error(`AI Clustering failed: ${err.message}`); 213 208 return []; 214 209 }
+3
src/pages/posts/[id].astro
··· 126 126 .tag.daily { 127 127 color: #fbbf24; 128 128 } 129 + .tag.midday { 130 + color: #f97316; 131 + } 129 132 .tag.nightly { 130 133 color: #818cf8; 131 134 }