feat: prepare deployment and latest app updates

This commit is contained in:
eric
2026-03-22 03:18:03 +01:00
parent 74042182ed
commit e0ba54f2c3
8 changed files with 28 additions and 64 deletions

View File

@@ -1,6 +1,5 @@
/**
* Image Generation service using Fal.ai
* Supports NSFW content generation
*/
import { fal } from "@fal-ai/client";
@@ -14,23 +13,13 @@ fal.config({
credentials: config.fal.apiKey,
});
/**
* Available image generation models on Fal.ai
*/
const MODELS = {
fast: "fal-ai/flux/schnell" as const,
quality: "fal-ai/flux/dev" as const,
anime: "fal-ai/flux/dev" as const,
};
type ModelType = keyof typeof MODELS;
const FAL_IMAGE_MODEL = "fal-ai/flux-pro/v1.1-ultra" as const;
/**
* Image generation options
*/
export interface ImageGenOptions {
prompt: string;
model?: ModelType;
aspectRatio?: "1:1" | "16:9" | "9:16" | "4:3" | "3:4";
numImages?: number;
nsfw?: boolean;
@@ -128,7 +117,7 @@ Output ONLY the enhanced prompt, nothing else.`;
/**
* Enhance a prompt for better image generation
*/
function enhancePrompt(prompt: string, model: ModelType, nsfw: boolean): string {
function enhancePrompt(prompt: string, nsfw: boolean): string {
const qualityBoosts = ["highly detailed", "high quality", "sharp focus", "professional"];
const hasQuality = qualityBoosts.some((q) => prompt.toLowerCase().includes(q));
@@ -144,27 +133,9 @@ function enhancePrompt(prompt: string, model: ModelType, nsfw: boolean): string
}
}
if (model === "anime" && !prompt.toLowerCase().includes("anime")) {
prompt = `anime style, ${prompt}`;
}
return prompt;
}
/**
* Convert aspect ratio to image size
*/
function getImageSize(aspectRatio: string): { width: number; height: number } {
const sizes: Record<string, { width: number; height: number }> = {
"1:1": { width: 1024, height: 1024 },
"16:9": { width: 1344, height: 768 },
"9:16": { width: 768, height: 1344 },
"4:3": { width: 1152, height: 896 },
"3:4": { width: 896, height: 1152 },
};
return sizes[aspectRatio] || sizes["1:1"];
}
/**
* Image Generation Service using Fal.ai
*/
@@ -175,14 +146,11 @@ export class ImageGenService {
async generate(options: ImageGenOptions & { style?: string }): Promise<ImageGenResult> {
const {
prompt,
model = "fast",
aspectRatio = "1:1",
numImages = 1,
nsfw = false,
style,
} = options;
const modelId = MODELS[model];
// First, use AI to enhance vague NSFW prompts into detailed ones
const aiEnhancedPrompt = nsfw
@@ -190,25 +158,29 @@ export class ImageGenService {
: prompt;
// Then apply standard quality enhancements
const finalPrompt = enhancePrompt(aiEnhancedPrompt, model, nsfw);
const size = getImageSize(aspectRatio);
const finalPrompt = enhancePrompt(aiEnhancedPrompt, nsfw);
const safetyTolerance = nsfw ? "5" : "2";
logger.debug("Generating image with Fal.ai", {
model: modelId,
size,
model: FAL_IMAGE_MODEL,
aspectRatio,
numImages,
originalPromptLength: prompt.length,
finalPromptLength: finalPrompt.length,
nsfw,
safetyTolerance,
});
try {
const result = await fal.subscribe(modelId, {
const result = await fal.subscribe(FAL_IMAGE_MODEL, {
input: {
prompt: finalPrompt,
image_size: size,
aspect_ratio: aspectRatio,
num_images: Math.min(numImages, 4),
enable_safety_checker: false,
enable_safety_checker: true,
safety_tolerance: safetyTolerance,
output_format: "jpeg",
enhance_prompt: false,
},
logs: false,
});
@@ -229,20 +201,20 @@ export class ImageGenService {
}
logger.info("Image generated successfully", {
model: modelId,
model: FAL_IMAGE_MODEL,
numImages: urls.length,
});
return {
urls,
model: modelId,
model: FAL_IMAGE_MODEL,
prompt: finalPrompt,
};
} catch (error) {
logger.error("Image generation failed", {
model: modelId,
model: FAL_IMAGE_MODEL,
promptLength: finalPrompt.length,
size,
aspectRatio,
nsfw,
numImages,
});

View File

@@ -273,32 +273,23 @@ const toolHandlers: Record<string, ToolHandler> = {
const prompt = args.prompt as string;
const style = args.style as string | undefined;
const aspectRatio = (args.aspect_ratio as "1:1" | "16:9" | "9:16" | "4:3" | "3:4") || "1:1";
const quality = (args.quality as "fast" | "quality" | "anime") || "fast";
if (!prompt || prompt.trim().length === 0) {
return "Error: No prompt provided for image generation.";
}
if (!config.fal.apiKey) {
return "Error: Image generation is not configured (missing FAL_KEY).";
return "Error: Image generation is not configured (missing FAL_API_KEY or FAL_KEY).";
}
logger.info("Generating image", {
promptLength: prompt.length,
style,
aspectRatio,
quality,
userId: context.userId
});
try {
const imageGen = getImageGenService();
// Auto-select anime model for anime/hentai style
let modelChoice = quality;
if (style === "anime" || style === "hentai") {
modelChoice = "anime";
}
// Only enable NSFW if user explicitly requests it
const nsfwKeywords = /\b(naked|nude|nsfw|porn|xxx|hentai|sex|fuck|cock|pussy|tits)\b/i;
@@ -310,7 +301,6 @@ const toolHandlers: Record<string, ToolHandler> = {
const result = await imageGen.generate({
prompt,
model: modelChoice,
aspectRatio,
numImages: 1,
nsfw: isNsfwRequest && Boolean(context.nsfwImageEnabled),

View File

@@ -220,12 +220,12 @@ export const IMAGE_GEN_TOOL: ChatCompletionTool = {
aspect_ratio: {
type: "string",
enum: ["1:1", "16:9", "9:16", "4:3", "3:4"],
description: "Aspect ratio. Use 9:16 or 3:4 for portraits/full body, 16:9 for wide scenes.",
description: "Aspect ratio. Default to 1:1 to keep image generation cheaper unless the user explicitly wants portrait or widescreen framing. Use 9:16 or 3:4 for portraits/full body, 16:9 for wide scenes.",
},
quality: {
type: "string",
enum: ["fast", "quality", "anime"],
description: "Model selection. 'fast' = quick generation, 'quality' = higher detail, 'anime' = anime style.",
description: "Style hint only. The backend uses a single Fal FLUX Ultra model.",
},
},
required: ["prompt"],