feat(fastapi): adds slide assets generation, adds dict and schema processors, fix(nextjs): changes image and icon schema fields to __xxxxx__

This commit is contained in:
sauravniraula 2025-07-17 06:38:02 +05:45
parent ede81ab9db
commit 03b2b06ff0
No known key found for this signature in database
GPG key ID: 60FCC1B5A5E83326
44 changed files with 331 additions and 316 deletions

View file

@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y \
WORKDIR /app
# Set environment variables
ENV APP_DATA_DIRECTORY=/app/user_data
ENV APP_DATA_DIRECTORY=/app_data
ENV TEMP_DIRECTORY=/tmp/presenton
# Install ollama

View file

@ -17,7 +17,7 @@ WORKDIR /app
RUN ls -a
# Set environment variables
ENV APP_DATA_DIRECTORY=/app/user_data
ENV APP_DATA_DIRECTORY=/app_data
ENV TEMP_DIRECTORY=/tmp/presenton
# Install ollama

BIN
app_data/fastapi.db Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

1
app_data/settings.json Normal file
View file

@ -0,0 +1 @@
{}

1
app_data/userConfig.json Normal file
View file

@ -0,0 +1 @@
{"LLM":"google","GOOGLE_API_KEY":"AIzaSyAFY4MWd8aE7L4qWkFdrqpDAMgO2M63Cc4","OLLAMA_URL":"http://localhost:11434","USE_CUSTOM_URL":false}

View file

@ -8,7 +8,7 @@ services:
# You can replace 5000 with any other port number of your choice to run Presenton on a different port number.
- "5000:80"
volumes:
- ./user_data:/app/user_data
- ./app_data:/app_data
environment:
- CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS}
- LLM=${LLM}
@ -38,7 +38,7 @@ services:
# You can replace 5000 with any other port number of your choice to run Presenton on a different port number.
- "5000:80"
volumes:
- ./user_data:/app/user_data
- ./app_data:/app_data
environment:
- CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS}
- LLM=${LLM}
@ -62,6 +62,7 @@ services:
- "8000:8000"
volumes:
- .:/app
- ./app_data:/app_data
environment:
- NODE_ENV=development
- CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS}
@ -93,6 +94,7 @@ services:
- "8000:8000"
volumes:
- .:/app
- ./app_data:/app_data
environment:
- NODE_ENV=development
- CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS}

View file

