Untitled
"server only"; import { auth } from "@clerk/nextjs/server"; import { generateText } from 'ai'; import { google } from '@ai-sdk/google'; import { v4 as uuidv4 } from "uuid"; import { desc, eq } from "drizzle-orm"; import { fal } from "@fal-ai/client"; import { z } from "zod"; import { getChatMessages, insertMessage } from "./messages"; import { db } from "@/db/db"; import { character, chatParticipants } from "@/db/schema"; const CharacterType = z.object({ name: z.string(), age: z.string(), profession: z.string(), physical_appearance: z.string(), personality: z.string(), background: z.string(), tone_and_speech: z.string(), habits_and_mannerisms: z.string(), initialMessage: z.string(), }); export const createCharacter = async () => { if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) { throw new Error("Google API key is not configured"); } const result = await db.select().from(character); // Initialize Google AI model with proper configuration const model = google('gemini-1.5-flash', { apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY, // Add this line structuredOutputs: true, }); try { const { text } = await generateText({ model, messages: [ { role: "user", content: [ { type: "text", text: `Return ONLY a JSON object with no markdown formatting or additional text. The JSON should have this exact structure: { "name": string, "age": string, "profession": string, "physical_appearance": string, "personality": string, "background": string, "tone_and_speech": string, "habits_and_mannerisms": string, "initialMessage": string } Requirements: - Name: Create a memorable name (NOT Zara) - Age: Between 10 and 80 - Profession: Real-world profession (NOT urban forger, NO sci-fi) - Physical Appearance: Detailed facial description - Personality: 2-3 realistic traits - Background: Brief, realistic backstory - Tone and Speech: Casual, modern communication style - Habits: Realistic behaviors and quirks - Initial Message: Casual, friendly first text message Communication style: - Use casual language (omg, lol, k, yeah, nah) - Include natural typos - Use contractions - Sound human and relatable IMPORTANT: Character must be different from these existing characters: ${JSON.stringify(result)} Must be a realistic, modern person - NO sci-fi or anime characters.` } ] } ] }); let cleanedText = text; if (text.includes('```json')) { cleanedText = text.replace(/```json\n|\n```/g, ''); } // Parse and validate the response let characterData; try { characterData = CharacterType.parse(JSON.parse(cleanedText)); } catch (parseError) { console.error("JSON Parse Error:", parseError); console.error("Raw text:", text); throw new Error("Invalid JSON response from AI"); } // Generate profile image const { imageUrl } = await createProfileImage(characterData.physical_appearance); // Insert character into database return insertCharacter({ ...characterData, imageUrl, }); } catch (error) { console.error("Error creating character:", error); // Improve error handling with more specific messages if (error.status === 401) { throw new Error("Authentication failed - check your Google AI API key"); } throw new Error("Failed to generate character"); } }; const createProfileImage = async (description: string) => { const result = await fal.subscribe("fal-ai/flux-pro/v1.1-ultra", { input: { prompt: description + `\n Please make the picture of the person, like it were a profile picture taken for a social media, there full face has to be visible. It's just a picture of them and not a screenshot of a website or profile. Consider this to be an image for their social media profile picture. `, }, logs: true, onQueueUpdate: (update) => { if (update.status === "IN_PROGRESS") { update.logs.map((log) => log.message).forEach(console.log); } }, }); return { imageUrl: result.data?.images?.[0]?.url, }; }; const insertCharacter = async ({ name, age, profession, physical_appearance, personality, background, tone_and_speech, habits_and_mannerisms, imageUrl, initialMessage, }: any) => { await db.insert(character).values({ name, age, profession, physical_appearance, personality, background, tone_and_speech, habits_and_mannerisms, profile_image: imageUrl, initial_message: initialMessage, }); }; export const swapCharacter = async () => { const { userId } = await auth(); await db .delete(chatParticipants) .where(eq(chatParticipants.user_id, userId!)); return await getCharacter(); }; let lastCharacterIndex = -1; function getRandomIndex(length: number) { let newIndex; do { newIndex = Math.floor(Math.random() * length); } while (newIndex === lastCharacterIndex && length > 1); lastCharacterIndex = newIndex; return newIndex; } export const getCharacter = async () => { const { userId } = await auth(); // Check if the user already has an assigned character const existingParticipant = await db .select() .from(chatParticipants) .where(eq(chatParticipants.user_id, userId!)) .limit(1); if (existingParticipant.length > 0) { // Fetch the character based on the existing participant const existingCharacter = await db .select() .from(character) .where(eq(character.id, existingParticipant[0].character_id!)) .limit(1); const messages = await getChatMessages(existingParticipant?.[0]?.chat_id); return { character: existingCharacter?.[0], chatParticipants: existingParticipant?.[0], messages, }; } const characters = await db.select().from(character); const charLength = characters.length; // Check if there are any characters to avoid errors if (charLength === 0) { await createCharacter(); const newCharacters = await db.select().from(character); await console.log(newCharacters); } // Generate a random index that's different from the last one const randomIndex = getRandomIndex(charLength); const info = await createChat({ char: characters[randomIndex], }); // Return the random character return info; }; const createChat = async ({ char }: any) => { const { userId } = await auth(); const chatId = uuidv4(); await db.insert(chatParticipants).values({ chat_id: chatId, character_id: char?.id, user_id: userId, }); if (char?.initial_message) { await insertMessage(chatId, "assistant", char?.initial_message!); } return { character: char, messages: [], }; }; export const getAllCharacters = async () => { return db.select().from(character).orderBy(desc(character.id)); };
Leave a Comment