Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/vercel-react-best-practices
41 changes: 22 additions & 19 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
# Visionary Lab API - Environment Variables

# Azure OpenAI Sora Video Generation
MODEL_PROVIDER=azure

# Azure OpenAI for Sora Video Generation (Required)
# ----------------------------------------------------------------------
SORA_AOAI_RESOURCE=your-resource-name
SORA_DEPLOYMENT=your-sora-deployment-name
SORA_AOAI_API_KEY=your-api-key

# Azure OpenAI GPT-Image-1 Image Generation
# Azure OpenAI LLM for prompt rewrite and analysis (GPT 4.1 recommended)
# ----------------------------------------------------------------------
LLM_AOAI_RESOURCE=your-llm-resource-name
LLM_DEPLOYMENT=your-llm-deployment-name
LLM_AOAI_API_KEY=your-llm-api-key

# Azure OpenAI for Image Generation
# ----------------------------------------------------------------------
IMAGEGEN_AOAI_RESOURCE=your-image-resource-name
IMAGEGEN_DEPLOYMENT=your-image-deployment-name
IMAGEGEN_15_DEPLOYMENT=your-image-1.5-deployment-name
IMAGEGEN_1_MINI_DEPLOYMENT=your-image-1-mini-deployment-name
IMAGEGEN_AOAI_API_KEY=your-image-api-key

# For the best experience, use both Sora and GPT-Image-1.
# For the best experience, use both Sora and GPT-Image-1.
# However, the app also works if you use only one of these models.

# Azure OpenAI LLM for prompt rewrite and analysis (GPT 4.1 recommended)
# Azure Cosmos DB for metadata storage
# ----------------------------------------------------------------------
LLM_AOAI_RESOURCE=your-llm-resource-name
LLM_DEPLOYMENT=your-llm-deployment-name
LLM_AOAI_API_KEY=your-llm-api-key
AZURE_COSMOS_DB_ENDPOINT=https://<name_of_cosmos_account>.documents.azure.com:443/
AZURE_COSMOS_DB_ID=your-cosmos-db-id
AZURE_COSMOS_CONTAINER_ID=your-container-id
AZURE_COSMOS_DB_KEY=your-key
USE_MANAGED_IDENTITY=false

# Azure Blob Storage for asset storage
# ----------------------------------------------------------------------
Expand All @@ -29,16 +41,7 @@ AZURE_STORAGE_ACCOUNT_KEY=your-storage-account-key
AZURE_BLOB_IMAGE_CONTAINER=images
AZURE_BLOB_VIDEO_CONTAINER=videos


# Azure Cosmos DB for metadata storage
# ----------------------------------------------------------------------
AZURE_COSMOS_DB_ENDPOINT=https://<name_of_cosmos_account>.documents.azure.com:443/
AZURE_COMOS_DB_ID=your-cosmos-db-id
AZURE_COSMOS_CONTAINER_ID=your-container-id
AZURE_COSMOS_DB_KEY=your-key
USE_MANAGED_IDENTITY=true

# Do not change. API-Version used for GPT-Image-1
# Azure Front Door (optional)
# ----------------------------------------------------------------------
MODEL_PROVIDER=azure
AOAI_API_VERSION=2025-04-01-preview
# AZURE_FRONT_DOOR_ENDPOINT=https://your-frontdoor.azurefd.net
# AZURE_FRONT_DOOR_ENABLED=false
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,5 @@ videos
# Visionary Lab
notebooks/video-generations/
frontend/lighthouse-report.html
scripts/allow-local-access.sh
.agents
8 changes: 1 addition & 7 deletions backend/api/endpoints/gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
)
from backend.models.metadata_models import AssetMetadataCreateRequest

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

router = APIRouter()
Expand Down Expand Up @@ -501,12 +500,7 @@ async def upload_asset(
**result,
)
except Exception as e:
import traceback