@ -25,5 +25,17 @@ http {
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
location /static {
proxy_pass http://localhost:8000;
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
location /app_data {
proxy_pass http://localhost:8000;
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
}
}

View file

@ -4,6 +4,7 @@ from fastapi.staticfiles import StaticFiles
from api.lifespan import app_lifespan
from api.middlewares import UserConfigEnvUpdateMiddleware
from api.v1.ppt.router import API_V1_PPT_ROUTER
from utils.asset_directory_utils import get_images_directory
app = FastAPI(lifespan=app_lifespan)
@ -14,7 +15,11 @@ app.include_router(API_V1_PPT_ROUTER)
# Static files
app.mount("/static", StaticFiles(directory="static"), name="static")
# APP.mount("/static/app-data", StaticFiles(directory=get_app_data_directory_env()))
app.mount(
"/app_data/images",
StaticFiles(directory=get_images_directory()),
name="app_data/images",
)
# Middlewares

View file

@ -1,16 +1,15 @@
import os
from fastapi import APIRouter, Body
from fastapi import APIRouter
from models.image_prompt import ImagePrompt
from services.image_generation_service import ImageGenerationService
from utils.get_env import get_app_data_directory_env
from utils.asset_directory_utils import get_images_directory
IMAGES_ROUTER = APIRouter(prefix="/images", tags=["Images"])
@IMAGES_ROUTER.get("/generate")
async def generate_image(prompt: str):
images_directory = os.path.join(get_app_data_directory_env(), "images")
images_directory = get_images_directory()
image_prompt = ImagePrompt(prompt=prompt)
image_generation_service = ImageGenerationService(images_directory)

View file

@ -187,7 +187,8 @@ async def stream_presentation(presentation_id: str):
layout = presentation.get_layout()
outline = presentation.get_presentation_outline()
asyncio_tasks = []
# These tasks will be gathered and awaited after all slides are generated
async_assets_generation_tasks = []
slides: List[SlideModel] = []
yield SSEResponse(
@ -206,7 +207,7 @@ async def stream_presentation(presentation_id: str):
content=slide_content,
)
slides.append(slide)
asyncio_tasks.append(process_slide_and_fetch_assets(slide))
async_assets_generation_tasks.append(process_slide_and_fetch_assets(slide))
yield SSEResponse(
event="response",
data=json.dumps({"type": "chunk", "chunk": slide.model_dump_json()}),
@ -217,11 +218,15 @@ async def stream_presentation(presentation_id: str):
data=json.dumps({"type": "chunk", "chunk": " ] }"}),
).to_string()
await asyncio.gather(*asyncio_tasks)
generated_assets_lists = await asyncio.gather(*async_assets_generation_tasks)
generated_assets = []
for assets_list in generated_assets_lists:
generated_assets.extend(assets_list)
with get_sql_session() as sql_session:
sql_session.add(presentation)
sql_session.add_all(slides)
sql_session.add_all(generated_assets)
sql_session.commit()
sql_session.refresh(presentation)
for each_slide in slides:

View file

@ -4,7 +4,8 @@ from pydantic import BaseModel, Field, HttpUrl, EmailStr
from models.presentation_layout import PresentationLayoutModel, SlideLayoutModel
from models.presentation_outline_model import PresentationOutlineModel
from services.schema_processor import SchemaProcessor
from utils.dict_utils import get_dict_at_path, get_dict_paths_with_key
from utils.schema_utils import remove_fields_from_schema
class ContactInfoModel(BaseModel):
@ -16,9 +17,8 @@ class ContactInfoModel(BaseModel):
class ImageModel(BaseModel):
url: str = Field(description="Image URL")
__image_type__: Literal["image"] = "image"
prompt: str = Field(description="Image prompt")
image_url__: str = Field(description="Image URL")
image_prompt__: str = Field(description="Image prompt")
# First Slide Layout
@ -417,14 +417,9 @@ presentation_layout = PresentationLayoutModel(
# print(json.dumps(FirstSlideModel.model_json_schema()))
# slide_schema = FirstSlideModel.model_json_schema()
# schema_processor = SchemaProcessor()
# print(
# json.dumps(
# schema_processor.remove_image_url_fields(FirstSlideModel.model_json_schema())
# )
# )
slide_schema = FirstSlideModel.model_json_schema()
slide_schema = remove_fields_from_schema(slide_schema, ["image_url__"])
print(slide_schema)
# print(PresentationOutlineModel.model_json_schema())

View file

@ -0,0 +1,14 @@
from typing import List
from pydantic import BaseModel
class DictGuide(BaseModel):
key: str
class ListGuide(BaseModel):
index: int
class JsonPathGuide(BaseModel):
guides: List[DictGuide | ListGuide]

View file

@ -1,13 +1,14 @@
from datetime import datetime
from typing import Optional
from sqlalchemy import JSON, Column
from sqlmodel import SQLModel, Field
from utils.randomizers import get_random_uuid
class ImageAsset(SQLModel, table=True):
id: str = Field(default=get_random_uuid, primary_key=True)
prompt: Optional[str] = Field(default=None)
path: str
id: str = Field(default_factory=get_random_uuid, primary_key=True)
created_at: datetime = Field(default=datetime.now())
path: str
extras: Optional[dict] = Field(sa_column=Column(JSON), default=None)

View file

@ -1,8 +1,6 @@
from services.schema_processor import SchemaProcessor
from services.temp_file_service import TempFileService
from services.database import sql_engine
SCHEMA_PROCESSOR = SchemaProcessor()
TEMP_FILE_SERVICE = TempFileService()
SQL_ENGINE = sql_engine

View file

@ -5,6 +5,7 @@ import aiohttp
from google import genai
from google.genai.types import GenerateContentConfig
from models.image_prompt import ImagePrompt
from models.sql.image_asset import ImageAsset
from utils.download_helpers import download_file
from utils.get_env import get_pexels_api_key_env
from utils.llm_provider import (
@ -18,7 +19,6 @@ class ImageGenerationService:
def __init__(self, output_directory: str):
self.output_directory = output_directory
os.makedirs(output_directory, exist_ok=True)
self.use_pexels = False
if get_pexels_api_key_env():
@ -35,7 +35,7 @@ class ImageGenerationService:
return self.generate_image_openai
return None
async def generate_image(self, prompt: ImagePrompt) -> str:
async def generate_image(self, prompt: ImagePrompt) -> str | ImageAsset:
if not self.image_gen_func:
print("No image generation function found. Using placeholder image.")
return "/static/images/placeholder.jpg"
@ -45,8 +45,17 @@ class ImageGenerationService:
try:
image_path = await self.image_gen_func(image_prompt, self.output_directory)
if image_path and os.path.exists(image_path):
return image_path
if image_path:
if image_path.startswith("http"):
return image_path
elif os.path.exists(image_path):
return ImageAsset(
path=image_path,
extras={
"prompt": prompt.prompt,
"theme_prompt": prompt.theme_prompt,
},
)
raise Exception(f"Image not found at {image_path}")
except Exception as e:
@ -84,7 +93,7 @@ class ImageGenerationService:
return image_path
async def get_image_from_pexels(self, prompt: str, output_directory: str) -> str:
async def get_image_from_pexels(self, prompt: str) -> str:
async with aiohttp.ClientSession() as session:
response = await session.get(
f"https://api.pexels.com/v1/search?query={prompt}&per_page=1",
@ -92,4 +101,4 @@ class ImageGenerationService:
)
data = await response.json()
image_url = data["photos"][0]["src"]["large"]
return await download_file(image_url, output_directory)
return image_url

View file

@ -1,105 +0,0 @@
from __future__ import annotations
from copy import deepcopy
from typing import List, Optional
class SchemaProcessor:
def resolve_refs(self, schema, defs):
if isinstance(schema, dict):
if "$ref" in schema:
ref_path = schema["$ref"]
if ref_path.startswith("#/$defs/"):
def_key = ref_path.replace("#/$defs/", "")
return self.resolve_refs(defs[def_key], defs)
else:
raise ValueError(f"Unsupported $ref path: {ref_path}")
else:
return {k: self.resolve_refs(v, defs) for k, v in schema.items()}
elif isinstance(schema, list):
return [self.resolve_refs(item, defs) for item in schema]
else:
return schema
def flatten_schema(self, schema):
schema = deepcopy(schema)
defs = schema.pop("$defs", {})
return self.resolve_refs(schema, defs)
def find_dict_with_key(
self, data: dict, target_key: str, current_path: Optional[List[str]] = None
) -> List[List[str]]:
if current_path is None:
current_path = []
paths = []
if target_key in data:
paths.append(current_path.copy())
for key, value in data.items():
if isinstance(value, dict):
new_path = current_path + [key]
paths.extend(self.find_dict_with_key(value, target_key, new_path))
elif isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, dict):
new_path = current_path + [key, str(i)]
paths.extend(
self.find_dict_with_key(item, target_key, new_path)
)
return paths
def get_dict_at_path(self, data: dict, path: List[str]) -> dict:
current = data
for part in path:
if part.isdigit():
current = current[int(part)]
else:
current = current[part]
return current
def set_dict_at_path(self, data: dict, path: List[str], value) -> None:
if not path:
raise ValueError("Path cannot be empty")
current = data
# Navigate to the parent of the target location
for part in path[:-1]:
if part.isdigit():
index = int(part)
if index >= len(current):
# Extend list if needed
current.extend([{}] * (index - len(current) + 1))
current = current[index]
else:
if part not in current:
current[part] = {}
current = current[part]
# Set the value at the final path component
final_part = path[-1]
if final_part.isdigit():
index = int(final_part)
if index >= len(current):
# Extend list if needed
current.extend([None] * (index - len(current) + 1))
current[index] = value
else:
current[final_part] = value
def remove_image_url_fields(self, data: dict) -> dict:
copied_data = data.copy()
image_type_paths = self.find_dict_with_key(copied_data, "__image_type__")
for path in image_type_paths:
dict_at_path = self.get_dict_at_path(copied_data, path)
if "properties" in dict_at_path:
del dict_at_path["properties"]["url"]
dict_at_parent_path = self.get_dict_at_path(copied_data, path[:-1])
if "required" in dict_at_parent_path:
dict_at_parent_path["required"].remove("url")
return copied_data

View file

@ -67,4 +67,4 @@ def test_gemini_schema_support():
response_schema=TwoColumnSlideModel.model_json_schema(),
),
)
print(response.parsed)
print(response.text)

