This commit is contained in:
lovebird 2026-04-08 12:12:14 +02:00
parent 1d6660639a
commit cb040c6580
4 changed files with 73 additions and 70 deletions

View File

@ -6,56 +6,12 @@
*/
import type { RunnableToolFunctionWithParse } from 'openai/lib/RunnableFunction';
import { createImage as createImageRouter } from '@/lib/image-router';
import { supabase } from '@/integrations/supabase/client';
import { createImageTool } from '@/modules/ai/imageTools';
type LogFunction = (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => void;
const defaultLog: LogFunction = (level, message, data) => console.log(`[${level}] ${message}`, data);
/**
* Upload image data to temp-images bucket
*/
const uploadToTempBucket = async (
imageData: ArrayBuffer,
prompt: string,
userId: string,
addLog: LogFunction = defaultLog
): Promise<string | null> => {
try {
// Create filename with timestamp and user ID
const timestamp = Date.now();
const fileName = `${userId}/${timestamp}-${prompt.slice(0, 20).replace(/[^a-zA-Z0-9]/g, '-')}.png`;
// Convert ArrayBuffer to Uint8Array for upload
const uint8Array = new Uint8Array(imageData);
// Upload to temp-images bucket
const { data, error } = await supabase.storage
.from('temp-images')
.upload(fileName, uint8Array, {
contentType: 'image/png',
cacheControl: '3600', // Cache for 1 hour
});
if (error) {
addLog('error', 'Failed to upload to temp-images bucket:', error);
return null;
}
// Get public URL
const { data: { publicUrl } } = supabase.storage
.from('temp-images')
.getPublicUrl(fileName);
addLog('info', 'Image uploaded to temp bucket:', { fileName, publicUrl });
return publicUrl;
} catch (error) {
addLog('error', 'Error uploading to temp bucket:', error);
return null;
}
};
// ── Input types ──
interface GenerateMarkdownImageInput {
@ -105,42 +61,35 @@ export const generateMarkdownImageTool = (userId: string, addLog: LogFunction =
prompt: args.prompt.substring(0, 100)
});
// Generate image using the image router
const result = await createImageRouter(args.prompt);
if (!result) {
const imageTool = createImageTool(userId, addLog, args.model);
const imageResultRaw = await (imageTool.function.function as any)({
prompt: args.prompt,
altText: args.altText,
caption: args.caption,
});
const imageResult = typeof imageResultRaw === 'string' ? JSON.parse(imageResultRaw) : imageResultRaw;
if (!imageResult?.success || !imageResult?.imageUrl) {
return {
success: false,
error: 'Failed to generate image',
markdown: `_Image generation failed for: "${args.prompt}"_`
};
}
// Upload to temp-images bucket
const publicUrl = await uploadToTempBucket(result.imageData, args.prompt, userId, addLog);
if (!publicUrl) {
return {
success: false,
error: 'Failed to upload image to storage',
markdown: `_Image upload failed for: "${args.prompt}"_`
error: imageResult?.error || 'Failed to generate or upload image',
markdown: imageResult?.markdown || `_Image generation failed for: "${args.prompt}"_`
};
}
// Build markdown with proper image syntax
const altText = args.altText || args.prompt;
const caption = args.caption ? `\n*${args.caption}*` : '';
const markdown = `![${altText}](${publicUrl})${caption}`;
const markdown = `![${altText}](${imageResult.imageUrl})${caption}`;
addLog('info', 'Image generated and embedded in markdown', {
originalUrl: publicUrl,
originalUrl: imageResult.imageUrl,
markdownLength: markdown.length
});
return {
success: true,
markdown,
imageUrl: publicUrl,
imageUrl: imageResult.imageUrl,
message: 'Image generated and embedded in markdown successfully'
};
} catch (error: any) {
@ -203,7 +152,7 @@ export const generateTextWithImagesTool = (userId: string, addLog: LogFunction =
if (args.imagePrompts && args.imagePrompts.length > 0) {
const markdownImageTool = generateMarkdownImageTool(userId, addLog);
for (const imagePrompt of args.imagePrompts) {
const imageResult = await markdownImageTool.function.function({
const imageResult = await (markdownImageTool.function.function as any)({
prompt: imagePrompt.prompt,
altText: imagePrompt.altText,
caption: imagePrompt.caption,

View File

@ -46,6 +46,20 @@ export const uploadImage = async (file: File, userId: string): Promise<{ publicU
return { publicUrl: data.url, meta: data.meta };
};
/**
* Uploads generated image bytes using the same configured image upload flow.
*/
export const uploadGeneratedImageData = async (
imageData: ArrayBuffer,
prompt: string,
userId: string,
): Promise<{ publicUrl: string, meta?: any }> => {
const ts = Date.now();
const slug = prompt.slice(0, 20).replace(/[^a-zA-Z0-9]/g, '-');
const file = new File([imageData], `${ts}-${slug}.png`, { type: 'image/png' });
return uploadImage(file, userId);
};
/**
* Creates a picture record in the database.
*/

View File

@ -9,6 +9,7 @@
import { z } from 'zod';
import { createImage as createImageRouter } from '@/lib/image-router';
import { supabase } from '@/integrations/supabase/client';
import { uploadGeneratedImageData } from '@/lib/uploadUtils';
import type { RunnableToolFunctionWithParse } from 'openai/lib/RunnableFunction';
type LogFunction = (level: 'info' | 'warn' | 'error' | 'debug', message: string, data?: any) => void;
@ -16,7 +17,11 @@ const defaultLog: LogFunction = (level, message, data) => console.log(`[IMAGE-TO
// ── Upload helper ────────────────────────────────────────────────────────
const uploadToTempBucket = async (
const AI_IMAGE_UPLOAD_BACKEND = (import.meta.env.VITE_AI_IMAGE_UPLOAD_BACKEND || 'vfs').toLowerCase() === 'supabase'
? 'supabase'
: 'vfs';
const uploadToSupabaseTempBucket = async (
imageData: ArrayBuffer,
prompt: string,
userId: string,
@ -49,6 +54,26 @@ const uploadToTempBucket = async (
}
};
const uploadGeneratedImage = async (
imageData: ArrayBuffer,
prompt: string,
userId: string,
addLog: LogFunction,
): Promise<string | null> => {
if (AI_IMAGE_UPLOAD_BACKEND === 'vfs') {
try {
const { publicUrl } = await uploadGeneratedImageData(imageData, prompt, userId);
addLog('info', 'Image uploaded via VFS flow', { publicUrl });
return publicUrl;
} catch (err) {
addLog('error', 'VFS upload failed', err);
return null;
}
}
return uploadToSupabaseTempBucket(imageData, prompt, userId, addLog);
};
// ── Zod schemas ──────────────────────────────────────────────────────────
const generateImageSchema = z.object({
@ -69,7 +94,7 @@ export const createImageTool = (
function: {
name: 'generate_image',
description:
'Generate an image from a text prompt. The image is uploaded to storage and ' +
'Generate an image from a text prompt. The image is uploaded to configured storage and ' +
'returned as a markdown image tag you can embed in your response. ' +
'Use descriptive, detailed prompts for best results.',
parameters: {
@ -106,7 +131,7 @@ export const createImageTool = (
});
}
const publicUrl = await uploadToTempBucket(result.imageData, args.prompt, userId, addLog);
const publicUrl = await uploadGeneratedImage(result.imageData, args.prompt, userId, addLog);
if (!publicUrl) {
return JSON.stringify({
success: false,

View File

@ -2,6 +2,7 @@ import { PostMediaItem } from "@/modules/posts/views/types";
import { MediaItem } from "@/types";
import { SupabaseClient } from "@supabase/supabase-js";
import { fetchWithDeduplication, apiClient, getAuthHeaders } from "@/lib/db";
import { uploadImage } from "@/lib/uploadUtils";
import { FetchMediaOptions } from "@/utils/mediaUtils";
/*
{
@ -78,8 +79,22 @@ export const fetchUserPictures = async (userId: string) => (await fetchPictures(
/** Convenience alias: fetch N recent pictures */
export const fetchRecentPictures = async (limit: number = 50) => (await fetchPictures({ limit })).data;
const POSTS_UPLOAD_BACKEND = (import.meta.env.VITE_POSTS_UPLOAD_BACKEND || 'vfs').toLowerCase() === 'supabase'
? 'supabase'
: 'vfs';
export const uploadFileToStorage = async (userId: string, file: File | Blob, fileName?: string, client?: SupabaseClient) => {
// Keep direct Supabase storage call for large files / efficiency
if (POSTS_UPLOAD_BACKEND === 'vfs') {
const uploadFile = file instanceof File
? file
: new File([file], fileName || `${userId}/${Date.now()}-${Math.random().toString(36).substring(7)}`, {
type: file.type || 'application/octet-stream'
});
const { publicUrl } = await uploadImage(uploadFile, userId);
return publicUrl;
}
// Legacy fallback: direct Supabase storage upload.
const { supabase } = await import("@/integrations/supabase/client");
const db = client || supabase;
const name = fileName || `${userId}/${Date.now()}-${Math.random().toString(36).substring(7)}`;