Untitled
unknown
plain_text
9 months ago
7.4 kB
12
Indexable
"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));
};
Editor is loading...
Leave a Comment