Fix hero card aspect ratio and add horoscope generation functionality
Introduce horoscope generation via OpenAI API, including new API endpoints and database schema. Adjust card components in `home.tsx` to use `aspect-[16/9]` for consistent image sizing, resolving previous height stretching issues. Update dependencies in `package.json`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 413891e8-d784-4bea-b9f5-91a5a68316b4 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: ca1aa952-242c-43c1-9e28-47aed39cee1b Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/413891e8-d784-4bea-b9f5-91a5a68316b4/nTLKCC5 Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export { registerChatRoutes } from "./routes";
|
||||
export { chatStorage, type IChatStorage } from "./storage";
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { Express, Request, Response } from "express";
|
||||
import OpenAI from "openai";
|
||||
import { chatStorage } from "./storage";
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.AI_INTEGRATIONS_OPENAI_API_KEY,
|
||||
baseURL: process.env.AI_INTEGRATIONS_OPENAI_BASE_URL,
|
||||
});
|
||||
|
||||
export function registerChatRoutes(app: Express): void {
|
||||
// Get all conversations
|
||||
app.get("/api/conversations", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const conversations = await chatStorage.getAllConversations();
|
||||
res.json(conversations);
|
||||
} catch (error) {
|
||||
console.error("Error fetching conversations:", error);
|
||||
res.status(500).json({ error: "Failed to fetch conversations" });
|
||||
}
|
||||
});
|
||||
|
||||
// Get single conversation with messages
|
||||
app.get("/api/conversations/:id", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
const conversation = await chatStorage.getConversation(id);
|
||||
if (!conversation) {
|
||||
return res.status(404).json({ error: "Conversation not found" });
|
||||
}
|
||||
const messages = await chatStorage.getMessagesByConversation(id);
|
||||
res.json({ ...conversation, messages });
|
||||
} catch (error) {
|
||||
console.error("Error fetching conversation:", error);
|
||||
res.status(500).json({ error: "Failed to fetch conversation" });
|
||||
}
|
||||
});
|
||||
|
||||
// Create new conversation
|
||||
app.post("/api/conversations", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { title } = req.body;
|
||||
const conversation = await chatStorage.createConversation(title || "New Chat");
|
||||
res.status(201).json(conversation);
|
||||
} catch (error) {
|
||||
console.error("Error creating conversation:", error);
|
||||
res.status(500).json({ error: "Failed to create conversation" });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete conversation
|
||||
app.delete("/api/conversations/:id", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id);
|
||||
await chatStorage.deleteConversation(id);
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error("Error deleting conversation:", error);
|
||||
res.status(500).json({ error: "Failed to delete conversation" });
|
||||
}
|
||||
});
|
||||
|
||||
// Send message and get AI response (streaming)
|
||||
app.post("/api/conversations/:id/messages", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const conversationId = parseInt(req.params.id);
|
||||
const { content } = req.body;
|
||||
|
||||
// Save user message
|
||||
await chatStorage.createMessage(conversationId, "user", content);
|
||||
|
||||
// Get conversation history for context
|
||||
const messages = await chatStorage.getMessagesByConversation(conversationId);
|
||||
const chatMessages = messages.map((m) => ({
|
||||
role: m.role as "user" | "assistant",
|
||||
content: m.content,
|
||||
}));
|
||||
|
||||
// Set up SSE
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
|
||||
// Stream response from OpenAI
|
||||
const stream = await openai.chat.completions.create({
|
||||
model: "gpt-5.1",
|
||||
messages: chatMessages,
|
||||
stream: true,
|
||||
max_completion_tokens: 8192,
|
||||
});
|
||||
|
||||
let fullResponse = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const content = chunk.choices[0]?.delta?.content || "";
|
||||
if (content) {
|
||||
fullResponse += content;
|
||||
res.write(`data: ${JSON.stringify({ content })}\n\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// Save assistant message
|
||||
await chatStorage.createMessage(conversationId, "assistant", fullResponse);
|
||||
|
||||
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
|
||||
res.end();
|
||||
} catch (error) {
|
||||
console.error("Error sending message:", error);
|
||||
// Check if headers already sent (SSE streaming started)
|
||||
if (res.headersSent) {
|
||||
res.write(`data: ${JSON.stringify({ error: "Failed to send message" })}\n\n`);
|
||||
res.end();
|
||||
} else {
|
||||
res.status(500).json({ error: "Failed to send message" });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { db } from "../../db";
|
||||
import { conversations, messages } from "@shared/schema";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
|
||||
export interface IChatStorage {
|
||||
getConversation(id: number): Promise<typeof conversations.$inferSelect | undefined>;
|
||||
getAllConversations(): Promise<(typeof conversations.$inferSelect)[]>;
|
||||
createConversation(title: string): Promise<typeof conversations.$inferSelect>;
|
||||
deleteConversation(id: number): Promise<void>;
|
||||
getMessagesByConversation(conversationId: number): Promise<(typeof messages.$inferSelect)[]>;
|
||||
createMessage(conversationId: number, role: string, content: string): Promise<typeof messages.$inferSelect>;
|
||||
}
|
||||
|
||||
export const chatStorage: IChatStorage = {
|
||||
async getConversation(id: number) {
|
||||
const [conversation] = await db.select().from(conversations).where(eq(conversations.id, id));
|
||||
return conversation;
|
||||
},
|
||||
|
||||
async getAllConversations() {
|
||||
return db.select().from(conversations).orderBy(desc(conversations.createdAt));
|
||||
},
|
||||
|
||||
async createConversation(title: string) {
|
||||
const [conversation] = await db.insert(conversations).values({ title }).returning();
|
||||
return conversation;
|
||||
},
|
||||
|
||||
async deleteConversation(id: number) {
|
||||
await db.delete(messages).where(eq(messages.conversationId, id));
|
||||
await db.delete(conversations).where(eq(conversations.id, id));
|
||||
},
|
||||
|
||||
async getMessagesByConversation(conversationId: number) {
|
||||
return db.select().from(messages).where(eq(messages.conversationId, conversationId)).orderBy(messages.createdAt);
|
||||
},
|
||||
|
||||
async createMessage(conversationId: number, role: string, content: string) {
|
||||
const [message] = await db.insert(messages).values({ conversationId, role, content }).returning();
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user