supabase
This commit is contained in:
parent
1d6660639a
commit
cb040c6580
@ -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 = `${caption}`;
|
||||
const markdown = `${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,
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)}`;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user