View file

@ -0,0 +1,8 @@
import os
from utils.get_env import get_app_data_directory_env
def get_images_directory():
images_directory = os.path.join(get_app_data_directory_env(), "images")
os.makedirs(images_directory, exist_ok=True)
return images_directory

View file

@ -0,0 +1,48 @@
from typing import List
from models.json_path_guide import JsonPathGuide, DictGuide, ListGuide
def get_dict_paths_with_key(data: dict, key: str) -> List[JsonPathGuide]:
result = []
def _find_paths(obj, current_path: List[DictGuide | ListGuide]):
if isinstance(obj, dict):
if key in obj:
result.append(JsonPathGuide(guides=current_path.copy()))
for k, v in obj.items():
new_path = current_path + [DictGuide(key=k)]
_find_paths(v, new_path)
elif isinstance(obj, list):
for i, item in enumerate(obj):
new_path = current_path + [ListGuide(index=i)]
_find_paths(item, new_path)
_find_paths(data, [])
return result
def get_dict_at_path(data: dict, path: JsonPathGuide) -> dict:
current = data
for guide in path.guides:
if isinstance(guide, DictGuide):
current = current[guide.key]
elif isinstance(guide, ListGuide):
current = current[guide.index]
return current
def set_dict_at_path(data: dict, path: JsonPathGuide, value: dict):
current = data
for guide in path.guides[:-1]:
if isinstance(guide, DictGuide):
current = current[guide.key]
elif isinstance(guide, ListGuide):
current = current[guide.index]
if path.guides:
final_guide = path.guides[-1]
if isinstance(final_guide, DictGuide):
current[final_guide.key] = value
elif isinstance(final_guide, ListGuide):
current[final_guide.index] = value

