Merge branch 'feat/custom_schema_and_layout' into feat/hot_reload_layout_preview
This commit is contained in:
commit
ce4e665e5c
19 changed files with 278 additions and 1000 deletions
|
|
@ -42,85 +42,15 @@ You are an expert presentation creator. Generate structured presentations based
|
|||
- Ensure titles create a **logical flow** through the presentation
|
||||
- Keep titles **concise but meaningful**
|
||||
|
||||
### Slide Body Content
|
||||
- Use **full markdown formatting** for rich content structure
|
||||
- Apply consistent formatting:
|
||||
- `**bold**` for key concepts and emphasis
|
||||
- `*italic*` for secondary emphasis or definitions
|
||||
- `- or *` for bullet points and lists
|
||||
- `> ` for quotes or callouts
|
||||
- `### ` for subsections within slides
|
||||
- ``` for code blocks (when applicable)
|
||||
- `inline code` for technical terms or specific terminology
|
||||
|
||||
### Content Structure Per Slide
|
||||
- **Opening/Hook**: Start with engaging content
|
||||
- **Main Points**: 3-5 key points maximum per slide
|
||||
- **Supporting Details**: Brief explanations or examples
|
||||
- **Visual Cues**: Suggest where charts, images, or diagrams would be beneficial
|
||||
- **Transitions**: Natural flow to next slide topic
|
||||
|
||||
### Speaker Notes
|
||||
- Include **comprehensive speaker notes** for each slide
|
||||
- Provide **additional context** not covered in slide content
|
||||
- Add **timing suggestions** and **delivery tips**
|
||||
- Include **visual element descriptions** (charts, images, icons)
|
||||
- Specify if notes apply to **specific slides** or **entire presentation**
|
||||
- Add **interaction opportunities** (questions, polls, discussions)
|
||||
|
||||
## Quality Standards
|
||||
|
||||
### Content Quality
|
||||
- Ensure **factual accuracy** and **current information**
|
||||
- Maintain **consistent tone** throughout presentation
|
||||
- Create **logical progression** between slides
|
||||
- Include **actionable insights** where appropriate
|
||||
- Balance **depth and accessibility** for target audience
|
||||
|
||||
### Formatting Consistency
|
||||
- Use **uniform markdown styling** across all slides
|
||||
- Maintain **consistent bullet point structure**
|
||||
- Apply **appropriate heading levels**
|
||||
- Ensure **readable content density**
|
||||
|
||||
### Language and Tone
|
||||
- Generate content in the **specified language**
|
||||
- Adapt **tone and complexity** to target audience
|
||||
- Use **active voice** and **clear, direct language**
|
||||
- Include **engaging elements** (questions, scenarios, examples)
|
||||
|
||||
## Special Considerations
|
||||
|
||||
### Slide Count Compliance
|
||||
- Generate **exactly** the number of slides requested
|
||||
- Distribute content **evenly** across slides
|
||||
- Ensure **no slide is significantly longer** than others
|
||||
- Create **balanced information flow**
|
||||
|
||||
### Visual Integration
|
||||
- Suggest **relevant visual elements** in notes
|
||||
- Indicate **optimal placement** for charts, graphs, images
|
||||
- Recommend **slide layouts** for different content types
|
||||
- Specify **color schemes** or **design elements** when relevant
|
||||
|
||||
### Interactivity Elements
|
||||
- Include **audience engagement opportunities**
|
||||
- Suggest **discussion points** or **questions**
|
||||
- Recommend **interactive elements** (polls, breakout sessions)
|
||||
- Provide **transition phrases** between sections
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before finalizing, ensure:
|
||||
- [ ] Exact number of slides generated
|
||||
- [ ] All titles are plain text (no markdown)
|
||||
- [ ] All slide bodies use proper markdown formatting
|
||||
- [ ] Comprehensive notes provided for each slide
|
||||
- [ ] Logical flow between slides
|
||||
- [ ] Consistent formatting throughout
|
||||
- [ ] Content appropriate for specified language
|
||||
- [ ] No slide title appears in slide body
|
||||
- [ ] Speaker notes clearly indicate scope (slide-specific or presentation-wide)
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from utils.llm_provider import (
|
|||
get_small_model,
|
||||
is_google_selected,
|
||||
)
|
||||
from utils.schema_utils import remove_fields_from_schema
|
||||
from utils.schema_utils import remove_fields_from_schema, generate_constraint_sentences
|
||||
|
||||
system_prompt = """
|
||||
Generate structured slide based on provided title and outline, follow mentioned steps and notes and provide structured output.
|
||||
|
|
@ -19,6 +19,7 @@ system_prompt = """
|
|||
2. Generate structured slide based on the outline and title.
|
||||
|
||||
# Notes
|
||||
- **Strictly follow the max and min character limit for each property in the slide.**
|
||||
- Slide body should not use words like "This slide", "This presentation".
|
||||
- Rephrase the slide body to make it flow naturally.
|
||||
- Do not use markdown formatting in slide body.
|
||||
|
|
@ -35,11 +36,12 @@ def get_user_prompt(title: str, outline: str):
|
|||
"""
|
||||
|
||||
|
||||
def get_prompt_to_generate_slide_content(title: str, outline: str):
|
||||
def get_prompt_to_generate_slide_content(title: str, outline: str, schema_constraints: str = ""):
|
||||
|
||||
return [
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_prompt,
|
||||
"content": system_prompt + f"\n{schema_constraints}",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
|
|
@ -52,6 +54,8 @@ async def get_slide_content_from_type_and_outline(
|
|||
slide_layout: SlideLayoutModel, outline: SlideOutlineModel
|
||||
):
|
||||
model = get_small_model()
|
||||
|
||||
schema_constraints = generate_constraint_sentences(slide_layout.json_schema)
|
||||
|
||||
if not is_google_selected():
|
||||
client = get_llm_client()
|
||||
|
|
@ -60,6 +64,7 @@ async def get_slide_content_from_type_and_outline(
|
|||
messages=get_prompt_to_generate_slide_content(
|
||||
outline.title,
|
||||
outline.body,
|
||||
schema_constraints,
|
||||
),
|
||||
response_format={
|
||||
"type": "json_schema",
|
||||
|
|
@ -79,7 +84,7 @@ async def get_slide_content_from_type_and_outline(
|
|||
model=model,
|
||||
contents=[get_user_prompt(outline.title, outline.body)],
|
||||
config=GenerateContentConfig(
|
||||
system_instruction=system_prompt,
|
||||
system_instruction=system_prompt + f"\n{schema_constraints}",
|
||||
response_mime_type="application/json",
|
||||
response_json_schema=slide_layout.json_schema,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from utils.get_env import (
|
|||
)
|
||||
|
||||
|
||||
|
||||
def get_llm_provider():
|
||||
try:
|
||||
return LLMProvider(get_llm_provider_env())
|
||||
|
|
@ -77,9 +78,9 @@ def get_llm_api_key():
|
|||
|
||||
def get_llm_client():
|
||||
client = AsyncOpenAI(
|
||||
base_url=get_model_base_url(),
|
||||
api_key=get_llm_api_key(),
|
||||
)
|
||||
base_url=get_model_base_url(),
|
||||
api_key=get_llm_api_key(),
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,3 +48,72 @@ def remove_fields_from_schema(schema: dict, fields_to_remove: List[str]):
|
|||
]
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def generate_constraint_sentences(schema: dict) -> str:
|
||||
"""
|
||||
Generate human-readable constraint sentences from a JSON schema.
|
||||
|
||||
Args:
|
||||
schema: JSON schema dictionary
|
||||
|
||||
Returns:
|
||||
String containing constraint sentences separated by newlines
|
||||
"""
|
||||
constraints = []
|
||||
|
||||
def extract_constraints_recursive(obj, prefix=""):
|
||||
if isinstance(obj, dict):
|
||||
if "properties" in obj:
|
||||
properties = obj["properties"]
|
||||
for prop_name, prop_def in properties.items():
|
||||
current_path = f"{prefix}.{prop_name}" if prefix else prop_name
|
||||
|
||||
if isinstance(prop_def, dict):
|
||||
prop_type = prop_def.get("type")
|
||||
|
||||
# Handle string constraints
|
||||
if prop_type == "string":
|
||||
min_length = prop_def.get("minLength")
|
||||
max_length = prop_def.get("maxLength")
|
||||
|
||||
if min_length is not None and max_length is not None:
|
||||
constraints.append(f" - {current_path} should be less than {max_length} characters and greater than {min_length} characters")
|
||||
elif max_length is not None:
|
||||
constraints.append(f" - {current_path} should be less than {max_length} characters")
|
||||
elif min_length is not None:
|
||||
constraints.append(f" - {current_path} should be greater than {min_length} characters")
|
||||
|
||||
# Handle array constraints
|
||||
elif prop_type == "array":
|
||||
min_items = prop_def.get("minItems")
|
||||
max_items = prop_def.get("maxItems")
|
||||
|
||||
if min_items is not None and max_items is not None:
|
||||
constraints.append(f" - {current_path} should have more than {min_items} items and less than {max_items} items")
|
||||
elif max_items is not None:
|
||||
constraints.append(f" - {current_path} should have less than {max_items} items")
|
||||
elif min_items is not None:
|
||||
constraints.append(f" - {current_path} should have more than {min_items} items")
|
||||
|
||||
# Recurse into nested objects
|
||||
if prop_type == "object" or "properties" in prop_def:
|
||||
extract_constraints_recursive(prop_def, current_path)
|
||||
|
||||
# Handle array items if they have properties
|
||||
if prop_type == "array" and "items" in prop_def:
|
||||
items_def = prop_def["items"]
|
||||
if isinstance(items_def, dict) and ("properties" in items_def or items_def.get("type") == "object"):
|
||||
extract_constraints_recursive(items_def, f"{current_path}[*]")
|
||||
|
||||
# Also recurse into other nested structures
|
||||
for key, value in obj.items():
|
||||
if key not in ["properties", "type", "minLength", "maxLength", "minItems", "maxItems"] and isinstance(value, dict):
|
||||
extract_constraints_recursive(value, prefix)
|
||||
|
||||
# Start extraction from the root schema
|
||||
extract_constraints_recursive(schema)
|
||||
|
||||
return "\n".join(constraints)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
|
||||
<div className="">
|
||||
<div
|
||||
id="presentation-slides-wrapper"
|
||||
className="mx-auto flex flex-col items-center overflow-hidden justify-center "
|
||||
|
||||
>
|
||||
|
|
@ -102,7 +103,6 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
presentationData.slides.length > 0 &&
|
||||
presentationData.slides.map((slide: any, index: number) => (
|
||||
<div key={index} className="w-full">
|
||||
|
||||
{renderSlideContent(slide, false)}
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import { ApiError } from "@/models/errors";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import puppeteer, { Browser, ElementHandle } from "puppeteer";
|
||||
import puppeteer, { Browser, ElementHandle, Page } from "puppeteer";
|
||||
import { ElementAttributes, SlideAttributesResult } from "@/types/element_attibutes";
|
||||
import { convertElementAttributesToPptxSlides } from "@/utils/pptx_models_utils";
|
||||
import { PptxPresentationModel } from "@/types/pptx_models";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import crypto from "crypto";
|
||||
import sharp from "sharp";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// Interface for getAllChildElementsAttributes function arguments
|
||||
interface GetAllChildElementsAttributesArgs {
|
||||
element: ElementHandle<Element>;
|
||||
rootRect?: { left: number; top: number; width: number; height: number } | null;
|
||||
|
|
@ -23,41 +21,27 @@ interface GetAllChildElementsAttributesArgs {
|
|||
|
||||
export async function GET(request: NextRequest) {
|
||||
let browser: Browser | null = null;
|
||||
let page: Page | null = null;
|
||||
|
||||
try {
|
||||
const id = await getPresentationId(request);
|
||||
browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
});
|
||||
[browser, page] = await getBrowserAndPage(id);
|
||||
const screenshotsDir = getScreenshotsDir();
|
||||
|
||||
// Ensure screenshots directory exists
|
||||
const tempDir = process.env.TEMP_DIRECTORY;
|
||||
if (!tempDir) {
|
||||
console.warn('TEMP_DIRECTORY environment variable not set, skipping screenshot');
|
||||
return undefined;
|
||||
}
|
||||
const screenshotsDir = path.join(tempDir, 'screenshots');
|
||||
if (!fs.existsSync(screenshotsDir)) {
|
||||
fs.mkdirSync(screenshotsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const slides = await getSlides(browser, id);
|
||||
const slides = await getSlides(page);
|
||||
const slides_attributes = await getSlidesAttributes(slides, screenshotsDir);
|
||||
const slides_pptx_models = convertElementAttributesToPptxSlides(slides_attributes.elements, slides_attributes.backgroundColors);
|
||||
await postProcessSlidesAttributes(slides_attributes, screenshotsDir);
|
||||
const slides_pptx_models = convertElementAttributesToPptxSlides(slides_attributes);
|
||||
const presentation_pptx_model: PptxPresentationModel = {
|
||||
slides: slides_pptx_models,
|
||||
};
|
||||
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
await closeBrowserAndPage(browser, page);
|
||||
|
||||
return NextResponse.json(presentation_pptx_model);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
await closeBrowserAndPage(browser, page);
|
||||
if (error instanceof ApiError) {
|
||||
return NextResponse.json(error, { status: 400 });
|
||||
}
|
||||
|
|
@ -73,42 +57,19 @@ async function getPresentationId(request: NextRequest) {
|
|||
return id;
|
||||
}
|
||||
|
||||
async function getSlidesAttributes(slides: ElementHandle<Element>[], screenshotsDir: string) {
|
||||
const slideResults: SlideAttributesResult[] = [];
|
||||
//? Can't use Promise.all because of the screenshot
|
||||
//? taking screenshot with mess up position of elements
|
||||
for (const slide of slides) {
|
||||
const result = await getAllChildElementsAttributes({ element: slide, screenshotsDir });
|
||||
slideResults.push(result);
|
||||
}
|
||||
async function getBrowserAndPage(id: string): Promise<[Browser, Page]> {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-gpu',
|
||||
'--disable-web-security',
|
||||
'--window-size=1920,1080'
|
||||
],
|
||||
});
|
||||
|
||||
const elements = slideResults.map(result => result.elements);
|
||||
const backgroundColors = slideResults.map(result => result.backgroundColor);
|
||||
|
||||
return {
|
||||
elements,
|
||||
backgroundColors
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async function getSlides(browser: Browser, id: string) {
|
||||
const slides_wrapper = await getSlidesWrapper(browser, id);
|
||||
const slides = await slides_wrapper.$$(":scope > div > div");
|
||||
return slides;
|
||||
}
|
||||
|
||||
async function getSlidesWrapper(browser: Browser, id: string): Promise<ElementHandle<Element>> {
|
||||
const page = await getPresentationPage(browser, id);
|
||||
const slides_wrapper = await page.$("#presentation-slides-wrapper");
|
||||
if (!slides_wrapper) {
|
||||
throw new ApiError("Presentation slides not found");
|
||||
}
|
||||
return slides_wrapper;
|
||||
}
|
||||
|
||||
|
||||
async function getPresentationPage(browser: Browser, id: string) {
|
||||
const page = await browser.newPage();
|
||||
|
||||
page.on('console', (msg) => {
|
||||
|
|
@ -118,16 +79,116 @@ async function getPresentationPage(browser: Browser, id: string) {
|
|||
});
|
||||
|
||||
await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 });
|
||||
await page.goto(`http://localhost/presentation?id=${id}`, {
|
||||
await page.goto(`http://localhost/pdf-maker?id=${id}`, {
|
||||
waitUntil: "networkidle0",
|
||||
timeout: 60000,
|
||||
});
|
||||
return page;
|
||||
return [browser, page];
|
||||
}
|
||||
|
||||
async function closeBrowserAndPage(browser: Browser | null, page: Page | null) {
|
||||
await page?.close();
|
||||
await browser?.close();
|
||||
}
|
||||
|
||||
function getScreenshotsDir() {
|
||||
const tempDir = process.env.TEMP_DIRECTORY;
|
||||
if (!tempDir) {
|
||||
console.warn('TEMP_DIRECTORY environment variable not set, skipping screenshot');
|
||||
throw new ApiError('TEMP_DIRECTORY environment variable not set');
|
||||
}
|
||||
const screenshotsDir = path.join(tempDir, 'screenshots');
|
||||
if (!fs.existsSync(screenshotsDir)) {
|
||||
fs.mkdirSync(screenshotsDir, { recursive: true });
|
||||
}
|
||||
return screenshotsDir;
|
||||
}
|
||||
|
||||
async function postProcessSlidesAttributes(slidesAttributes: SlideAttributesResult[], screenshotsDir: string) {
|
||||
for (const slideAttributes of slidesAttributes) {
|
||||
for (const element of slideAttributes.elements) {
|
||||
if (element.should_screenshot) {
|
||||
const screenshotPath = await screenshotElement(element, screenshotsDir);
|
||||
element.imageSrc = screenshotPath;
|
||||
element.should_screenshot = false;
|
||||
element.element = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function screenshotElement(element: ElementAttributes, screenshotsDir: string) {
|
||||
const screenshotPath = path.join(screenshotsDir, `${uuidv4()}.png`) as `${string}.png`;
|
||||
|
||||
// Hide all elements except the target element and its ancestors
|
||||
await element.element?.evaluate((el) => {
|
||||
const originalDisplays = new Map();
|
||||
|
||||
const hideAllExcept = (targetElement: Element) => {
|
||||
const allElements = document.querySelectorAll('*');
|
||||
|
||||
allElements.forEach((elem) => {
|
||||
if (targetElement === elem || targetElement.contains(elem) || elem.contains(targetElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const computedStyle = window.getComputedStyle(elem);
|
||||
originalDisplays.set(elem, computedStyle.display);
|
||||
(elem as HTMLElement).style.display = 'none';
|
||||
});
|
||||
};
|
||||
|
||||
hideAllExcept(el);
|
||||
|
||||
(el as any).__restoreDisplays = () => {
|
||||
originalDisplays.forEach((display, elem) => {
|
||||
(elem as HTMLElement).style.display = display;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
const screenshot = await element.element?.screenshot({ path: screenshotPath });
|
||||
if (!screenshot) {
|
||||
throw new ApiError("Failed to screenshot element");
|
||||
}
|
||||
|
||||
await element.element?.evaluate((el) => {
|
||||
if ((el as any).__restoreDisplays) {
|
||||
(el as any).__restoreDisplays();
|
||||
}
|
||||
});
|
||||
|
||||
return screenshotPath;
|
||||
}
|
||||
|
||||
|
||||
async function getSlidesAttributes(slides: ElementHandle<Element>[], screenshotsDir: string): Promise<SlideAttributesResult[]> {
|
||||
const slideAttributes = await Promise.all(slides.map(async (slide) => {
|
||||
return await getAllChildElementsAttributes({ element: slide, screenshotsDir });
|
||||
}));
|
||||
|
||||
return slideAttributes;
|
||||
}
|
||||
|
||||
|
||||
async function getSlides(page: Page) {
|
||||
const slides_wrapper = await getSlidesWrapper(page);
|
||||
const slides = await slides_wrapper.$$(":scope > div > div");
|
||||
return slides;
|
||||
}
|
||||
|
||||
async function getSlidesWrapper(page: Page): Promise<ElementHandle<Element>> {
|
||||
const slides_wrapper = await page.$("#presentation-slides-wrapper");
|
||||
if (!slides_wrapper) {
|
||||
throw new ApiError("Presentation slides not found");
|
||||
}
|
||||
return slides_wrapper;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async function getAllChildElementsAttributes({ element, rootRect = null, depth = 0, inheritedFont, inheritedBackground, inheritedBorderRadius, screenshotsDir }: GetAllChildElementsAttributesArgs): Promise<SlideAttributesResult> {
|
||||
// Get rootRect if not provided (first call)
|
||||
const currentRootRect = rootRect || await element.evaluate((el) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {
|
||||
|
|
@ -138,67 +199,23 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth =
|
|||
};
|
||||
});
|
||||
|
||||
// Check if this element is SVG or canvas or table
|
||||
const tagName = await element.evaluate((el) => el.tagName.toLowerCase());
|
||||
|
||||
|
||||
if (tagName === 'svg' || tagName === 'canvas' || tagName === 'table') {
|
||||
return {
|
||||
elements: [],
|
||||
backgroundColor: undefined
|
||||
};
|
||||
|
||||
// // Get basic attributes for the element
|
||||
// const attributes = await getElementAttributes(element);
|
||||
// // Take screenshot of SVG/canvas/table element with accurate colors and opacity
|
||||
// const screenshotPath = await takeElementScreenshot(element, screenshotsDir);
|
||||
|
||||
// // Update image source to point to the screenshot
|
||||
// if (screenshotPath) {
|
||||
// attributes.imageSrc = screenshotPath;
|
||||
// }
|
||||
|
||||
// // Adjust position relative to root
|
||||
// if (attributes.position && attributes.position.left !== undefined && attributes.position.top !== undefined) {
|
||||
// attributes.position = {
|
||||
// left: attributes.position.left - currentRootRect.left,
|
||||
// top: attributes.position.top - currentRootRect.top,
|
||||
// width: attributes.position.width,
|
||||
// height: attributes.position.height,
|
||||
// };
|
||||
// }
|
||||
|
||||
// // Return early without processing children for these elements
|
||||
// return {
|
||||
// elements: [attributes],
|
||||
// backgroundColor: undefined
|
||||
// };
|
||||
}
|
||||
|
||||
// Get direct children only (not all descendants)
|
||||
const directChildElementHandles = await element.$$(':scope > *');
|
||||
|
||||
const allResults: { attributes: ElementAttributes; depth: number }[] = [];
|
||||
|
||||
// Process direct children recursively
|
||||
for (const childElementHandle of directChildElementHandles) {
|
||||
// Get attributes for current child
|
||||
const attributes = await getElementAttributes(childElementHandle);
|
||||
|
||||
// Apply inherited font only on elements that have direct text
|
||||
if (inheritedFont && !attributes.font && attributes.innerText && attributes.innerText.trim().length > 0) {
|
||||
attributes.font = inheritedFont;
|
||||
}
|
||||
// Apply inherited background only on elements that have shadow
|
||||
if (inheritedBackground && !attributes.background && attributes.shadow) {
|
||||
attributes.background = inheritedBackground;
|
||||
}
|
||||
// Apply inherited border radius if element doesn't have it
|
||||
if (inheritedBorderRadius && !attributes.borderRadius) {
|
||||
attributes.borderRadius = inheritedBorderRadius;
|
||||
}
|
||||
|
||||
// Adjust position relative to root
|
||||
if (attributes.position && attributes.position.left !== undefined && attributes.position.top !== undefined) {
|
||||
attributes.position = {
|
||||
left: attributes.position.left - currentRootRect.left,
|
||||
|
|
@ -208,10 +225,18 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth =
|
|||
};
|
||||
}
|
||||
|
||||
// Add current child to results
|
||||
if (attributes.tagName === 'svg' || attributes.tagName === 'canvas' || attributes.tagName === 'table') {
|
||||
attributes.should_screenshot = true;
|
||||
attributes.element = childElementHandle;
|
||||
}
|
||||
|
||||
allResults.push({ attributes, depth });
|
||||
|
||||
// Recursively process children of this child
|
||||
//? If the element is a svg, canvas, or table, we don't need to go deeper
|
||||
if (attributes.should_screenshot) {
|
||||
break;
|
||||
}
|
||||
|
||||
const childResults = await getAllChildElementsAttributes({
|
||||
element: childElementHandle,
|
||||
rootRect: currentRootRect,
|
||||
|
|
@ -224,7 +249,6 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth =
|
|||
allResults.push(...childResults.elements.map(attr => ({ attributes: attr, depth: depth + 1 })));
|
||||
}
|
||||
|
||||
// Find background color from elements with root position (only in first call)
|
||||
let backgroundColor: string | undefined;
|
||||
if (!rootRect) {
|
||||
const elementsWithRootPosition = allResults.filter(({ attributes }) => {
|
||||
|
|
@ -243,13 +267,15 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth =
|
|||
}
|
||||
}
|
||||
|
||||
// Filter results (only in first call)
|
||||
const filteredResults = !rootRect ? allResults.filter(({ attributes }) => {
|
||||
const hasBackground = attributes.background && attributes.background.color;
|
||||
const hasBorder = attributes.border && attributes.border.color;
|
||||
const hasShadow = attributes.shadow && attributes.shadow.color;
|
||||
const hasText = attributes.innerText && attributes.innerText.trim().length > 0;
|
||||
const hasImage = attributes.imageSrc;
|
||||
const isSvg = attributes.tagName === 'svg';
|
||||
const isCanvas = attributes.tagName === 'canvas';
|
||||
const isTable = attributes.tagName === 'table';
|
||||
|
||||
const isRootPosition = attributes.position &&
|
||||
attributes.position.left === 0 &&
|
||||
|
|
@ -257,7 +283,7 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth =
|
|||
attributes.position.width === currentRootRect.width &&
|
||||
attributes.position.height === currentRootRect.height;
|
||||
|
||||
const hasOtherProperties = hasBackground || hasBorder || hasShadow || hasText || hasImage;
|
||||
const hasOtherProperties = hasBackground || hasBorder || hasShadow || hasText || hasImage || isSvg || isCanvas || isTable;
|
||||
return hasOtherProperties && !isRootPosition;
|
||||
}) : allResults;
|
||||
|
||||
|
|
@ -274,7 +300,6 @@ async function getAllChildElementsAttributes({ element, rootRect = null, depth =
|
|||
return zIndexB - zIndexA;
|
||||
})
|
||||
.map(({ attributes }) => {
|
||||
// Set background color to backgroundColor for elements that have shadow but no background color
|
||||
if (attributes.shadow && attributes.shadow.color && (!attributes.background || !attributes.background.color) && backgroundColor) {
|
||||
attributes.background = {
|
||||
color: backgroundColor,
|
||||
|
|
@ -416,8 +441,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
} = {};
|
||||
|
||||
if (boxShadow && boxShadow !== 'none') {
|
||||
// Handle multiple shadows (comma-separated) - find the first meaningful one
|
||||
// Need to split on commas but not inside function calls like rgba()
|
||||
const shadows: string[] = [];
|
||||
let currentShadow = '';
|
||||
let parenCount = 0;
|
||||
|
|
@ -429,7 +452,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
} else if (char === ')') {
|
||||
parenCount--;
|
||||
} else if (char === ',' && parenCount === 0) {
|
||||
// This comma is outside of any function call, so it separates shadows
|
||||
shadows.push(currentShadow.trim());
|
||||
currentShadow = '';
|
||||
continue;
|
||||
|
|
@ -437,7 +459,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
currentShadow += char;
|
||||
}
|
||||
|
||||
// Add the last shadow
|
||||
if (currentShadow.trim()) {
|
||||
shadows.push(currentShadow.trim());
|
||||
}
|
||||
|
|
@ -450,7 +471,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
for (let i = 0; i < shadows.length; i++) {
|
||||
const shadowStr = shadows[i];
|
||||
|
||||
// Parse the shadow to check if it has meaningful values
|
||||
const shadowParts = shadowStr.split(' ');
|
||||
const numericParts: number[] = [];
|
||||
const colorParts: string[] = [];
|
||||
|
|
@ -458,7 +478,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
let currentColor = '';
|
||||
let inColorFunction = false;
|
||||
|
||||
// Parse each part
|
||||
for (let j = 0; j < shadowParts.length; j++) {
|
||||
const part = shadowParts[j];
|
||||
const trimmedPart = part.trim();
|
||||
|
|
@ -469,23 +488,19 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
continue;
|
||||
}
|
||||
|
||||
// Check if this part starts a color function (rgba, rgb, hsl, hsla)
|
||||
if (trimmedPart.match(/^(rgba?|hsla?)\s*\(/i)) {
|
||||
inColorFunction = true;
|
||||
currentColor = trimmedPart;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're inside a color function, keep building it
|
||||
if (inColorFunction) {
|
||||
currentColor += ' ' + trimmedPart;
|
||||
|
||||
// Check if we've reached the end of the color function
|
||||
const openParens = (currentColor.match(/\(/g) || []).length;
|
||||
const closeParens = (currentColor.match(/\)/g) || []).length;
|
||||
|
||||
if (openParens <= closeParens) {
|
||||
// Color function is complete
|
||||
colorParts.push(currentColor);
|
||||
currentColor = '';
|
||||
inColorFunction = false;
|
||||
|
|
@ -501,7 +516,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
}
|
||||
}
|
||||
|
||||
// Check if the color is not completely transparent using colorToHex
|
||||
let hasVisibleColor = false;
|
||||
if (colorParts.length > 0) {
|
||||
const shadowColor = colorParts.join(' ');
|
||||
|
|
@ -509,34 +523,28 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
hasVisibleColor = !!(colorResult.hex && colorResult.hex !== '000000' && colorResult.opacity !== 0);
|
||||
}
|
||||
|
||||
// Check if we have any non-zero numeric values (offset, blur, or spread)
|
||||
const hasNonZeroValues = numericParts.some(value => value !== 0);
|
||||
|
||||
// Calculate a score for this shadow (higher is better)
|
||||
let shadowScore = 0;
|
||||
if (hasNonZeroValues) {
|
||||
// Count non-zero numeric values
|
||||
shadowScore += numericParts.filter(value => value !== 0).length;
|
||||
}
|
||||
if (hasVisibleColor) {
|
||||
shadowScore += 2; // Bonus for visible color
|
||||
shadowScore += 2;
|
||||
}
|
||||
|
||||
// Select this shadow if it has a better score
|
||||
if ((hasNonZeroValues || hasVisibleColor) && shadowScore > bestShadowScore) {
|
||||
selectedShadow = shadowStr;
|
||||
bestShadowScore = shadowScore;
|
||||
}
|
||||
}
|
||||
|
||||
// If no meaningful shadow found, use the first one
|
||||
if (!selectedShadow && shadows.length > 0) {
|
||||
selectedShadow = shadows[0];
|
||||
}
|
||||
|
||||
if (selectedShadow) {
|
||||
|
||||
// Parse the selected shadow
|
||||
const shadowParts = selectedShadow.split(' ');
|
||||
const numericParts: number[] = [];
|
||||
const colorParts: string[] = [];
|
||||
|
|
@ -544,7 +552,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
let currentColor = '';
|
||||
let inColorFunction = false;
|
||||
|
||||
// Parse each part
|
||||
for (let i = 0; i < shadowParts.length; i++) {
|
||||
const part = shadowParts[i];
|
||||
const trimmedPart = part.trim();
|
||||
|
|
@ -555,23 +562,19 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
continue;
|
||||
}
|
||||
|
||||
// Check if this part starts a color function (rgba, rgb, hsl, hsla)
|
||||
if (trimmedPart.match(/^(rgba?|hsla?)\s*\(/i)) {
|
||||
inColorFunction = true;
|
||||
currentColor = trimmedPart;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're inside a color function, keep building it
|
||||
if (inColorFunction) {
|
||||
currentColor += ' ' + trimmedPart;
|
||||
|
||||
// Check if we've reached the end of the color function
|
||||
const openParens = (currentColor.match(/\(/g) || []).length;
|
||||
const closeParens = (currentColor.match(/\)/g) || []).length;
|
||||
|
||||
if (openParens <= closeParens) {
|
||||
// Color function is complete
|
||||
colorParts.push(currentColor);
|
||||
currentColor = '';
|
||||
inColorFunction = false;
|
||||
|
|
@ -587,14 +590,12 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
}
|
||||
}
|
||||
|
||||
// Handle different shadow formats
|
||||
if (numericParts.length >= 2) {
|
||||
const offsetX = numericParts[0];
|
||||
const offsetY = numericParts[1];
|
||||
const blurRadius = numericParts.length >= 3 ? numericParts[2] : 0;
|
||||
const spreadRadius = numericParts.length >= 4 ? numericParts[3] : 0;
|
||||
|
||||
// Handle color - it can be anywhere in the parts
|
||||
let shadowColor = 'rgba(0, 0, 0, 0.3)'; // default color
|
||||
if (colorParts.length > 0) {
|
||||
shadowColor = colorParts.join(' ');
|
||||
|
|
@ -602,7 +603,6 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
|
||||
const shadowColorResult = colorToHex(shadowColor);
|
||||
|
||||
// Create shadow object if we have any meaningful values or visible color
|
||||
const hasValidValues = offsetX !== 0 || offsetY !== 0 || blurRadius > 0 || spreadRadius !== 0 ||
|
||||
(shadowColorResult.hex && shadowColorResult.hex !== '000000' && shadowColorResult.opacity !== 0);
|
||||
|
||||
|
|
@ -650,24 +650,19 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
const lineHeight = computedStyles.lineHeight;
|
||||
const innerText = el.textContent || '';
|
||||
|
||||
// Check if text is multiline by looking for newline characters or checking if text wraps due to bounds
|
||||
const htmlEl = el as HTMLElement;
|
||||
|
||||
// Get font size for comparison
|
||||
const fontSize = parseFloat(computedStyles.fontSize);
|
||||
const computedLineHeight = parseFloat(computedStyles.lineHeight);
|
||||
|
||||
// Estimate single line height (use computed line height if available, otherwise use font size * 1.2)
|
||||
const singleLineHeight = !isNaN(computedLineHeight) ? computedLineHeight : fontSize * 1.2;
|
||||
|
||||
// Check for multiline text
|
||||
const hasExplicitLineBreaks = innerText.includes('\n') || innerText.includes('\r') || innerText.includes('\r\n');
|
||||
const hasTextWrapping = htmlEl.offsetHeight > singleLineHeight * 2; // Allow some tolerance
|
||||
const hasTextWrapping = htmlEl.offsetHeight > singleLineHeight * 2;
|
||||
const hasOverflow = htmlEl.scrollHeight > htmlEl.clientHeight;
|
||||
|
||||
const isMultiline = hasExplicitLineBreaks || hasTextWrapping || hasOverflow;
|
||||
|
||||
// Only return line height if text is multiline
|
||||
if (isMultiline && lineHeight && lineHeight !== 'normal') {
|
||||
const parsedLineHeight = parseFloat(lineHeight);
|
||||
if (!isNaN(parsedLineHeight)) {
|
||||
|
|
@ -804,53 +799,3 @@ async function getElementAttributes(element: ElementHandle<Element>): Promise<El
|
|||
});
|
||||
return attributes;
|
||||
}
|
||||
|
||||
async function takeElementScreenshot(element: ElementHandle<Element>, screenshotsDir: string): Promise<string | undefined> {
|
||||
try {
|
||||
// Check element visibility and dimensions
|
||||
const elementInfo = await element.evaluate((el) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const styles = window.getComputedStyle(el);
|
||||
|
||||
// Check if element is visible
|
||||
const isVisible = styles.visibility !== 'hidden' &&
|
||||
styles.display !== 'none' &&
|
||||
styles.opacity !== '0';
|
||||
|
||||
if (!isVisible || rect.width <= 0 || rect.height <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
};
|
||||
});
|
||||
|
||||
if (!elementInfo) {
|
||||
console.warn('Element is not visible or has invalid dimensions, skipping screenshot');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
const uuid = crypto.randomUUID();
|
||||
const filename = `${uuid}.png`;
|
||||
const filePath = path.join(screenshotsDir, filename);
|
||||
|
||||
// Take screenshot of the element with accurate colors and opacity
|
||||
// This captures the element exactly as rendered in the browser with all CSS styles applied
|
||||
await element.screenshot({
|
||||
path: filePath as `${string}.png`,
|
||||
type: 'png',
|
||||
omitBackground: true // Use transparent background for better quality
|
||||
});
|
||||
|
||||
console.log(`Screenshot saved: ${filePath}`);
|
||||
return filePath;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error taking element screenshot:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
704
servers/nextjs/package-lock.json
generated
704
servers/nextjs/package-lock.json
generated
|
|
@ -35,7 +35,6 @@
|
|||
"@tiptap/extension-underline": "^2.0.0",
|
||||
"@tiptap/react": "^2.11.5",
|
||||
"@tiptap/starter-kit": "^2.11.5",
|
||||
"animejs": "^3.2.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
|
|
@ -44,19 +43,17 @@
|
|||
"marked": "^15.0.11",
|
||||
"next": "^14.2.14",
|
||||
"next-themes": "^0.4.6",
|
||||
"pptxgenjs": "^4.0.1",
|
||||
"puppeteer": "^24.13.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-element-to-jsx-string": "^15.0.0",
|
||||
"react-redux": "^9.1.2",
|
||||
"recharts": "^2.15.4",
|
||||
"sharp": "^0.34.3",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwind-scrollbar-hide": "^2.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -65,7 +62,6 @@
|
|||
"@types/puppeteer": "^5.4.7",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"cypress": "^14.3.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
|
|
@ -119,12 +115,6 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@base2/pretty-print-object": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz",
|
||||
"integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/@cypress/request": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz",
|
||||
|
|
@ -238,15 +228,6 @@
|
|||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
|
||||
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz",
|
||||
|
|
@ -285,402 +266,6 @@
|
|||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-arm64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-darwin-x64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz",
|
||||
"integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz",
|
||||
"integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz",
|
||||
"integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz",
|
||||
"integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
|
||||
"integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-s390x": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz",
|
||||
"integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz",
|
||||
"integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
|
||||
"integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz",
|
||||
"integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz",
|
||||
"integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-arm64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-ppc64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
|
||||
"integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-s390x": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz",
|
||||
"integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linux-x64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-linuxmusl-x64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-wasm32": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz",
|
||||
"integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^1.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-arm64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz",
|
||||
"integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-ia32": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz",
|
||||
"integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@img/sharp-win32-x64": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz",
|
||||
"integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
|
|
@ -2783,16 +2368,6 @@
|
|||
"@types/react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sharp": {
|
||||
"version": "0.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz",
|
||||
"integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==",
|
||||
"deprecated": "This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"sharp": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sinonjs__fake-timers": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
|
||||
|
|
@ -2852,12 +2427,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/animejs": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz",
|
||||
"integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
|
|
@ -3597,18 +3166,6 @@
|
|||
"react-dom": "^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
|
@ -3627,15 +3184,6 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||
|
|
@ -3691,6 +3239,7 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
|
|
@ -4014,14 +3563,6 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
|
|
@ -4819,12 +4360,6 @@
|
|||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/https": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
|
||||
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
|
|
@ -4869,27 +4404,6 @@
|
|||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/image-size": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
|
||||
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"queue": "6.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"image-size": "bin/image-size.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||
|
|
@ -4926,12 +4440,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
|
||||
|
|
@ -5063,15 +4571,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
|
|
@ -5105,12 +4604,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
|
@ -5230,18 +4723,6 @@
|
|||
"verror": "1.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"license": "(MIT OR GPL-3.0-or-later)",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lazy-ass": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
|
||||
|
|
@ -5252,15 +4733,6 @@
|
|||
"node": "> 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
|
|
@ -5848,12 +5320,6 @@
|
|||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -6091,27 +5557,6 @@
|
|||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pptxgenjs": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz",
|
||||
"integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.8.1",
|
||||
"https": "^1.0.0",
|
||||
"image-size": "^1.2.1",
|
||||
"jszip": "^3.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pptxgenjs/node_modules/@types/node": {
|
||||
"version": "22.16.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.4.tgz",
|
||||
"integrity": "sha512-PYRhNtZdm2wH/NT2k/oAJ6/f2VD2N2Dag0lGlx2vWgMSJXGNmlce5MiTQzoWAiIJtso30mjnfQCOKVH+kAQC/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
|
@ -6135,12 +5580,6 @@
|
|||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
|
||||
|
|
@ -6479,15 +5918,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/queue": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
|
||||
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
@ -6533,27 +5963,6 @@
|
|||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-element-to-jsx-string": {
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz",
|
||||
"integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@base2/pretty-print-object": "1.0.1",
|
||||
"is-plain-object": "5.0.0",
|
||||
"react-is": "18.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0",
|
||||
"react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-element-to-jsx-string/node_modules/react-is": {
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz",
|
||||
"integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
|
|
@ -6692,27 +6101,6 @@
|
|||
"pify": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
|
|
@ -6945,53 +6333,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.3",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
|
||||
"integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.4",
|
||||
"semver": "^7.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.34.3",
|
||||
"@img/sharp-darwin-x64": "0.34.3",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.2.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.2.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.2.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.2.0",
|
||||
"@img/sharp-libvips-linux-ppc64": "1.2.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.2.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.2.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.2.0",
|
||||
"@img/sharp-linux-arm": "0.34.3",
|
||||
"@img/sharp-linux-arm64": "0.34.3",
|
||||
"@img/sharp-linux-ppc64": "0.34.3",
|
||||
"@img/sharp-linux-s390x": "0.34.3",
|
||||
"@img/sharp-linux-x64": "0.34.3",
|
||||
"@img/sharp-linuxmusl-arm64": "0.34.3",
|
||||
"@img/sharp-linuxmusl-x64": "0.34.3",
|
||||
"@img/sharp-wasm32": "0.34.3",
|
||||
"@img/sharp-win32-arm64": "0.34.3",
|
||||
"@img/sharp-win32-ia32": "0.34.3",
|
||||
"@img/sharp-win32-x64": "0.34.3"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
@ -7096,19 +6437,6 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
||||
|
|
@ -7251,21 +6579,6 @@
|
|||
"bare-events": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
|
@ -7817,6 +7130,7 @@
|
|||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
|
|
@ -7897,6 +7211,18 @@
|
|||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"bin": {
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
|
|
|
|||
|
|
@ -52,13 +52,13 @@
|
|||
"react-dom": "^18",
|
||||
"react-redux": "^9.1.2",
|
||||
"recharts": "^2.15.4",
|
||||
"sharp": "^0.34.3",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwind-scrollbar-hide": "^2.0.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tiptap-markdown": "^0.8.10",
|
||||
"ws": "^8.18.0",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -67,7 +67,6 @@
|
|||
"@types/puppeteer": "^5.4.7",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/sharp": "^0.32.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/ws": "^8.5.13",
|
||||
"cypress": "^14.3.3",
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export const layoutName = 'Basic Info'
|
|||
export const layoutDescription = 'A clean slide layout with title, description text, and a supporting image.'
|
||||
|
||||
const basicInfoSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Product Overview').meta({
|
||||
title: z.string().min(3).max(50).default('Product Overview').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(800).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({
|
||||
description: z.string().min(10).max(180).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({
|
||||
description: "Main description text content",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const layoutName = 'Bullet Icons Only'
|
|||
export const layoutDescription = 'A slide layout with title, grid of bullet points with icons (no descriptions), and a supporting image.'
|
||||
|
||||
const bulletIconsOnlySlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Solutions').meta({
|
||||
title: z.string().min(3).max(50).default('Solutions').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
|
|
@ -17,10 +17,10 @@ const bulletIconsOnlySlideSchema = z.object({
|
|||
description: "Supporting image for the slide",
|
||||
}),
|
||||
bulletPoints: z.array(z.object({
|
||||
title: z.string().min(2).max(100).meta({
|
||||
title: z.string().min(2).max(80).meta({
|
||||
description: "Bullet point title",
|
||||
}),
|
||||
subtitle: z.string().min(5).max(200).optional().meta({
|
||||
subtitle: z.string().min(5).max(180).optional().meta({
|
||||
description: "Optional short subtitle or brief explanation",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export const layoutName = 'Bullet with Icons'
|
|||
export const layoutDescription = 'A bullets style slide with main content, supporting image, and bullet points with icons and descriptions.'
|
||||
|
||||
const bulletWithIconsSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Problem').meta({
|
||||
title: z.string().min(3).max(50).default('Problem').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Businesses face challenges with outdated technology and rising costs, limiting efficiency and growth in competitive markets.').meta({
|
||||
description: z.string().max(180).default('Businesses face challenges with outdated technology and rising costs, limiting efficiency and growth in competitive markets.').meta({
|
||||
description: "Main description text explaining the problem or topic",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
|
|
@ -20,10 +20,10 @@ const bulletWithIconsSlideSchema = z.object({
|
|||
description: "Supporting image for the slide",
|
||||
}),
|
||||
bulletPoints: z.array(z.object({
|
||||
title: z.string().min(2).max(100).meta({
|
||||
title: z.string().min(2).max(80).meta({
|
||||
description: "Bullet point title",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: z.string().min(10).max(180).meta({
|
||||
description: "Bullet point description",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ const chartDataSchema = z.object({
|
|||
});
|
||||
|
||||
const chartWithBulletsSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Market Size').meta({
|
||||
title: z.string().min(3).max(50).default('Market Size').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Businesses face challenges with outdated technology and rising costs, limiting efficiency and growth in competitive markets.').meta({
|
||||
description: z.string().min(10).max(180).default('Businesses face challenges with outdated technology and rising costs, limiting efficiency and growth in competitive markets.').meta({
|
||||
description: "Description text below the title",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
|
|
@ -51,10 +51,10 @@ const chartWithBulletsSlideSchema = z.object({
|
|||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
bulletPoints: z.array(z.object({
|
||||
title: z.string().min(2).max(100).meta({
|
||||
title: z.string().min(2).max(80).meta({
|
||||
description: "Bullet point title",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: z.string().min(10).max(180).meta({
|
||||
description: "Bullet point description",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export const layoutName = 'Intro Slide'
|
|||
export const layoutDescription = 'A clean slide layout with title, description text, presenter info, and a supporting image.'
|
||||
|
||||
const introSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Product Overview').meta({
|
||||
title: z.string().min(3).max(50).default('Product Overview').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(800).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({
|
||||
description: z.string().min(10).max(180).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({
|
||||
description: "Main description text content",
|
||||
}),
|
||||
presenterName: z.string().min(2).max(50).default('John Doe').meta({
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ export const layoutName = 'Metrics with Image'
|
|||
export const layoutDescription = 'A slide layout with supporting image on the left and title, description, and metrics grid on the right. Can be used alternatively with MetricSlide.'
|
||||
|
||||
const metricsWithImageSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Competitive Advantage').meta({
|
||||
title: z.string().min(3).max(50).default('Competitive Advantage').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(600).default('Ginyard International Co. stands out by offering custom digital solutions tailored to client needs, alongside long-term support to ensure lasting relationships and continuous adaptation.').meta({
|
||||
description: z.string().min(10).max(180).default('Ginyard International Co. stands out by offering custom digital solutions tailored to client needs, alongside long-term support to ensure lasting relationships and continuous adaptation.').meta({
|
||||
description: "Description text below the title",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const layoutName = 'Numbered Bullets'
|
|||
export const layoutDescription = 'A slide layout with large title, supporting image, and numbered bullet points with descriptions.'
|
||||
|
||||
const numberedBulletsSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Market Validation').meta({
|
||||
title: z.string().min(3).max(50).default('Market Validation').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
|
|
@ -17,10 +17,10 @@ const numberedBulletsSlideSchema = z.object({
|
|||
description: "Supporting image for the slide",
|
||||
}),
|
||||
bulletPoints: z.array(z.object({
|
||||
title: z.string().min(2).max(100).meta({
|
||||
title: z.string().min(2).max(80).meta({
|
||||
description: "Bullet point title",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: z.string().min(10).max(180).meta({
|
||||
description: "Bullet point description",
|
||||
}),
|
||||
})).min(1).max(4).default([
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const tableOfContentsSlideSchema = z.object({
|
|||
number: z.number().min(1).meta({
|
||||
description: "Section number"
|
||||
}),
|
||||
title: z.string().min(1).max(100).meta({
|
||||
title: z.string().min(1).max(80).meta({
|
||||
description: "Section title"
|
||||
}),
|
||||
pageNumber: z.string().min(1).max(10).meta({
|
||||
|
|
|
|||
|
|
@ -13,17 +13,17 @@ const teamMemberSchema = z.object({
|
|||
position: z.string().min(2).max(50).meta({
|
||||
description: "Job title or position"
|
||||
}),
|
||||
description: z.string().min(10).max(120).meta({
|
||||
description: z.string().max(180).meta({
|
||||
description: "Brief description of the team member (around 100 characters)"
|
||||
}),
|
||||
image: ImageSchema
|
||||
});
|
||||
|
||||
const teamSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Team Members').meta({
|
||||
description: "Main title of the slide",
|
||||
title: z.string().min(3).max(50).default('Our Team Members').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
companyDescription: z.string().min(10).max(600).default('Ginyard International Co. is a leading provider of innovative digital solutions tailored for businesses. Our mission is to empower organizations to achieve their goals through cutting-edge technology and strategic partnerships.').meta({
|
||||
companyDescription: z.string().min(10).max(180).default('Ginyard International Co. is a leading provider of innovative digital solutions tailored for businesses. Our mission is to empower organizations to achieve their goals through cutting-edge technology and strategic partnerships.').meta({
|
||||
description: "Company description or team introduction text",
|
||||
}),
|
||||
teamMembers: z.array(teamMemberSchema).min(2).max(6).default([
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { ElementHandle } from "puppeteer";
|
||||
|
||||
export interface ElementAttributes {
|
||||
tagName: string;
|
||||
id?: string;
|
||||
|
|
@ -57,6 +59,8 @@ export interface ElementAttributes {
|
|||
shape?: 'rectangle' | 'circle';
|
||||
connectorType?: string;
|
||||
textWrap?: boolean;
|
||||
should_screenshot?: boolean;
|
||||
element?: ElementHandle<Element>;
|
||||
}
|
||||
|
||||
export interface SlideAttributesResult {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ElementAttributes } from "@/types/element_attibutes";
|
||||
import { ElementAttributes, SlideAttributesResult } from "@/types/element_attibutes";
|
||||
import {
|
||||
PptxSlideModel,
|
||||
PptxTextBoxModel,
|
||||
|
|
@ -63,15 +63,14 @@ function convertLineHeightToRelative(lineHeight?: number, fontSize?: number): nu
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts ElementAttributes[][] to PptxSlideModel[]
|
||||
* Each inner array represents elements on a slide
|
||||
* Converts SlideAttributesResult[] to PptxSlideModel[]
|
||||
* Each SlideAttributesResult represents elements on a slide
|
||||
*/
|
||||
export function convertElementAttributesToPptxSlides(
|
||||
slidesAttributes: ElementAttributes[][],
|
||||
backgroundColors?: (string | undefined)[]
|
||||
slidesAttributes: SlideAttributesResult[]
|
||||
): PptxSlideModel[] {
|
||||
return slidesAttributes.map((slideElements, index) => {
|
||||
const shapes = slideElements.map(element => {
|
||||
return slidesAttributes.map((slideAttributes) => {
|
||||
const shapes = slideAttributes.elements.map(element => {
|
||||
return convertElementToPptxShape(element);
|
||||
}).filter(Boolean); // Remove any null/undefined shapes
|
||||
|
||||
|
|
@ -80,9 +79,9 @@ export function convertElementAttributesToPptxSlides(
|
|||
};
|
||||
|
||||
// Add background color if available
|
||||
if (backgroundColors && backgroundColors[index]) {
|
||||
if (slideAttributes.backgroundColor) {
|
||||
slide.background = {
|
||||
color: backgroundColors[index]!,
|
||||
color: slideAttributes.backgroundColor,
|
||||
opacity: 1.0
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue