diff --git a/client/src/components/horoscope-widget.tsx b/client/src/components/horoscope-widget.tsx
index 48a7091..a5546f9 100644
--- a/client/src/components/horoscope-widget.tsx
+++ b/client/src/components/horoscope-widget.tsx
@@ -1,8 +1,15 @@
import { useState, useEffect, useCallback } from "react";
import { useLocation } from "wouter";
+import { useQuery } from "@tanstack/react-query";
import { Star, Heart, Briefcase, TrendingUp, ChevronLeft, ChevronRight } from "lucide-react";
import { SIGNS, ELEMENT_COLORS, getHoroscope, getRating } from "@/lib/horoscope-data";
+interface AIHoroscope {
+ signIndex: number;
+ signName: string;
+ general: string;
+}
+
function MiniStars({ count, max = 5 }: { count: number; max?: number }) {
return (
@@ -18,6 +25,12 @@ export function HoroscopeWidget() {
const [index, setIndex] = useState(0);
const [paused, setPaused] = useState(false);
+ const { data: aiHoroscopes = [] } = useQuery
({
+ queryKey: ["/api/horoscopes/today"],
+ staleTime: 1000 * 60 * 30,
+ refetchOnWindowFocus: true,
+ });
+
const next = useCallback(() => setIndex((i) => (i + 1) % SIGNS.length), []);
const prev = useCallback(() => setIndex((i) => (i - 1 + SIGNS.length) % SIGNS.length), []);
@@ -28,7 +41,9 @@ export function HoroscopeWidget() {
}, [paused, next]);
const sign = SIGNS[index];
- const horoscope = getHoroscope(index);
+ // Prefer the real AI-generated horoscope for today; fall back to static text until loaded.
+ const aiForSign = aiHoroscopes.find((h) => h.signIndex === index);
+ const generalText = aiForSign?.general || getHoroscope(index).general;
return (
-
{horoscope.general}
+
{generalText}
Mehr lesen
diff --git a/client/src/pages/horoscope.tsx b/client/src/pages/horoscope.tsx
index 7053f57..58844cf 100644
--- a/client/src/pages/horoscope.tsx
+++ b/client/src/pages/horoscope.tsx
@@ -77,7 +77,25 @@ function getEventColor(type: string) {
}
}
+interface CosmicEvent {
+ title: string;
+ description: string;
+ dateRange: string;
+ icon: string;
+ type: string;
+ affectedSigns: string[];
+}
+
function AstroEventsSection() {
+ const { data: apiEvents = [] } = useQuery
({
+ queryKey: ["/api/cosmic-events"],
+ staleTime: 1000 * 60 * 30,
+ refetchOnWindowFocus: true,
+ });
+
+ // Use live AI-generated events when available, otherwise fall back to static list.
+ const events = apiEvents.length > 0 ? apiEvents : ASTRO_EVENTS;
+
return (
@@ -85,7 +103,7 @@ function AstroEventsSection() {
Aktuelle kosmische Ereignisse
- {ASTRO_EVENTS.map((event, i) => {
+ {events.map((event, i) => {
const ec = getEventColor(event.type);
return (
({
queryKey: ["/api/horoscopes/today"],
+ staleTime: 1000 * 60 * 30,
+ refetchOnWindowFocus: true,
});
useEffect(() => {
diff --git a/server/horoscope-generator.ts b/server/horoscope-generator.ts
index 8eee5d1..6097dab 100644
--- a/server/horoscope-generator.ts
+++ b/server/horoscope-generator.ts
@@ -1,6 +1,6 @@
import OpenAI from "openai";
import { db } from "./db";
-import { dailyHoroscopes } from "@shared/schema";
+import { dailyHoroscopes, cosmicEvents } from "@shared/schema";
import { eq, and } from "drizzle-orm";
const openai = new OpenAI({
@@ -110,3 +110,102 @@ export async function getOrGenerateHoroscope(signIndex: number): Promise
{
+ const today = getTodayStr();
+ const rows = await db.select().from(cosmicEvents)
+ .where(eq(cosmicEvents.dateStr, today));
+ return rows
+ .sort((a, b) => a.position - b.position)
+ .map((r) => ({
+ title: r.title,
+ description: r.description,
+ dateRange: r.dateRange,
+ icon: r.icon,
+ type: r.type,
+ affectedSigns: JSON.parse(r.affectedSigns || "[]"),
+ }));
+}
+
+export async function generateCosmicEvents(): Promise {
+ const today = getTodayStr();
+
+ const existing = await db.select().from(cosmicEvents)
+ .where(eq(cosmicEvents.dateStr, today));
+ if (existing.length >= 6) {
+ console.log(`Cosmic events for ${today} already exist.`);
+ return;
+ }
+
+ console.log(`Generating cosmic events for ${today}...`);
+
+ const d = new Date();
+ const monthName = d.toLocaleDateString("de-DE", { month: "long", year: "numeric" });
+
+ try {
+ const response = await openai.chat.completions.create({
+ model: "gpt-5-mini",
+ messages: [
+ {
+ role: "system",
+ content: `Du bist ein erfahrener Astrologe, der die aktuellen kosmischen Ereignisse (Planetenkonstellationen, Mondphasen, Retrograden, Tierkreis-Saison) für eine deutschsprachige Volksmusik- und Schlager-Website beschreibt. Du kennst die realen astronomischen Daten. Heute ist der ${today} (${monthName}). Beschreibe ausschließlich Ereignisse, die JETZT, rund um dieses Datum tatsächlich aktiv oder relevant sind. Verwende realistische, dem aktuellen Datum entsprechende Datumsangaben. Schreibe warm und zugänglich auf Deutsch.`,
+ },
+ {
+ role: "user",
+ content: `Erstelle die 6 wichtigsten aktuellen kosmischen Ereignisse für heute (${today}). Dazu sollten gehören: die aktuelle Tierkreis-Saison (Sonne im Zeichen), die aktuelle Mondphase, eventuelle Retrograden, sowie relevante Planetentransite (Venus, Mars, Saturn, Jupiter).
+
+Antworte NUR mit einem JSON-Array von genau 6 Objekten (kein Markdown, keine Erklärung) in diesem exakten Format:
+[
+ {
+ "title": "Kurzer Titel, z.B. 'Vollmond im Skorpion' oder 'Zwillinge-Saison'",
+ "description": "3-4 Sätze über die Bedeutung dieses Ereignisses und seinen Einfluss.",
+ "dateRange": "Realistische Datumsangabe passend zum heutigen Datum, z.B. '21. Mai – 20. Juni 2026' oder '11. Juni 2026'",
+ "icon": "EINER VON: mercury, venus, moon, newmoon, sun, saturn, jupiter, mars",
+ "type": "EINER VON: retrograde, moon, transit, season",
+ "affectedSigns": ["3-4 betroffene Sternzeichen auf Deutsch, z.B. Widder, Stier, Zwillinge, Krebs, Löwe, Jungfrau, Waage, Skorpion, Schütze, Steinbock, Wassermann, Fische"]
+ }
+]`,
+ },
+ ],
+ max_completion_tokens: 2500,
+ });
+
+ const content = response.choices[0]?.message?.content || "";
+ const jsonMatch = content.match(/\[[\s\S]*\]/);
+ if (!jsonMatch) {
+ console.error("Failed to parse cosmic events");
+ return;
+ }
+
+ const parsed = JSON.parse(jsonMatch[0]);
+ if (!Array.isArray(parsed) || parsed.length === 0) {
+ console.error("Cosmic events response is not a valid array");
+ return;
+ }
+
+ for (let i = 0; i < parsed.length; i++) {
+ const ev = parsed[i];
+ const icon = VALID_ICONS.includes(ev.icon) ? ev.icon : "moon";
+ const type = VALID_TYPES.includes(ev.type) ? ev.type : "transit";
+ const signs = Array.isArray(ev.affectedSigns) ? ev.affectedSigns : [];
+
+ await db.insert(cosmicEvents).values({
+ dateStr: today,
+ position: i,
+ title: String(ev.title || "").slice(0, 120),
+ description: String(ev.description || ""),
+ dateRange: String(ev.dateRange || "").slice(0, 120),
+ icon,
+ type,
+ affectedSigns: JSON.stringify(signs),
+ });
+ }
+
+ console.log(`Generated ${parsed.length} cosmic events for ${today}.`);
+ } catch (err: any) {
+ console.error("Error generating cosmic events:", err.message);
+ }
+}
diff --git a/server/routes.ts b/server/routes.ts
index b10b9b6..97dd706 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -3,7 +3,8 @@ import { createServer, type Server } from "http";
import { storage } from "./storage";
import { insertArticleSchema } from "@shared/schema";
import { seedDatabase } from "./seed";
-import { generateDailyHoroscopes, getHoroscopesForToday, getOrGenerateHoroscope } from "./horoscope-generator";
+import { generateDailyHoroscopes, getHoroscopesForToday, getOrGenerateHoroscope, getCosmicEventsForToday, generateCosmicEvents } from "./horoscope-generator";
+import { startDailyScheduler } from "./scheduler";
import { analyzeAllArticleImages, getCachedFocalPoints } from "./focal-point";
import { optimizeImage } from "./image-optimizer";
import { getAuthUrl, exchangeCodeForTokens, isConnected, fetchGalleryFromDropbox, getValidAccessToken, migrateGalleryToCloudinary } from "./dropbox";
@@ -58,9 +59,7 @@ export async function registerRoutes(
): Promise {
await seedDatabase();
- generateDailyHoroscopes().catch((err) =>
- console.error("Background horoscope generation failed:", err.message)
- );
+ startDailyScheduler();
storage.getArticles().then((articles) => {
analyzeAllArticleImages(articles).catch(err =>
@@ -682,6 +681,26 @@ export async function registerRoutes(
}
});
+ // Cosmic events API
+ app.get("/api/cosmic-events", async (_req, res) => {
+ try {
+ const events = await getCosmicEventsForToday();
+ res.json(events);
+ } catch (err: any) {
+ res.status(500).json({ message: err.message });
+ }
+ });
+
+ app.post("/api/cosmic-events/generate", async (_req, res) => {
+ try {
+ await generateCosmicEvents();
+ const events = await getCosmicEventsForToday();
+ res.json({ generated: events.length, events });
+ } catch (err: any) {
+ res.status(500).json({ message: err.message });
+ }
+ });
+
function parseRssItems(xml: string, maxAgeDays?: number): { title: string; link: string; source: string; pubDate: string }[] {
const channelTitle = xml.match(/[\s\S]*?(.*?)<\/title>/)?.[1]?.replace(//g, "").trim() || "";
const items: { title: string; link: string; source: string; pubDate: string }[] = [];
diff --git a/server/scheduler.ts b/server/scheduler.ts
new file mode 100644
index 0000000..d07780e
--- /dev/null
+++ b/server/scheduler.ts
@@ -0,0 +1,28 @@
+import { generateDailyHoroscopes, generateCosmicEvents } from "./horoscope-generator";
+
+async function runDailyGeneration() {
+ try {
+ await generateDailyHoroscopes();
+ } catch (err: any) {
+ console.error("[scheduler] Horoscope generation failed:", err.message);
+ }
+ try {
+ await generateCosmicEvents();
+ } catch (err: any) {
+ console.error("[scheduler] Cosmic events generation failed:", err.message);
+ }
+}
+
+export function startDailyScheduler() {
+ // Run once at startup (generators are idempotent — they skip if today already exists).
+ runDailyGeneration();
+
+ // Re-check every 6 hours. Covers the day rollover without relying on container restarts.
+ const SIX_HOURS = 6 * 60 * 60 * 1000;
+ setInterval(() => {
+ console.log("[scheduler] Running scheduled daily generation check...");
+ runDailyGeneration();
+ }, SIX_HOURS);
+
+ console.log("[scheduler] Daily horoscope + cosmic events scheduler started.");
+}
diff --git a/shared/schema.ts b/shared/schema.ts
index 91d8e43..18ba3dd 100644
--- a/shared/schema.ts
+++ b/shared/schema.ts
@@ -50,6 +50,21 @@ export const dailyHoroscopes = pgTable("daily_horoscopes", {
export type DailyHoroscope = typeof dailyHoroscopes.$inferSelect;
+export const cosmicEvents = pgTable("cosmic_events", {
+ id: serial("id").primaryKey(),
+ dateStr: varchar("date_str", { length: 10 }).notNull(),
+ position: integer("position").notNull(),
+ title: varchar("title", { length: 120 }).notNull(),
+ description: text("description").notNull(),
+ dateRange: varchar("date_range", { length: 120 }).notNull(),
+ icon: varchar("icon", { length: 20 }).notNull(),
+ type: varchar("type", { length: 20 }).notNull(),
+ affectedSigns: text("affected_signs").notNull(),
+ createdAt: timestamp("created_at").notNull().defaultNow(),
+});
+
+export type CosmicEvent = typeof cosmicEvents.$inferSelect;
+
export const users = pgTable("users", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
username: text("username").notNull().unique(),