View file

@ -3,14 +3,13 @@ import json
from google.genai.types import GenerateContentConfig
from models.presentation_layout import SlideLayoutModel
from models.presentation_outline_model import SlideOutlineModel
from services import SCHEMA_PROCESSOR
from services.schema_processor import SchemaProcessor
from utils.llm_provider import (
get_google_llm_client,
get_llm_client,
get_small_model,
is_google_selected,
)
from utils.schema_utils import remove_fields_from_schema
system_prompt = """
Generate structured slide based on provided title and outline, follow mentioned steps and notes and provide structured output.
@ -66,7 +65,9 @@ async def get_slide_content_from_type_and_outline(
"type": "json_schema",
"json_schema": {
"name": "SlideContent",
"schema": slide_layout.json_schema,
"schema": remove_fields_from_schema(
slide_layout.json_schema, ["__image_url__", "__icon_url__"]
),
},
},
)

View file

@ -1,33 +1,60 @@
import os
import asyncio
from typing import List, Tuple
from models.presentation_layout import SlideLayoutModel
from models.sql.asset import ImageAsset
from models.image_prompt import ImagePrompt
from models.sql.image_asset import ImageAsset
from models.sql.slide import SlideModel
from services import SCHEMA_PROCESSOR
from services.icon_finder_service import IconFinderService
from services.image_generation_service import ImageGenerationService
from utils.get_env import get_app_data_directory_env
from utils.asset_directory_utils import get_images_directory
from utils.dict_utils import get_dict_at_path, get_dict_paths_with_key, set_dict_at_path
async def process_slide_and_fetch_assets(
slide: SlideModel, layout: SlideLayoutModel
) -> SlideModel:
image_directory = os.path.join(get_app_data_directory_env(), "images")
slide: SlideModel,
) -> List[ImageAsset]:
image_directory = get_images_directory()
image_generation_service = ImageGenerationService(image_directory)
icon_finder_service = IconFinderService()
image_type_paths = SCHEMA_PROCESSOR.find_dict_with_key(
slide.content, "__image_type__"
)
for path in image_type_paths:
image_dict = SCHEMA_PROCESSOR.get_dict_at_path(slide.content, path)
image_prompt = image_dict["prompt"]
if image_dict["__image_type__"] == "image":
image_path = await image_generation_service.generate_image(image_prompt)
image_dict["url"] = image_path
else:
icon_path = await icon_finder_service.search_icons(image_prompt)
image_dict["url"] = icon_path[0]
async_tasks = []
SCHEMA_PROCESSOR.set_dict_at_path(slide.content, path, image_dict)
image_paths = get_dict_paths_with_key(slide.content, "__image_prompt__")
icon_paths = get_dict_paths_with_key(slide.content, "__icon_query__")
for image_path in image_paths:
image_prompt_parent = get_dict_at_path(slide.content, image_path)
async_tasks.append(
image_generation_service.generate_image(
ImagePrompt(
prompt=image_prompt_parent["__image_prompt__"],
)
)
)
for icon_path in icon_paths:
icon_query_parent = get_dict_at_path(slide.content, icon_path)
async_tasks.append(
icon_finder_service.search_icons(icon_query_parent["__icon_query__"])
)
results = await asyncio.gather(*async_tasks)
results.reverse()
return_assets = []
for image_path in image_paths:
image_dict = get_dict_at_path(slide.content, image_path)
result = results.pop()
if isinstance(result, ImageAsset):
return_assets.append(result)
image_dict["__image_url__"] = result.path
else:
image_dict["__image_url__"] = result
set_dict_at_path(slide.content, image_path, image_dict)
for icon_path in icon_paths:
icon_dict = get_dict_at_path(slide.content, icon_path)
icon_dict["__icon_url__"] = results.pop()[0]
set_dict_at_path(slide.content, icon_path, icon_dict)
return return_assets

View file

@ -0,0 +1,50 @@
from copy import deepcopy
from typing import List
from utils.dict_utils import get_dict_paths_with_key, get_dict_at_path, set_dict_at_path
def resolve_refs(schema, defs):
if isinstance(schema, dict):
if "$ref" in schema:
ref_path = schema["$ref"]
if ref_path.startswith("#/$defs/"):
def_key = ref_path.replace("#/$defs/", "")
return resolve_refs(defs[def_key], defs)
else:
raise ValueError(f"Unsupported $ref path: {ref_path}")
else:
return {k: resolve_refs(v, defs) for k, v in schema.items()}
elif isinstance(schema, list):
return [resolve_refs(item, defs) for item in schema]
else:
return schema
def flatten_schema(schema):
schema = deepcopy(schema)
defs = schema.pop("$defs", {})
return resolve_refs(schema, defs)
def remove_fields_from_schema(schema: dict, fields_to_remove: List[str]):
schema = deepcopy(schema)
properties_paths = get_dict_paths_with_key(schema, "properties")
for path in properties_paths:
parent_obj = get_dict_at_path(schema, path)
if "properties" in parent_obj and isinstance(parent_obj["properties"], dict):
for field in fields_to_remove:
if field in parent_obj["properties"]:
del parent_obj["properties"][field]
required_paths = get_dict_paths_with_key(schema, "required")
for path in required_paths:
parent_obj = get_dict_at_path(schema, path)
if "required" in parent_obj and isinstance(parent_obj["required"], list):
parent_obj["required"] = [
field
for field in parent_obj["required"]
if field not in fields_to_remove
]
return schema

View file

@ -1,48 +0,0 @@
import fs from 'fs';
import path from 'path';
import { NextRequest, NextResponse } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: { filepath: string[] } },
) {
const BASE_DIR = "/app";
const filepath = params.filepath.join("/");
if (!params.filepath) {
return new NextResponse('No file specified', { status: 400 });
}
const filePath = path.join(BASE_DIR, filepath);
if (!fs.existsSync(filePath)) {
return new NextResponse('File not found', { status: 404 });
}
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
return new NextResponse('Access to directories is forbidden', { status: 403 });
}
const fileStream = fs.createReadStream(filePath);
const headers = new Headers();
headers.set('Content-Disposition', `inline; filename="${path.basename(filePath)}"`);
headers.set('Content-Type', getMimeType(filePath));
return new NextResponse(fileStream as any, { headers });
}
function getMimeType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
switch (ext) {
case '.txt': return 'text/plain';
case '.json': return 'application/json';
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.png': return 'image/png';
case '.pdf': return 'application/pdf';
default: return 'application/octet-stream';
}
}

View file

@ -46,7 +46,7 @@ const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data: s
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}

View file

@ -15,8 +15,8 @@ const cardSlideSchema = z.object({
}),
cards: z.array(z.object({
icon: IconSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Default card icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Default card icon'
}).meta({
description: "Icon for the card",
}),
@ -29,24 +29,24 @@ const cardSlideSchema = z.object({
})).min(2).max(6).default([
{
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Lightning fast icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Lightning fast icon'
},
title: 'Lightning Fast',
description: 'Optimized performance for quick results and seamless user experience'
},
{
icon: {
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
prompt: 'Secure and safe icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
__icon_query__: 'Secure and safe icon'
},
title: 'Secure & Safe',
description: 'Enterprise-grade security with advanced encryption and protection'
},
{
icon: {
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
prompt: 'Precise targeting icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
__icon_query__: 'Precise targeting icon'
},
title: 'Precise Targeting',
description: 'Advanced analytics to reach your exact audience with precision'
@ -73,7 +73,7 @@ const CardSlideLayout: React.FC<CardSlideLayoutProps> = ({ data: slideData }) =>
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}
@ -108,8 +108,8 @@ const CardSlideLayout: React.FC<CardSlideLayoutProps> = ({ data: slideData }) =>
<div className="mb-4 group-hover:scale-110 transition-transform duration-300">
<div className="w-16 h-16 mx-auto bg-blue-100 rounded-xl flex items-center justify-center overflow-hidden print:w-12 print:h-12">
<img
src={card.icon?.url || ''}
alt={card.icon?.prompt || card.title}
src={card.icon?.__icon_url__ || ''}
alt={card.icon?.__icon_query__ || card.title}
className="w-full h-full object-cover"
/>
</div>

View file

@ -76,7 +76,7 @@ const ComparisonSlideLayout: React.FC<ComparisonSlideLayoutProps> = ({ data: sli
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}

View file

@ -36,7 +36,7 @@ const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}

View file

@ -43,7 +43,7 @@ const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data: slideData })
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}

View file

@ -16,8 +16,8 @@ const iconSlideSchema = z.object({
description: "Optional subtitle or description",
}),
icon: IconSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'A beautiful road in the mountains'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'A beautiful road in the mountains'
}).meta({
description: "Main slide icon",
}),
@ -55,8 +55,8 @@ const IconSlideLayout: React.FC<IconSlideLayoutProps> = ({ data: slideData }) =>
<div className="relative mb-8 p-8 rounded-3xl bg-white border-2 shadow-xl print:shadow-md">
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-blue-800 opacity-5 rounded-3xl"></div>
<img
src={slideData?.icon?.url || ''}
alt={slideData?.icon?.prompt || ''}
src={slideData?.icon?.__icon_url__ || ''}
alt={slideData?.icon?.__icon_query__ || ''}
className="w-24 h-24 object-contain relative z-10 print:w-20 print:h-20"
/>
<div className="absolute -bottom-2 -right-2 w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full opacity-80"></div>

View file

@ -19,8 +19,8 @@ const imageSlideSchema = z.object({
description: "Main description text",
}),
image: ImageSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'A beautiful road in the mountains'
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__image_prompt__: 'A beautiful road in the mountains'
}).meta({
description: "Main slide image",
}),
@ -47,8 +47,8 @@ const ImageSlideLayout: React.FC<ImageSlideLayoutProps> = ({ data: slideData })
{/* Left panel - Image */}
<div className="flex-1 relative">
<img
src={slideData?.image?.url || ''}
alt={slideData?.image?.prompt || ''}
src={slideData?.image?.__image_url__ || ''}
alt={slideData?.image?.__image_prompt__ || ''}
className="w-full h-full object-cover"
/>
{/* Overlay gradient */}

View file

@ -15,8 +15,8 @@ const processSlideSchema = z.object({
}),
steps: z.array(z.object({
icon: IconSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Default step icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Default step icon'
}).meta({
description: "Icon for the step",
}),
@ -29,24 +29,24 @@ const processSlideSchema = z.object({
})).min(2).max(6).default([
{
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Plan and strategy icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Plan and strategy icon'
},
title: 'Plan & Strategy',
description: 'Define objectives, analyze requirements, and create a comprehensive roadmap'
},
{
icon: {
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
prompt: 'Execute and build icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
__icon_query__: 'Execute and build icon'
},
title: 'Execute & Build',
description: 'Implement solutions with precision using cutting-edge technology and best practices'
},
{
icon: {
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
prompt: 'Launch and optimize icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
__icon_query__: 'Launch and optimize icon'
},
title: 'Launch & Optimize',
description: 'Deploy the solution and continuously improve based on performance metrics'
@ -73,7 +73,7 @@ const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data: slideData
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}
@ -110,8 +110,8 @@ const ProcessSlideLayout: React.FC<ProcessSlideLayoutProps> = ({ data: slideData
</div>
<div className="absolute -bottom-2 -right-2 w-8 h-8 bg-white rounded-full flex items-center justify-center shadow-lg print:w-6 print:h-6">
<img
src={step.icon?.url || ''}
alt={step.icon?.prompt || step.title}
src={step.icon?.__icon_url__ || ''}
alt={step.icon?.__icon_query__ || step.title}
className="w-6 h-6 object-cover rounded-full print:w-4 print:h-4"
/>
</div>

View file

@ -48,7 +48,7 @@ const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData })
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}

View file

@ -94,7 +94,7 @@ const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: sli
<div
className="relative w-full aspect-[16/9] bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
style={slideData?.backgroundImage ? {
backgroundImage: `url("${slideData.backgroundImage.url}")`,
backgroundImage: `url("${slideData.backgroundImage.__image_url__}")`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'

View file

@ -77,7 +77,7 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
<div
className="relative w-full aspect-[16/9] bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
style={slideData?.backgroundImage ? {
backgroundImage: `url("${slideData.backgroundImage.url}")`,
backgroundImage: `url("${slideData.backgroundImage.__image_url__}")`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
@ -99,7 +99,7 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
<div className="relative z-10 flex flex-col h-full px-8 py-8">
{/* Professional Header */}
<header className="mb-6">
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage?.__image_prompt__
? 'text-white drop-shadow-lg'
: 'text-slate-900'
}`}>
@ -109,7 +109,7 @@ const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) =>
</h1>
{slideData?.subtitle && (
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage?.__image_prompt__
? 'text-slate-200 drop-shadow-md'
: 'text-slate-600'
}`}>

View file

@ -24,8 +24,8 @@ const timelineSlideSchema = z.object({
description: "Event description",
}),
icon: IconSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Default event icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Default event icon'
}).meta({
description: "Icon for the event",
})
@ -35,8 +35,8 @@ const timelineSlideSchema = z.object({
title: 'Foundation',
description: 'Company founded with a vision to transform digital experiences',
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Foundation icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Foundation icon'
}
},
{
@ -44,8 +44,8 @@ const timelineSlideSchema = z.object({
title: 'First Success',
description: 'Launched first product and gained initial market traction',
icon: {
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
prompt: 'First success icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
__icon_query__: 'First success icon'
}
},
{
@ -53,8 +53,8 @@ const timelineSlideSchema = z.object({
title: 'Expansion',
description: 'Expanded team and entered new markets with innovative solutions',
icon: {
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
prompt: 'Expansion icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
__icon_query__: 'Expansion icon'
}
},
{
@ -62,8 +62,8 @@ const timelineSlideSchema = z.object({
title: 'Innovation',
description: 'Introduced breakthrough technology and achieved industry recognition',
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Innovation icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Innovation icon'
}
}
]).meta({
@ -88,7 +88,7 @@ const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data: slideDa
<div
className="relative w-full aspect-[16/9] flex flex-col bg-gradient-to-br from-slate-50 via-white to-slate-100 overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
style={slideData?.backgroundImage ? {
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.url})`,
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
backgroundSize: 'cover',
backgroundPosition: 'center'
} : {}}
@ -137,8 +137,8 @@ const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data: slideDa
<h3 className="text-xl font-bold text-gray-800 mb-3 leading-tight flex items-center gap-2 print:text-lg">
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center overflow-hidden flex-shrink-0 print:w-6 print:h-6">
<img
src={event.icon?.url || ''}
alt={event.icon?.prompt || event.title}
src={event.icon?.__icon_url__ || ''}
alt={event.icon?.__icon_query__ || event.title}
className="w-full h-full object-cover"
/>
</div>

View file

@ -14,8 +14,8 @@ const type1SlideSchema = z.object({
description: "Main description text",
}),
image: ImageSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'A beautiful road in the mountains'
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__image_prompt__: 'A beautiful road in the mountains'
}).meta({
description: "Main slide image",
})
@ -52,8 +52,8 @@ const Type1SlideLayout: React.FC<Type1SlideLayoutProps> = ({ data: slideData })
{/* Image */}
<div className="w-full h-full min-h-[200px] lg:min-h-[300px]">
<img
src={slideData?.image?.url || ''}
alt={slideData?.image?.prompt || slideData?.title || ''}
src={slideData?.image?.__image_url__ || ''}
alt={slideData?.image?.__image_prompt__ || slideData?.title || ''}
className="w-full h-full object-cover rounded-lg shadow-md"
/>
</div>

View file

@ -25,24 +25,24 @@ const type3SlideSchema = z.object({
heading: 'First Feature',
description: 'Description for the first featured item with detailed information',
image: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'A beautiful road in the mountains'
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__image_prompt__: 'A beautiful road in the mountains'
}
},
{
heading: 'Second Feature',
description: 'Description for the second featured item with relevant details',
image: {
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
prompt: 'Modern office workspace'
__image_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
__image_prompt__: 'Modern office workspace'
}
},
{
heading: 'Third Feature',
description: 'Description for the third featured item with important points',
image: {
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
prompt: 'Laptop with code on screen'
__image_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
__image_prompt__: 'Laptop with code on screen'
}
}
]).meta({
@ -94,8 +94,8 @@ const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData })
{/* Image */}
<div className="max-md:h-[140px] max-lg:h-[180px] h-48 w-full">
<img
src={item.image?.url || ''}
alt={item.image?.prompt || item.heading}
src={item.image?.__image_url__ || ''}
alt={item.image?.__image_prompt__ || item.heading}
className="w-full h-full object-cover"
/>
</div>

View file

@ -18,8 +18,8 @@ const type7SlideSchema = z.object({
description: "Item description",
}),
icon: IconSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Default icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Default icon'
}).meta({
description: "Icon for the item",
})
@ -28,32 +28,32 @@ const type7SlideSchema = z.object({
heading: 'Professional Service',
description: 'High-quality professional services tailored to your specific needs and requirements',
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Professional service icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Professional service icon'
}
},
{
heading: 'Expert Consultation',
description: 'Expert advice and consultation from experienced professionals in the field',
icon: {
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
prompt: 'Expert consultation icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
__icon_query__: 'Expert consultation icon'
}
},
{
heading: 'Quality Assurance',
description: 'Comprehensive quality assurance processes to ensure excellent results',
icon: {
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
prompt: 'Quality assurance icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
__icon_query__: 'Quality assurance icon'
}
},
{
heading: 'Customer Support',
description: 'Dedicated customer support available to assist you throughout the process',
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Customer support icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Customer support icon'
}
}
]).meta({
@ -100,8 +100,8 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
<div className="flex-shrink-0 lg:w-16">
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
<img
src={item.icon?.url || ''}
alt={item.icon?.prompt || item.heading}
src={item.icon?.__icon_url__ || ''}
alt={item.icon?.__icon_query__ || item.heading}
className="w-full h-full object-cover"
/>
</div>
@ -135,8 +135,8 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
<div className="text-center mb-4">
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
<img
src={item.icon?.url || ''}
alt={item.icon?.prompt || item.heading}
src={item.icon?.__icon_url__ || ''}
alt={item.icon?.__icon_query__ || item.heading}
className="w-full h-full object-cover"
/>
</div>

View file

@ -21,8 +21,8 @@ const type8SlideSchema = z.object({
description: "Item description",
}),
icon: IconSchema.default({
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Default icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Default icon'
}).meta({
description: "Icon for the item",
})
@ -31,24 +31,24 @@ const type8SlideSchema = z.object({
heading: 'Advanced Features',
description: 'Cutting-edge functionality designed to enhance productivity and user experience',
icon: {
url: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
prompt: 'Advanced features icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
__icon_query__: 'Advanced features icon'
}
},
{
heading: 'Reliable Performance',
description: 'Consistent and dependable performance across all platforms and devices',
icon: {
url: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
prompt: 'Reliable performance icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
__icon_query__: 'Reliable performance icon'
}
},
{
heading: 'Secure Environment',
description: 'Enterprise-grade security measures to protect your data and privacy',
icon: {
url: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
prompt: 'Secure environment icon'
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
__icon_query__: 'Secure environment icon'
}
}
]).meta({
@ -83,8 +83,8 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
<div className="text-center mb-4">
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
<img
src={item.icon?.url || ''}
alt={item.icon?.prompt || item.heading}
src={item.icon?.__icon_url__ || ''}
alt={item.icon?.__icon_query__ || item.heading}
className="w-full h-full object-cover"
/>
</div>
@ -117,8 +117,8 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
<div className="w-[32px] md:w-[64px] h-[32px] md:h-[64px]">
<div className="w-full h-full bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
<img
src={item.icon?.url || ''}
alt={item.icon?.prompt || item.heading}
src={item.icon?.__icon_url__ || ''}
alt={item.icon?.__icon_query__ || item.heading}
className="w-full h-full object-cover"
/>
</div>

View file

@ -1,21 +1,19 @@
import * as z from "zod";
export const ImageSchema = z.object({
url: z.url().meta({
__image_url__: z.url().meta({
description: "URL to image",
}),
prompt: z.string().meta({
__image_prompt__: z.string().meta({
description: "Prompt used to generate the image",
}),
__image_type__:z.literal('image')
})
export const IconSchema = z.object({
url: z.string().meta({
__icon_url__: z.string().meta({
description: "URL to icon",
}),
prompt: z.string().meta({
description: "Prompt used to generate the icon",
__icon_query__: z.string().meta({
description: "Query used to search the icon",
}),
__image_type__:z.literal('icon')
})

View file

@ -42,12 +42,6 @@ const nextConfig = {
},
],
},
rewrites: async () => [
{
source: "/static/:path*",
destination: "/api/static/:path*",
},
],
};
export default nextConfig;