error_detail = str(e)
error_trace = traceback.format_exc()
print(f"Upload error: {error_detail}")
print(f"Error trace: {error_trace}")
logger.error("Upload error: %s", e, exc_info=True)
raise HTTPException(status_code=500, detail=str(e))


Expand Down
1 change: 0 additions & 1 deletion backend/api/endpoints/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
router = APIRouter()

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

pipeline_service = ImagePipelineService()
Expand Down
4 changes: 0 additions & 4 deletions backend/api/endpoints/videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@


# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


Expand All @@ -62,9 +61,6 @@ def get_cosmos_service() -> Optional[CosmosDBService]:
return CosmosDBService()
return None
except Exception as e:
import logging

logger = logging.getLogger(__name__)
logger.warning(f"Cosmos DB service unavailable: {e}")
return None

Expand Down
1 change: 0 additions & 1 deletion backend/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from azure.storage.blob import generate_container_sas, ContainerSasPermissions

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize Sora 2 client
Expand Down
1 change: 0 additions & 1 deletion backend/core/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import cv2
import numpy as np

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class VideoExtractor:
Expand Down
3 changes: 3 additions & 0 deletions backend/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class Settings(BaseSettings):
IMAGE_DIR: str = "./static/images"
VIDEO_DIR: str = "./static/videos"

# Logging Configuration
LOG_LEVEL: str = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL

# GPT-Image-1 Default Settings
GPT_IMAGE_DEFAULT_SIZE: str = "1024x1024"
GPT_IMAGE_DEFAULT_QUALITY: str = "high"
Expand Down
1 change: 0 additions & 1 deletion backend/core/cosmos_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from azure.identity import DefaultAzureCredential, CredentialUnavailableError
from backend.core.config import settings

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


Expand Down
1 change: 0 additions & 1 deletion backend/core/gpt_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from tempfile import SpooledTemporaryFile

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


Expand Down
31 changes: 31 additions & 0 deletions backend/core/logging_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Centralized logging configuration for the backend application.

This module provides a single point of logging configuration. Call setup_logging()
once at application startup before importing other modules.
"""

import logging

from backend.core.config import settings


def setup_logging() -> None:
"""Configure logging for the entire application.

Call this function once at startup, before other modules are imported,
to ensure all loggers inherit this configuration.

The log level is controlled by the LOG_LEVEL environment variable,
defaulting to INFO if not specified.
"""
log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO)

logging.basicConfig(
level=log_level,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

# Suppress verbose Azure SDK logging
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(
logging.WARNING
)
1 change: 0 additions & 1 deletion backend/core/sora.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from typing import List, Optional

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


Expand Down
10 changes: 4 additions & 6 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
# Configure logging first, before other imports
from .core.logging_config import setup_logging
setup_logging()

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
import os
import logging
import uvicorn
from .core.config import settings
from .api.endpoints import images, metadata_router, videos, gallery, env

# Configure logging to suppress Azure Blob Storage verbose logs
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(
logging.WARNING
)

# Create directories if they don't exist
os.makedirs(settings.UPLOAD_DIR, exist_ok=True)
os.makedirs(settings.IMAGE_DIR, exist_ok=True)
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/analyze/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { Button } from "@/components/ui/button";
import { analyzeImageCustom, type ImageAnalysisResponse } from "@/services/api";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { PageTransition } from "@/components/ui/page-transition";
import dynamic from "next/dynamic";
const PageTransition = dynamic(() => import("@/components/ui/page-transition").then(m => ({ default: m.PageTransition })), { ssr: false });
import { useImageSettings } from "@/context/image-settings-context";

export default function AnalyzePage() {
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/edit-image/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import React from 'react';
import EditorContainer from './components/EditorContainer';
import { PageHeader } from '@/components/page-header';
import { FadeScaleTransition } from '@/components/ui/page-transition';
import dynamic from "next/dynamic";
const FadeScaleTransition = dynamic(() => import('@/components/ui/page-transition').then(m => ({ default: m.FadeScaleTransition })), { ssr: false });

export default function EditImagePage() {
return (
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/gallery/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
import { formatDistanceToNow } from "date-fns";
import { useSearchParams } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { SlideTransition } from "@/components/ui/page-transition";
import dynamic from "next/dynamic";
const SlideTransition = dynamic(() => import("@/components/ui/page-transition").then(m => ({ default: m.SlideTransition })), { ssr: false });

// Component that safely uses useSearchParams
function SearchParamsWrapper({ onFolderChange }: { onFolderChange: (folder: string | null) => void }) {
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/jobs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { RefreshCw, Clock } from "lucide-react"
import { PageHeader } from "@/components/page-header"
import { useJobs } from "@/context/jobs-context"
import { VideoJob, convertToVideoJob } from "@/types/jobs"
import { FadeScaleTransition } from "@/components/ui/page-transition"
import dynamic from "next/dynamic";
const FadeScaleTransition = dynamic(() => import("@/components/ui/page-transition").then(m => ({ default: m.FadeScaleTransition })), { ssr: false });

import {
Card,
Expand Down
1 change: 1 addition & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FolderProvider } from "@/context/folder-context";
import { VideoQueueClient } from "@/components/video-queue-client";
import { RefreshJobsButton } from "@/components/refresh-jobs-button";
import { Toaster } from "@/components/ui/sonner";
import dynamic from "next/dynamic";
import { AnimatedLayout } from "@/components/animated-layout";
import { SessionProvider } from "next-auth/react";
import { auth } from "@/auth";
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/new-image/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { ImageCreationContainer } from "@/components/ImageCreationContainer";
import { Badge } from "@/components/ui/badge";
import { useSearchParams } from "next/navigation";
import { FolderIcon } from "lucide-react";
import { PageTransition } from "@/components/ui/page-transition";
import dynamic from "next/dynamic";
const PageTransition = dynamic(() => import("@/components/ui/page-transition").then(m => ({ default: m.PageTransition })), { ssr: false });
import { ImageDetailView } from "@/components/ImageDetailView";
import { RowBasedMasonryGrid } from "@/components/RowBasedMasonryGrid";

Expand Down
3 changes: 2 additions & 1 deletion frontend/app/new-video/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { VideoOverlay } from "@/components/VideoOverlay";
import { useVideoQueue, registerGalleryRefreshCallback, unregisterGalleryRefreshCallback } from "@/context/video-queue-context";
import { protectImagePrompt, fetchFolders, MediaType } from "@/services/api";
import { useImageSettings } from "@/context/image-settings-context";
import { SlideTransition } from "@/components/ui/page-transition";
import dynamic from "next/dynamic";
const SlideTransition = dynamic(() => import("@/components/ui/page-transition").then(m => ({ default: m.SlideTransition })), { ssr: false });
import { VideoDetailView } from "@/components/VideoDetailView";

// Separate component that uses useSearchParams
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { Loader2, Check, AlertCircle, Plus, X, Shield } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { useImageSettings, BrandsProtectionMode } from "@/context/image-settings-context";
import { useSearchParams } from "next/navigation";
import { FadeScaleTransition } from "@/components/ui/page-transition";
import dynamic from "next/dynamic";
const FadeScaleTransition = dynamic(() => import("@/components/ui/page-transition").then(m => ({ default: m.FadeScaleTransition })), { ssr: false });

// No longer need video settings types

Expand Down
4 changes: 2 additions & 2 deletions frontend/components/ImageDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ export function ImageDetailView({
};

// Add event listeners
img.addEventListener('load', handleImageLoad);
img.addEventListener('error', handleImageError);
img.addEventListener('load', handleImageLoad, { passive: true });
img.addEventListener('error', handleImageError, { passive: true });

// Check if image is already loaded
if (img.complete) {
Expand Down
Loading