Merge pull request #514 from presenton/refactor/template_improvement

refactor/template improvement
This commit is contained in:
Shiva Raj Badu 2026-04-13 19:21:59 +05:45 committed by GitHub
commit 9f7d64d578
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 910 additions and 281 deletions

View file

@ -6,13 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Presenton</title>
<style>
@font-face {
font-family: "Unbounded";
src: url("../assets/fonts/Unbounded-Medium.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "Syne";
src: url("../assets/fonts/Syne-Regular.ttf") format("truetype");
@ -62,10 +55,7 @@
overflow: hidden;
font-family: "Syne", "Segoe UI", system-ui, sans-serif;
color: var(--title);
background:
radial-gradient(circle at 20% 18%, rgba(127, 104, 255, 0.16), transparent 28%),
radial-gradient(circle at 82% 84%, rgba(108, 144, 255, 0.12), transparent 30%),
linear-gradient(135deg, var(--bg-top) 0%, #f8f9ff 42%, var(--bg-bottom) 100%);
}
body::before {
@ -73,11 +63,7 @@
position: fixed;
inset: -20%;
pointer-events: none;
background:
radial-gradient(circle at center, rgba(255, 255, 255, 0.55), transparent 45%),
radial-gradient(circle at 30% 20%, rgba(145, 124, 255, 0.14), transparent 24%);
filter: blur(90px);
opacity: 0.9;
}
.launch-screen {
@ -98,7 +84,7 @@
font-size: clamp(1.3rem, 2.05vw, 1.72rem);
line-height: 1.12;
letter-spacing: -0.045em;
font-family: "Unbounded", "Syne", system-ui, sans-serif;
font-family: "Syne", system-ui, sans-serif;
font-weight: 500;
white-space: nowrap;
}
@ -142,12 +128,7 @@
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg,
transparent 0%,
rgba(255, 255, 255, 0.12) 36%,
rgba(255, 255, 255, 0.58) 50%,
rgba(255, 255, 255, 0.12) 64%,
transparent 100%);
animation: shimmer 1.85s linear infinite;
}
@ -209,4 +190,4 @@
<script src="./script.js"></script>
</body>
</html>
</html>

View file

@ -158,8 +158,7 @@ const SettingsPage = () => {
isDisabled: false,
text: "Save Configuration",
}));
trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/upload" });
router.push("/upload");
} catch (error) {
const message =
error instanceof Error

View file

@ -178,6 +178,8 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
"SCRIPT",
"STYLE",
"NOSCRIPT", // Script/style elements
"PRE",
"CODE", // Code block elements
];
// List of class patterns that indicate ignored element trees
@ -197,6 +199,10 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
"flowchart",
"mermaid",
"diagram",
"prism",
"token",
"code-block",
"language-",
];
// Check if current element or any parent is in ignored list
@ -231,6 +237,14 @@ const TiptapTextReplacer: React.FC<TiptapTextReplacerProps> = ({
return true;
}
// Skip syntax-highlighted and code-rendered areas entirely
if (
currentElement.classList.contains("prism-code-block") ||
!!currentElement.closest("pre, code, .prism-code-block")
) {
return true;
}
currentElement = currentElement.parentElement;
}
return false;

View file

@ -76,7 +76,7 @@ export const SlidePreviewSection: React.FC<SlidePreviewSectionProps> = ({
size="lg"
onClick={onInitTemplate}
disabled={isLoading}
className="px-4 py-2 h-auto text-xs font-semibold rounded-full shadow-lg hover:shadow-xl transition-all duration-300 "
className="px-4 py-2 h-auto text-xs font-syne font-medium rounded-full shadow-lg hover:shadow-xl transition-all duration-300 "
style={{
background: isLoading
? '#E5E7EB'
@ -91,7 +91,7 @@ export const SlidePreviewSection: React.FC<SlidePreviewSectionProps> = ({
</>
) : (
<>
<Sparkles className="w-4 h-4 mr-1" />
Generate Template
<ChevronRight className="w-4 h-4 ml-1" />
</>

View file

@ -2,6 +2,7 @@
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "@/store/store";
import "../utils/prism-languages";
import { Skeleton } from "@/components/ui/skeleton";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
@ -62,6 +63,10 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
const data = await DashboardApi.getPresentation(presentation_id);
dispatch(setPresentationData(data));
setContentLoading(false);
if (data.fonts) {
useFontLoader(data.fonts);
}
if (data?.theme) {
try {
applyTheme(data.theme);

View file

@ -4,6 +4,7 @@ import React from "react";
import { Button } from "@/components/ui/button";
import { useRouter, useSearchParams } from "next/navigation";
import PdfMakerPage from "./PdfMakerPage";
import "../utils/prism-languages";
const page = () => {
const router = useRouter();

View file

@ -389,6 +389,9 @@ const PresentationHeader = ({
) : (
titleBlock
)}
<a href={`/pdf-maker?id=${presentation_id}`}>
pdf-maker
</a>
<div className="flex items-center gap-2.5">
{isPresentationSaving && <div className="flex items-center gap-2">

View file

@ -2,6 +2,7 @@
import React, { useLayoutEffect, useState } from "react";
import { useSelector } from "react-redux";
import { RootState } from "@/store/store";
import "../../utils/prism-languages";
import { Skeleton } from "@/components/ui/skeleton";
import PresentationMode from "./PresentationMode";
import SidePanel from "./SidePanel";

View file

@ -27,7 +27,7 @@ const ThemeSelector = ({ current_theme, themes: allThemes }: { current_theme: an
dispatch(updateTheme(theme));
};
const resetTheme = async () => {
dispatch(updateTheme({} as any));
dispatch(updateTheme(null));
clearPresentationThemeFromElement(
document.getElementById("presentation-slides-wrapper")
);

View file

@ -6,6 +6,7 @@ import { DashboardApi } from '../../services/api/dashboard';
import { clearHistory } from "@/store/slices/undoRedoSlice";
import { applyPresentationThemeToElement } from "../utils/applyPresentationThemeDom";
import { resolveBackendAssetUrl } from "@/utils/api";
import { useFontLoader } from "../../hooks/useFontLoad";
const normalizePresentationAssets = <T,>(input: T): T => {
if (Array.isArray(input)) {
@ -46,6 +47,9 @@ export const usePresentationData = (
dispatch(clearHistory());
setLoading(false);
}
if (data.fonts) {
useFontLoader(data.fonts);
}
if (normalizedData?.theme) {
const el = document.getElementById("presentation-slides-wrapper");
applyPresentationThemeToElement(el, normalizedData.theme);

View file

@ -3,6 +3,7 @@ import React from "react";
import PresentationPage from "./components/PresentationPage";
import { Button } from "@/components/ui/button";
import { useRouter, useSearchParams } from "next/navigation";
import "../utils/prism-languages";
const page = () => {
const router = useRouter();

View file

@ -4,6 +4,7 @@ import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { ArrowLeft, Home, Loader2, Trash2 } from "lucide-react";
import "../../utils/prism-languages";
import { MixpanelEvent, trackEvent } from "@/utils/mixpanel";
import TemplateService from "../../services/api/template";

View file

@ -2,6 +2,7 @@
import React, { Suspense } from "react";
import { Loader2 } from "lucide-react";
import GroupLayoutPreview from "./components/TemplatePreviewClient";
import "../utils/prism-languages";
const TemplatePreviewPage = () => {
return (

View file

@ -0,0 +1,14 @@
import Prism from "prismjs";
import "prismjs/components/prism-json";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-jsx";
import "prismjs/components/prism-tsx";
import "prismjs/components/prism-python";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-yaml";
import "prismjs/components/prism-markdown";
void Prism;
export {};

View file

@ -1,4 +1,5 @@
import * as z from "zod";
import { fitCodeBlock, PRISM_CODE_BLOCK_STYLES } from "./codeBlockFitting";
export const slideLayoutId = "api-request-response-slide";
@ -63,15 +64,43 @@ export const Schema = z.object({
export type SchemaType = z.infer<typeof Schema>;
function normalizeApiJsonSnippet(content?: string) {
return (content || "")
.replace(/\r\n?/g, "\n")
.replace(/^\s*\/\s*$/gm, ",")
.replace(/\n\s*:\s*\n\s*/g, ": ")
.replace(/\n\s*\/\s*\n/g, ",\n")
.replace(/,\s*([}\]])/g, "$1")
.trimEnd();
}
const CodeSlide03ApiRequestResponse = ({
data,
}: {
data: Partial<SchemaType>;
}) => {
const requestCode = fitCodeBlock({
language: "json",
content: normalizeApiJsonSnippet(data.requestSnippet?.content),
maxWidth: 540,
maxHeight: 230,
maxFontSize: 14,
minFontSize: 8,
});
const responseCode = fitCodeBlock({
language: "json",
content: normalizeApiJsonSnippet(data.responseSnippet?.content),
maxWidth: 540,
maxHeight: 430,
maxFontSize: 14,
minFontSize: 8,
});
return (
<>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap" rel="stylesheet" />
<style>{PRISM_CODE_BLOCK_STYLES}</style>
<div
className="relative h-[720px] w-[1280px] overflow-hidden p-[53px]"
style={{
@ -83,8 +112,8 @@ const CodeSlide03ApiRequestResponse = ({
<div className="relative z-10 flex h-full flex-col">
<h2 className="text-[64px] font-medium" style={{ color: "var(--background-text,#ffffff)" }}>{data.title}</h2>
<div className="mt-[22px] grid flex-1 grid-cols-2 gap-[22px]">
<div className="flex flex-col gap-[12px] ">
<div className="mt-[22px] grid min-h-0 flex-1 grid-cols-2 gap-[22px]">
<div className="flex min-h-0 flex-col gap-[12px]">
<div
className="rounded-[14px] border p-[14px]"
style={{
@ -113,7 +142,7 @@ const CodeSlide03ApiRequestResponse = ({
</div>
<div
className=" flex-1 border rounded-[18px]"
className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[18px] border"
style={{
backgroundColor: "var(--card-color,#0F172B80)",
borderColor: "var(--stroke,#1D293D80)",
@ -129,17 +158,27 @@ const CodeSlide03ApiRequestResponse = ({
>
{data.requestSnippet?.fileName}
</p>
<pre className=" w-full px-[14px] py-[20px] whitespace-pre-wrap break-words overflow-hidden" style={{ color: "var(--background-text,#ffffff)" }}>
<code className="w-full ">
{data.requestSnippet?.content}
</code>
</pre>
<div className="min-h-0 w-full flex-1 overflow-hidden px-[14px] py-[20px]">
<pre
className="prism-code-block m-0 w-full overflow-hidden"
style={{
color: "var(--background-text,#ffffff)",
fontFamily: requestCode.fontFamily,
fontSize: `${requestCode.fontSize}px`,
lineHeight: `${requestCode.lineHeight}px`,
whiteSpace: "pre-wrap",
overflowWrap: "break-word",
wordBreak: "normal",
tabSize: 2,
}}
dangerouslySetInnerHTML={{ __html: requestCode.highlightedHtml }}
/>
</div>
</div>
</div>
<div
className=" flex-1 border rounded-[18px]"
className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[18px] border"
style={{
backgroundColor: "var(--card-color,#0F172B80)",
borderColor: "var(--stroke,#1D293D80)",
@ -155,12 +194,22 @@ const CodeSlide03ApiRequestResponse = ({
>
{data.responseSnippet?.fileName}
</p>
<pre className=" w-full px-[14px] py-[20px] whitespace-pre-wrap break-words overflow-hidden" style={{ color: "var(--background-text,#ffffff)" }}>
<code className="w-full ">
{data.responseSnippet?.content}
</code>
</pre>
<div className="min-h-0 w-full flex-1 overflow-hidden px-[14px] py-[20px]">
<pre
className="prism-code-block m-0 w-full overflow-hidden"
style={{
color: "var(--background-text,#ffffff)",
fontFamily: responseCode.fontFamily,
fontSize: `${responseCode.fontSize}px`,
lineHeight: `${responseCode.lineHeight}px`,
whiteSpace: "pre-wrap",
overflowWrap: "break-word",
wordBreak: "normal",
tabSize: 2,
}}
dangerouslySetInnerHTML={{ __html: responseCode.highlightedHtml }}
/>
</div>
</div>
</div>
</div>

View file

@ -139,7 +139,7 @@ const CodeSlide04FeatureGrid = ({ data }: { data: Partial<SchemaType> }) => {
url={feature.icon?.__icon_url__}
strokeColor={"currentColor"}
className="h-[24px] w-[24px] object-contain"
color="var(--primary-text, #000000)"
color="var(--primary-text, #ffffff)"
title={feature.icon.__icon_query__}
/>
</span>

View file

@ -1,126 +1,5 @@
import * as z from "zod";
const CODE_BLOCK_MAX_FONT_SIZE = 16;
const CODE_BLOCK_MIN_FONT_SIZE = 8;
const CODE_BLOCK_WIDTH = 506;
const CODE_BLOCK_HEIGHT = 430;
const CODE_CHAR_WIDTH_RATIO = 0.62;
const CODE_LINE_HEIGHT_RATIO = 1.25;
const CODE_FONT_FAMILY = "var(--code-font-family,'Liberation Mono', monospace)";
function splitCollapsedPythonImports(line: string) {
const importSegments = line
.split(/(?=\sfrom\s+[A-Za-z0-9_.]+\s+import\s+)/g)
.map((segment) => segment.trim())
.filter(Boolean);
return importSegments.length > 1 ? importSegments : [line];
}
function expandInlinePythonStatement(line: string) {
const inlineReturnMatch = line.match(/^(\s*def\s+[^(]+\([^)]*\):)\s+return\s+(.+)$/);
if (!inlineReturnMatch) {
return [line];
}
return [inlineReturnMatch[1], ` return ${inlineReturnMatch[2]}`];
}
function expandPathListAssignment(line: string) {
const trimmedLine = line.trim();
if (!trimmedLine.startsWith("urlpatterns = [") || !trimmedLine.endsWith("]")) {
return [line];
}
const pathCalls = trimmedLine.match(/path\([^)]*\)/g);
if (!pathCalls?.length) {
return [line];
}
return [
"urlpatterns = [",
...pathCalls.map((pathCall) => ` ${pathCall},`),
"]",
];
}
function normalizePythonCode(content: string) {
const normalizedLines: string[] = [];
for (const line of content.split("\n")) {
const importLines = splitCollapsedPythonImports(line);
for (const importLine of importLines) {
const expandedPathLines = expandPathListAssignment(importLine);
for (const expandedPathLine of expandedPathLines) {
normalizedLines.push(...expandInlinePythonStatement(expandedPathLine));
}
}
}
return normalizedLines.join("\n").replace(/\n{3,}/g, "\n\n");
}
function normalizeCodeContent(language?: string, content?: string) {
let normalizedContent = (content || "")
.replace(/\r\n?/g, "\n")
.replace(/\\\[/g, "[")
.replace(/\\\]/g, "]")
.trimEnd();
if (language?.toLowerCase() === "python") {
normalizedContent = normalizePythonCode(normalizedContent);
}
return normalizedContent;
}
function getCodeBlockTypography(content?: string) {
const normalizedLines = (content || "").replace(/\t/g, " ").split("\n");
const longestLineLength = Math.max(
1,
...normalizedLines.map((line) => line.length)
);
for (let fontSize = CODE_BLOCK_MAX_FONT_SIZE; fontSize >= CODE_BLOCK_MIN_FONT_SIZE; fontSize -= 0.5) {
const lineHeight = Math.round(fontSize * CODE_LINE_HEIGHT_RATIO);
const fitsWidth = longestLineLength * fontSize * CODE_CHAR_WIDTH_RATIO <= CODE_BLOCK_WIDTH;
const fitsHeight = normalizedLines.length * lineHeight <= CODE_BLOCK_HEIGHT;
if (fitsWidth && fitsHeight) {
return { fontSize, lineHeight };
}
}
return {
fontSize: CODE_BLOCK_MIN_FONT_SIZE,
lineHeight: Math.round(CODE_BLOCK_MIN_FONT_SIZE * CODE_LINE_HEIGHT_RATIO),
};
}
function getCodeLineRuns(content: string, lineHeight: number) {
const codeLineRuns: { text: string; marginTop: number }[] = [];
let blankLineCount = 0;
for (const line of content.split("\n")) {
if (line.length === 0) {
blankLineCount += 1;
continue;
}
codeLineRuns.push({
text: line,
marginTop: blankLineCount * lineHeight,
});
blankLineCount = 0;
}
return codeLineRuns;
}
import { fitCodeBlock, PRISM_CODE_BLOCK_STYLES } from "./codeBlockFitting";
export const slideLayoutId = "code-explanation-split-slide";
export const slideLayoutName = "Code Explanation Split Slide";
@ -186,16 +65,19 @@ const CodeSlide02CodeExplanationSplit = ({
}: {
data: Partial<SchemaType>;
}) => {
const normalizedCodeContent = normalizeCodeContent(
data.codeSnippet?.language,
data.codeSnippet?.content
);
const codeTypography = getCodeBlockTypography(normalizedCodeContent);
const codeLineRuns = getCodeLineRuns(normalizedCodeContent, codeTypography.lineHeight);
const fittedCode = fitCodeBlock({
language: data.codeSnippet?.language,
content: data.codeSnippet?.content,
maxWidth: 506,
maxHeight: 430,
maxFontSize: 16,
minFontSize: 8,
});
return (
<>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap" rel="stylesheet" />
<style>{PRISM_CODE_BLOCK_STYLES}</style>
<div
className="relative h-[720px] w-[1280px] overflow-hidden p-[53px]"
style={{
@ -229,22 +111,21 @@ const CodeSlide02CodeExplanationSplit = ({
className="min-h-0 w-full flex-1 overflow-hidden px-[32px] py-[20px]"
style={{
color: "var(--background-text,#ffffff)",
fontFamily: CODE_FONT_FAMILY,
}}
>
{codeLineRuns.map((codeLineRun, index) => (
<div
key={`code-line-${index}`}
style={{
marginTop: codeLineRun.marginTop ? `${codeLineRun.marginTop}px` : undefined,
fontSize: `${codeTypography.fontSize}px`,
lineHeight: `${codeTypography.lineHeight}px`,
whiteSpace: "pre",
}}
>
{codeLineRun.text}
</div>
))}
<pre
className="prism-code-block m-0 w-full overflow-hidden"
style={{
fontFamily: fittedCode.fontFamily,
fontSize: `${fittedCode.fontSize}px`,
lineHeight: `${fittedCode.lineHeight}px`,
whiteSpace: "pre-wrap",
overflowWrap: "break-word",
wordBreak: "normal",
tabSize: 2,
}}
dangerouslySetInnerHTML={{ __html: fittedCode.highlightedHtml }}
/>
</div>
</div>

View file

@ -91,11 +91,16 @@ const CodeSlide06Workflow = ({ data }: { data: Partial<SchemaType> }) => {
<h2 className="text-[64px] font-medium" style={{ color: "var(--background-text,#ffffff)" }}>{data.title}</h2>
<div className="mt-[52px] grid flex-1 grid-cols-[1fr_auto_1fr_auto_1fr_auto_1fr] items-center gap-[12px]">
<div className="mt-[52px] grid flex-1 justify-center items-center gap-[12px]"
style={{
gridTemplateColumns: data.steps?.length === 1 ? '1fr' : data?.steps?.length === 2 ? '1fr auto 1fr' : data?.steps?.length === 3 ? '1fr auto 1fr auto 1fr' : data?.steps?.length === 4 ? '1fr auto 1fr auto 1fr auto 1fr' : '1fr auto 1fr auto 1fr auto 1fr auto 1fr',
}}
>
{data?.steps?.map((step, index) => (
<Fragment key={`${step.title}-${index}`}>
<div
className="rounded-[18px] border p-[21px] text-center"
className="rounded-[18px] border max-w-[500px] mx-auto p-[21px] text-center"
style={{
boxShadow: "0 33.333px 66.667px -16px rgba(0, 0, 0, 0.25)",
borderColor: "var(--stroke,#1D293D80)",

View file

@ -0,0 +1,659 @@
import { jsonrepair } from "jsonrepair";
import Prism from "prismjs";
import "prismjs/components/prism-json";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-jsx";
import "prismjs/components/prism-tsx";
import "prismjs/components/prism-python";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-yaml";
import "prismjs/components/prism-markdown";
const DEFAULT_CODE_CHAR_WIDTH_RATIO = 0.62;
const DEFAULT_CODE_LINE_HEIGHT_RATIO = 1.25;
const DEFAULT_FONT_STEP = 0.5;
const HARD_MIN_FONT_SIZE = 4;
export const DEFAULT_CODE_FONT_FAMILY = "var(--code-font-family,'Liberation Mono', monospace)";
export const PRISM_CODE_BLOCK_STYLES = `
.prism-code-block .token {
display: inline !important;
white-space: inherit !important;
}
.prism-code-block .token.comment,
.prism-code-block .token.prolog,
.prism-code-block .token.doctype,
.prism-code-block .token.cdata {
color: #7b8ebf;
}
.prism-code-block .token.punctuation {
color: #a8b7e0;
}
.prism-code-block .token.property,
.prism-code-block .token.tag,
.prism-code-block .token.constant,
.prism-code-block .token.symbol,
.prism-code-block .token.deleted {
color: #7bc4ff;
}
.prism-code-block .token.boolean,
.prism-code-block .token.number {
color: #f5c97b;
}
.prism-code-block .token.selector,
.prism-code-block .token.attr-name,
.prism-code-block .token.string,
.prism-code-block .token.char,
.prism-code-block .token.builtin,
.prism-code-block .token.inserted {
color: #9fe6b8;
}
.prism-code-block .token.operator,
.prism-code-block .token.entity,
.prism-code-block .token.url,
.prism-code-block .token.variable {
color: #f5a97f;
}
.prism-code-block .token.atrule,
.prism-code-block .token.attr-value,
.prism-code-block .token.function,
.prism-code-block .token.class-name {
color: #b8a8ff;
}
.prism-code-block .token.keyword {
color: #7aa2ff;
}
.prism-code-block .token.regex,
.prism-code-block .token.important {
color: #f9e2af;
}
`;
interface FitCodeBlockOptions {
language?: string;
content?: string;
maxWidth: number;
maxHeight: number;
maxFontSize?: number;
minFontSize?: number;
fontStep?: number;
charWidthRatio?: number;
lineHeightRatio?: number;
}
interface TypographyCandidate {
lineHeight: number;
maxCharsPerLine: number;
renderedLineCount: number;
}
export interface FittedCodeBlock {
text: string;
highlightedHtml: string;
prismLanguage: string;
fontSize: number;
lineHeight: number;
fontFamily: string;
}
function splitCollapsedPythonImports(line: string) {
const importSegments = line
.split(/(?=\sfrom\s+[A-Za-z0-9_.]+\s+import\s+)/g)
.map((segment) => segment.trim())
.filter(Boolean);
return importSegments.length > 1 ? importSegments : [line];
}
function expandInlinePythonStatement(line: string) {
const inlineReturnMatch = line.match(/^(\s*def\s+[^(]+\([^)]*\):)\s+return\s+(.+)$/);
if (!inlineReturnMatch) {
return [line];
}
return [inlineReturnMatch[1], `return ${inlineReturnMatch[2]}`];
}
function expandPathListAssignment(line: string) {
const trimmedLine = line.trim();
if (!trimmedLine.startsWith("urlpatterns = [") || !trimmedLine.endsWith("]")) {
return [line];
}
const pathCalls = trimmedLine.match(/path\([^)]*\)/g);
if (!pathCalls?.length) {
return [line];
}
return [
"urlpatterns = [",
...pathCalls.map((pathCall) => ` ${pathCall},`),
"]",
];
}
function normalizePythonCode(content: string) {
const normalizedLines: string[] = [];
for (const line of content.split("\n")) {
const importLines = splitCollapsedPythonImports(line);
for (const importLine of importLines) {
const expandedPathLines = expandPathListAssignment(importLine);
for (const expandedPathLine of expandedPathLines) {
normalizedLines.push(...expandInlinePythonStatement(expandedPathLine));
}
}
}
return normalizedLines.join("\n").replace(/\n{3,}/g, "\n\n");
}
function tryFormatJson(content: string) {
const trimmedContent = content.replace(/^\uFEFF/, "").trim();
if (!trimmedContent) {
return "";
}
const normalizedSeparatorsContent = trimmedContent
.replace(/^\s*\/\s*$/gm, ",")
.replace(/\r\n?/g, "\n")
.replace(/\n\s*:\s*/g, ": ")
.replace(/\n\s*,\s*/g, ", ");
const parseAndFormat = (raw: string) => {
try {
const parsed = JSON.parse(raw);
if (typeof parsed === "string") {
try {
return JSON.stringify(JSON.parse(parsed), null, 2);
} catch {
return JSON.stringify(parsed, null, 2);
}
}
return JSON.stringify(parsed, null, 2);
} catch {
return null;
}
};
const extractedJsonMatch = normalizedSeparatorsContent.match(/[\[{][\s\S]*[\]}]/);
const extractedJsonCandidate = extractedJsonMatch?.[0];
const candidates = [
normalizedSeparatorsContent,
trimmedContent,
extractedJsonCandidate,
].filter((candidate): candidate is string => Boolean(candidate));
for (const candidate of candidates) {
const direct = parseAndFormat(candidate);
if (direct !== null) {
return direct;
}
try {
const repairedJson = jsonrepair(candidate);
const repaired = parseAndFormat(repairedJson);
if (repaired !== null) {
return repaired;
}
} catch {
// Try next parsing strategy.
}
}
const jsonLikeTokenMatch = normalizedSeparatorsContent.match(
/"(?:\\.|[^"\\])*"|true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}\[\]:,\/]/g
);
if (jsonLikeTokenMatch?.length) {
const normalizedTokens = jsonLikeTokenMatch.map((token) => (token === "/" ? "," : token));
let rebuilt = "";
for (const token of normalizedTokens) {
if (token === ":" || token === ",") {
rebuilt = rebuilt.replace(/\s*$/, "");
rebuilt += `${token} `;
continue;
}
if (token === "}" || token === "]") {
rebuilt = rebuilt.replace(/\s*$/, "");
rebuilt += token;
continue;
}
if (token === "{" || token === "[") {
rebuilt = rebuilt.replace(/\s*$/, "");
rebuilt += token;
continue;
}
rebuilt += token;
}
const rebuiltDirect = parseAndFormat(rebuilt);
if (rebuiltDirect !== null) {
return rebuiltDirect;
}
try {
const rebuiltRepaired = parseAndFormat(jsonrepair(rebuilt));
if (rebuiltRepaired !== null) {
return rebuiltRepaired;
}
} catch {
// Continue to best-effort line merge fallback below.
}
}
const normalizedLines = normalizedSeparatorsContent
.replace(/\n\s*:\s*\n\s*/g, ": ")
.replace(/\n\s*\/\s*\n/g, ",\n")
.split("\n")
.map((line) => line.replace(/\s+$/g, ""));
const mergedLines: string[] = [];
for (const rawLine of normalizedLines) {
const trimmedLine = rawLine.trim();
if (!trimmedLine) {
continue;
}
if (trimmedLine === ":") {
if (mergedLines.length > 0) {
mergedLines[mergedLines.length - 1] = `${mergedLines[mergedLines.length - 1]}:`;
}
continue;
}
if (trimmedLine === "/") {
if (mergedLines.length > 0 && !mergedLines[mergedLines.length - 1].trim().endsWith(",")) {
mergedLines[mergedLines.length - 1] = `${mergedLines[mergedLines.length - 1]},`;
}
continue;
}
const previousLine = mergedLines[mergedLines.length - 1]?.trim() || "";
if (previousLine.endsWith(":")) {
mergedLines[mergedLines.length - 1] = `${mergedLines[mergedLines.length - 1]} ${trimmedLine}`;
continue;
}
mergedLines.push(rawLine);
}
for (let index = 0; index < mergedLines.length - 1; index += 1) {
const currentLine = mergedLines[index].trim();
const nextLine = mergedLines[index + 1].trim();
const currentEndsWithComma = currentLine.endsWith(",");
const currentIsContainerStart = currentLine.endsWith("{") || currentLine.endsWith("[");
const nextStartsNewKey = nextLine.startsWith("\"");
const nextIsContainerEnd = nextLine.startsWith("}") || nextLine.startsWith("]");
if (!currentEndsWithComma && !currentIsContainerStart && nextStartsNewKey && !nextIsContainerEnd) {
mergedLines[index] = `${mergedLines[index]},`;
}
}
return mergedLines
.join("\n")
.replace(/,\s*([}\]])/g, "$1");
}
function isValidJsonContent(content: string) {
try {
JSON.parse(content.trim());
return true;
} catch {
return false;
}
}
function seemsJsonLike(content: string) {
const trimmed = content.trim();
if (!trimmed) {
return false;
}
if (/^[\[{]/.test(trimmed)) {
return true;
}
return /"[^"\n]+"\s*:/.test(trimmed) || /[\[{][\s\S]*[\]}]/.test(trimmed);
}
function unwrapMarkdownCodeFence(content: string) {
const trimmedContent = content.trim();
const fencedCodeMatch = trimmedContent.match(/^```([^\n`]*)\n([\s\S]*?)\n```$/);
if (!fencedCodeMatch) {
return {
content: content,
fenceLanguage: undefined as string | undefined,
};
}
return {
content: fencedCodeMatch[2],
fenceLanguage: fencedCodeMatch[1]?.trim().toLowerCase() || undefined,
};
}
function escapeHtml(content: string) {
return content
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
function resolvePrismLanguage(language?: string) {
const normalizedLanguage = language?.toLowerCase().trim();
if (!normalizedLanguage) {
return "clike";
}
if (normalizedLanguage.includes("json")) {
return "json";
}
if (normalizedLanguage.includes("python")) {
return "python";
}
if (normalizedLanguage === "ts") {
return "typescript";
}
if (normalizedLanguage === "js") {
return "javascript";
}
if (normalizedLanguage === "py") {
return "python";
}
if (normalizedLanguage === "sh" || normalizedLanguage === "shell") {
return "bash";
}
if (normalizedLanguage === "yml") {
return "yaml";
}
if (Prism.languages[normalizedLanguage]) {
return normalizedLanguage;
}
return "clike";
}
function highlightCode(content: string, language?: string) {
const prismLanguage = resolvePrismLanguage(language);
const grammar = Prism.languages[prismLanguage];
if (!grammar) {
return {
html: escapeHtml(content),
prismLanguage,
};
}
try {
return {
html: Prism.highlight(content, grammar, prismLanguage),
prismLanguage,
};
} catch {
return {
html: escapeHtml(content),
prismLanguage,
};
}
}
export function normalizeCodeContent(language?: string, content?: string) {
let normalizedContent = (content || "")
.replace(/\r\n?/g, "\n")
.replace(/\\\[/g, "[")
.replace(/\\\]/g, "]");
const unwrappedContent = unwrapMarkdownCodeFence(normalizedContent);
normalizedContent = unwrappedContent.content.trimEnd();
const normalizedLanguage = language?.toLowerCase()?.trim() || unwrappedContent.fenceLanguage;
const isJsonLanguage = normalizedLanguage?.includes("json");
const looksLikeJsonPayload = seemsJsonLike(normalizedContent);
if (normalizedLanguage === "python") {
normalizedContent = normalizePythonCode(normalizedContent);
} else if (isJsonLanguage || looksLikeJsonPayload) {
const formattedJson = tryFormatJson(normalizedContent);
normalizedContent = formattedJson;
}
return normalizedContent;
}
function countRenderedLines(content: string, maxCharsPerLine: number) {
const rawLines = content.split("\n");
let renderedLineCount = 0;
for (const rawLine of rawLines) {
const expandedLine = rawLine.replace(/\t/g, " ");
if (expandedLine.length === 0) {
renderedLineCount += 1;
continue;
}
renderedLineCount += Math.max(1, Math.ceil(expandedLine.length / maxCharsPerLine));
}
return Math.max(1, renderedLineCount);
}
function splitLineForLineBudget(line: string, maxCharsPerLine: number) {
if (line.length === 0) {
return [""];
}
const chunks: string[] = [];
for (let start = 0; start < line.length; start += maxCharsPerLine) {
chunks.push(line.slice(start, start + maxCharsPerLine));
}
return chunks;
}
function truncateContentToLineBudget(
content: string,
lineBudget: number,
maxCharsPerLine: number
) {
const linesForDisplay: string[] = [];
const rawLines = content.split("\n");
for (const rawLine of rawLines) {
const expandedLine = rawLine.replace(/\t/g, " ");
const chunks = splitLineForLineBudget(expandedLine, maxCharsPerLine);
for (const chunk of chunks) {
if (linesForDisplay.length >= lineBudget) {
const lastLineIndex = Math.max(0, lineBudget - 1);
const ellipsis = "...";
const existingLastLine = linesForDisplay[lastLineIndex] ?? "";
linesForDisplay[lastLineIndex] = `${existingLastLine.slice(
0,
Math.max(0, maxCharsPerLine - ellipsis.length)
)}${ellipsis}`;
return linesForDisplay.join("\n");
}
linesForDisplay.push(chunk);
}
}
return linesForDisplay.join("\n");
}
function createTypographyCandidate(
normalizedContent: string,
fontSize: number,
maxWidth: number,
charWidthRatio: number,
lineHeightRatio: number
): TypographyCandidate {
const lineHeight = Math.max(1, Math.round(fontSize * lineHeightRatio));
const maxCharsPerLine = Math.max(1, Math.floor(maxWidth / (fontSize * charWidthRatio)));
const renderedLineCount = countRenderedLines(normalizedContent, maxCharsPerLine);
return {
lineHeight,
maxCharsPerLine,
renderedLineCount,
};
}
function findFittingTypography(
normalizedContent: string,
startFontSize: number,
minFontSize: number,
maxWidth: number,
maxHeight: number,
fontStep: number,
charWidthRatio: number,
lineHeightRatio: number
) {
for (let fontSize = startFontSize; fontSize >= minFontSize; fontSize -= fontStep) {
const candidate = createTypographyCandidate(
normalizedContent,
fontSize,
maxWidth,
charWidthRatio,
lineHeightRatio
);
if (candidate.renderedLineCount * candidate.lineHeight <= maxHeight) {
return {
candidate,
fontSize,
};
}
}
return null;
}
export function fitCodeBlock({
language,
content,
maxWidth,
maxHeight,
maxFontSize = 16,
minFontSize = 8,
fontStep = DEFAULT_FONT_STEP,
charWidthRatio = DEFAULT_CODE_CHAR_WIDTH_RATIO,
lineHeightRatio = DEFAULT_CODE_LINE_HEIGHT_RATIO,
}: FitCodeBlockOptions): FittedCodeBlock {
const normalizedContent = normalizeCodeContent(language, content);
const highlightLanguage =
isValidJsonContent(normalizedContent) || seemsJsonLike(normalizedContent)
? "json"
: language;
const preferredMinFont = Math.max(1, minFontSize);
const hardMinFont = Math.max(1, Math.min(preferredMinFont, HARD_MIN_FONT_SIZE));
const startFont = Math.max(maxFontSize, preferredMinFont);
const preferredFit = findFittingTypography(
normalizedContent,
startFont,
preferredMinFont,
maxWidth,
maxHeight,
fontStep,
charWidthRatio,
lineHeightRatio
);
if (preferredFit) {
const highlighted = highlightCode(normalizedContent, highlightLanguage);
return {
text: normalizedContent,
highlightedHtml: highlighted.html,
prismLanguage: highlighted.prismLanguage,
fontSize: Math.round(preferredFit.fontSize * 10) / 10,
lineHeight: preferredFit.candidate.lineHeight,
fontFamily: DEFAULT_CODE_FONT_FAMILY,
};
}
if (hardMinFont < preferredMinFont) {
const emergencyFit = findFittingTypography(
normalizedContent,
preferredMinFont - fontStep,
hardMinFont,
maxWidth,
maxHeight,
fontStep,
charWidthRatio,
lineHeightRatio
);
if (emergencyFit) {
const highlighted = highlightCode(normalizedContent, highlightLanguage);
return {
text: normalizedContent,
highlightedHtml: highlighted.html,
prismLanguage: highlighted.prismLanguage,
fontSize: Math.round(emergencyFit.fontSize * 10) / 10,
lineHeight: emergencyFit.candidate.lineHeight,
fontFamily: DEFAULT_CODE_FONT_FAMILY,
};
}
}
const fallback = createTypographyCandidate(
normalizedContent,
hardMinFont,
maxWidth,
charWidthRatio,
lineHeightRatio
);
const fallbackLineBudget = Math.max(1, Math.floor(maxHeight / fallback.lineHeight));
const fallbackText = truncateContentToLineBudget(
normalizedContent,
fallbackLineBudget,
fallback.maxCharsPerLine
);
const highlighted = highlightCode(fallbackText, highlightLanguage);
return {
text: fallbackText,
highlightedHtml: highlighted.html,
prismLanguage: highlighted.prismLanguage,
fontSize: Math.round(hardMinFont * 10) / 10,
lineHeight: fallback.lineHeight,
fontFamily: DEFAULT_CODE_FONT_FAMILY,
};
}

View file

@ -9,7 +9,7 @@ const StatisticSchema = z.object({
value: z.string().max(8).meta({
description: "Main metric value shown at the top of one card.",
}),
label: z.string().max(20).meta({
label: z.string().max(45).meta({
description: "Label shown under the value.",
}),
});
@ -28,14 +28,14 @@ export const Schema = z.object({
.min(2)
.max(8)
.default([
{ value: "120", label: "Sales Team Strength" },
{ value: "15", label: "Senior Sales Officer" },
{ value: "1", label: "National Manager" },
{ value: "25", label: "Sales Officers" },
{ value: "2", label: "Regional Manager" },
{ value: "50", label: "Distributor Reps" },
{ value: "5", label: "Zonal Manager" },
{ value: "20", label: "Merchandising Team" },
{ value: "120", label: "Sales Team Strength with a long label to test the layouts" },
{ value: "15", label: "Senior Sales Officer with a long label to test the layout" },
{ value: "1", label: "National Manager with a long label to test the layout" },
{ value: "25", label: "Sales Officers with a long label to test the layout" },
{ value: "2", label: "Regional Manager with a long label to test the layout" },
{ value: "50", label: "Distributor Reps with a long label to test the layout" },
{ value: "5", label: "Zonal Manager with a long label to test the layout" },
{ value: "20", label: "Merchandising Team with a long label to the layout" },
])
.meta({
description: "statistic cards, with value and label each in a card",
@ -93,29 +93,35 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial<SchemaType> }) =
{data.stats && data.stats?.length > 4 && data.stats?.length <= 8 && (() => {
const rightArray = data.stats?.slice(0, Math.floor(data.stats?.length / 2));
const leftArray = data.stats?.slice(Math.floor(data.stats?.length / 2));
// const rightArray = data.stats?.slice(0, Math.floor(data.stats?.length / 2));
// const leftArray = data.stats?.slice(Math.floor(data.stats?.length / 2));
return (
<div className="h-full flex w-full">
<div className="flex flex-col h-full flex-1">
<div className="h-full grid grid-cols-2 w-full">
{/* <div className="flex flex-col h-full flex-1"> */}
{leftArray?.map((stat: any, index: number) => (
<div
key={`${stat?.value}-${index}`}
className="px-[52px] pt-[22px] h-full"
style={{ backgroundColor: index % 2 === 0 ? 'var(--card-color,#5C0FD908)' : 'var(--card-color,white)' }}
>
<p className=" text-[58px] leading-[56px]" style={{ color: "var(--background-text,#283E51)" }}>
{stat?.value}
</p>
<p className="mt-[12px] text-[24px]" style={{ color: "var(--background-text,#434A63)" }}>
{stat?.label}
</p>
</div>
))}
</div>
<div className="flex flex-col flex-1">
{data.stats?.map((stat: any, index: number) => (
<div
key={`${stat?.value}-${index}`}
className="px-[52px] pt-[22px] h-full"
style={{
backgroundColor: [0, 3, 4, 7].includes(index)
? 'var(--card-color,#5C0FD908)'
: 'var(--card-color,white)'
}}
>
<p className=" text-[58px] leading-[56px]" style={{ color: "var(--background-text,#283E51)" }}>
{stat?.value}
</p>
<p className="mt-[12px] text-[24px]" style={{ color: "var(--background-text,#434A63)" }}>
{stat?.label}
</p>
</div>
))}
{/* </div> */}
{/* <div className="flex flex-col flex-1">
{rightArray?.map((stat: any, index: number) => (
<div
@ -131,7 +137,7 @@ const EducationStatisticsGridSlide = ({ data }: { data: Partial<SchemaType> }) =
</p>
</div>
))}
</div>
</div> */}
</div>
);
})()}

View file

@ -32,7 +32,7 @@ const RowSchema = z.union([GeneralRowSchema, LegacyRowSchema]);
export const Schema = z.object({
title: z.string().max(14).default("Comparison Chart").meta({
title: z.string().max(24).default("Comparison Chart Comparison").meta({
description: "Main heading shown above the table.",
}),
subtitle: z.string().max(80).default(

View file

@ -8,15 +8,7 @@ export const slideLayoutDescription =
"A cover slide with a compact logo in the top-left, a date/text/label in the top-right, a centered title, and a image anchored to the bottom with a soft fade into the background.";
export const Schema = z.object({
image: z.object({
__image_url__: z.string().default("https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg"),
__image_prompt__: z.string().default("Image of the company"),
}).optional().default({
__image_url__:
"https://presenton-public.s3.ap-southeast-1.amazonaws.com/static/images/placeholder.jpg",
__image_prompt__: "Image of the company",
}),
label: z.string().min(3).max(16).optional().default("MARCH 2026").meta({
description: "Date/text/label shown at the top-right corner.",
}),
@ -56,11 +48,7 @@ const CoverSlide = ({ data }: { data: Partial<SchemaType> }) => {
>
<div className="flex items-center justify-between">
{data.image?.__image_url__ ? <img
src={data.image?.__image_url__ ?? ''}
alt={data.image?.__image_prompt__ || ''}
className="h-[42px] w-[171px] object-cover"
/> : <p></p>}
<p></p>
<p
className="text-[18px] font-normal leading-[18.991px] text-[#15342D]"

View file

@ -17,7 +17,7 @@ const IntroBlockSchema = z.object({
});
export const Schema = z.object({
title: z.string().min(4).max(16).default("Introduction").meta({
title: z.string().max(15).default("Introduction").meta({
description: "Primary title in the right column.",
}),
portraitImage: z.object({

View file

@ -81,13 +81,13 @@ const MetricCardSchema = z.object({
});
export const Schema = z.object({
title: z.string().min(4).max(12).default("Report").meta({
title: z.string().max(24).default("Report Report Report Report").meta({
description: "Slide heading text.",
}),
taglineLabel: z.string().min(3).max(10).default("TAGLINE").meta({
taglineLabel: z.string().max(24).default("TAGLINE").meta({
description: "Small label above intro paragraph.",
}),
taglineBody: z.string().min(40).max(120).default(
taglineBody: z.string().max(120).default(
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
).meta({
description: "Intro paragraph shown beneath the heading.",
@ -361,7 +361,7 @@ const ReportSnapshotSlide = ({ data }: { data: Partial<SchemaType> }) => {
{title}
</h2>
<div className="mt-[14px] w-[560px]">
<div className="mt-[14px] ">
<p
className="text-[20px] font-semibold tracking-[2.074px] text-[#083F37]"
style={{ color: "var(--primary-color,#083F37)" }}

View file

@ -3,10 +3,10 @@ import * as z from "zod";
const AnalysisItemSchema = z.object({
title: z.string().max(12).meta({
title: z.string().max(30).meta({
description: "Short item title displayed next to the icon.",
}),
description: z.string().max(30).meta({
description: z.string().max(60).meta({
description: "Supporting sentence shown below the title.",
}),
});
@ -35,12 +35,12 @@ export const Schema = z.object({
.max(6)
.default([
{ title: "Title 1", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 3", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 2", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 4", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 2", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 5", description: "Ut enim ad minima veniam, quis." },
{ title: "Title 1 title 1 title 1 title 1 title 1", description: "Ut enim ad minima veniam, quis. Ut enim ad minima veniam, quis. Ut enim" },
{ title: "Title 3 title 3 title 3 title 3 title 3", description: "Ut enim ad minima veniam, quis. Ut enim ad minima veniam, quis. Ut enim " },
{ title: "Title 2 title 2 title 2 title 2 title 2", description: "Ut enim ad minima veniam, quis. Ut enim ad minima veniam, quis. Ut enim " },
{ title: "Title 4 title 4 title 4 title 4 title 4", description: "Ut enim ad minima veniam, quis. Ut enim ad minima veniam, quis. Ut enim " },
{ title: "Title 2 title 2 title 2 title 2 title 2", description: "Ut enim ad minima veniam, quis. Ut enim ad minima veniam, quis. Ut enim " },
{ title: "Title 5 title 5 title 5 title 5 title 5", description: "Ut enim ad minima veniam, quis. Ut enim ad minima veniam, quis. Ut enim " },
])
.meta({
description: "List of points contains a title and description.",
@ -76,7 +76,7 @@ const DataAnalysisListSlide = ({ data }: { data: Partial<SchemaType> }) => {
</h2>
</div>
<div className="grid grid-cols-2 gap-x-[112px] gap-y-[52px] px-[82px] pt-[58px]">
<div className="grid grid-cols-2 gap-x-[92px] gap-y-[42px] px-[82px] pt-[58px]">
{items?.map((item, index) => (
<div key={`${item.title}-${index}`}>
<div className="flex items-center gap-[14px]">

View file

@ -136,9 +136,9 @@ function SummaryCard({
return (
<div className="flex gap-[10px] items-center rounded-[14px] py-[9px]">
<div
className="flex h-[36px] w-[36px] border border-[#ECF5FE] shrink-0 items-center justify-center rounded-full bg-[#ECF5FE] "
className="flex h-[36px] w-[36px] items-center justify-center border border-[#ECF5FE] shrink-0 rounded-full bg-[#ECF5FE] "
style={{
backgroundColor: "var(--card-color,#ECF5FE)",
backgroundColor: "var(--primary-color,#ECF5FE)",
borderColor: "var(--stroke,#ECF5FE)",
}}
>
@ -149,12 +149,7 @@ function SummaryCard({
color="var(--primary-text, #000000)"
title={iconAlt ?? ""}
/>
{/* <img
src={iconUrl ?? ""}
alt={iconAlt ?? ""}
className="h-[18px] w-[18px] object-contain"
/> */}
</div>
<div className="">
<p
@ -224,7 +219,11 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial<SchemaType> }) =>
</div>}
<div className="flex-1 flex flex-col pb-[30px]">
{halfChart && halfChart.length > 0 && <div className="mt-[14px] px-[64px] flex-1">
{halfChart && halfChart.length > 0 && <div className="mt-[14px] px-[64px] flex-1 "
style={{
height: otherHalfChart && otherHalfChart?.length > 0 ? '200px' : 'auto',
}}
>
<div
className={`grid h-full bg-white p-[13px] rounded-[14px] min-h-0 gap-[10px] `}
style={{
@ -240,13 +239,16 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial<SchemaType> }) =>
>
<div className="flex-1 " >
<FlexibleReportChart density="compact" chartType={chart.type} data={chart.data} series={chart.series} />
<ResponsiveContainer width="100%" height="100%" maxHeight={otherHalfChart && otherHalfChart?.length > 0 ? 200 : 400}>
<FlexibleReportChart density="compact" chartType={chart.type} data={chart.data} series={chart.series} />
</ResponsiveContainer>
</div>
</div>
))}
</div>
</div>}
{otherHalfChart && otherHalfChart.length > 0 && <div className="mt-[14px] px-[64px] flex-1">
{otherHalfChart && otherHalfChart.length > 0 && <div className="mt-[14px] px-[64px] flex-1 h-[200px] ">
<div
className={`grid h-full bg-white p-[13px] rounded-[14px] min-h-0 gap-[10px] `}
style={{
@ -260,10 +262,10 @@ const DataAnalysisDashboardSlide = ({ data }: { data: Partial<SchemaType> }) =>
className="rounded-[6px] flex flex-col overflow-hidden"
>
<div className="flex-1 " >
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height="100%" maxHeight={180}>
<FlexibleReportChart density="compact" chartType={chart.type} data={chart.data} series={chart.series} />
</ResponsiveContainer>
</div>
</div>
))}

View file

@ -8,7 +8,7 @@ const MilestoneItemSchema = z.object({
description: "Heading displayed below the milestone marker.",
}),
description: z.string().min(10).max(80).meta({
description: "Supporting milestone description shown under the heading.",
description: "Supporting milestone description shown under the heading. with max 80 characters",
}),
});
@ -32,27 +32,27 @@ export const Schema = z.object({
{
bulletNumber: "01",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet,",
},
{
bulletNumber: "02",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
description: "Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet,",
},
{
bulletNumber: "03",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
description: "Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, Lorem ipsum dolor sit amet",
},
{
bulletNumber: "04",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet,",
},
{
bulletNumber: "05",
heading: "Heading",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet,",
},
])
.meta({
@ -118,7 +118,7 @@ const MilestoneSlide = ({ data }: { data: Partial<SchemaType> }) => {
</div>
<div
key={`${item.heading}-${index}`}
className={`text-center mt-[20px] text-[#232223] ${index > 0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`}
className={`text-center h-[130px] mt-[20px] text-[#232223] ${index > 0 ? 'pr-[33px]' : ''} ${index === 0 ? 'px-[33px]' : ''}`}
style={{ color: "var(--background-text,#232223)" }}
>
<h3 className="text-[20px] text-[#232223] font-medium tracking-[2.074px]" style={{ color: "var(--background-text,#232223)" }}>

View file

@ -382,8 +382,6 @@ export function FlexibleReportChart({
case "bar":
return (
<ResponsiveContainer width="100%" height="100%">
<BarChart data={normalizedData as any[]} {...commonProps}>
<CartesianGrid vertical={false} {...gridProps} />
<XAxis

View file

@ -46,11 +46,11 @@ export const ProgressBar = ({ duration, onComplete }: ProgressBarProps) => {
return (
<div className="w-full space-y-2">
<div className="flex justify-end items-center text-white/80 text-sm">
<div className="flex justify-end items-center text-sm">
{/* <span>Processing...</span> */}
<span className='font-inter text-end font-medium text-xs'>{Math.round(progress)}%</span>
<span className='font-inter text-[#191919]/80 text-end font-medium text-xs'>{Math.round(progress)}%</span>
</div>
<div className="w-full bg-white rounded-full h-2 overflow-hidden">
<div className="w-full bg-white/40 rounded-full h-2 overflow-hidden">
<div
className="h-full bg-gradient-to-r from-[#9034EA] via-[#5146E5] to-[#9034EA] rounded-full animate-gradient transition-all duration-300 ease-out"
style={{

View file

@ -1,9 +1,25 @@
"use client"
import type React from "react"
import { BadgeCheck, Loader2, ShieldAlert } from "lucide-react"
import { BadgeCheck, Info, Loader2, ShieldAlert } from "lucide-react"
import { Toaster as Sonner, toast as sonnerToast } from "sonner"
/** Blue circle for neutral / informational toasts (matches web `servers/nextjs` Toaster). */
function NeutralToastIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 19" fill="none">
<path d="M9.12333 17.4567C13.7257 17.4567 17.4567 13.7257 17.4567 9.12337C17.4567 4.521 13.7257 0.790039 9.12333 0.790039C4.52096 0.790039 0.790001 4.521 0.790001 9.12337C0.790001 13.7257 4.52096 17.4567 9.12333 17.4567Z" fill="url(#paint0_linear_4686_451)" stroke="#2863A3" strokeWidth="1.58" strokeLinecap="round" strokeLinejoin="round" />
<defs>
<linearGradient id="paint0_linear_4686_451" x1="9.12333" y1="0.790039" x2="9.12333" y2="17.4567" gradientUnits="userSpaceOnUse">
<stop stopColor="#1880F6" />
<stop offset="1" stopColor="#75B5FF" />
</linearGradient>
</defs>
</svg>
)
}
/** Toasts with both title and description (matches styled [data-title] / [data-description]). */
export const notify = {
error: (title: string, description: string) =>
@ -20,7 +36,7 @@ const Toaster = ({ icons, ...props }: ToasterProps) => {
const defaultIcons: NonNullable<ToasterProps["icons"]> = {
success: <BadgeCheck aria-hidden="true" />,
error: <ShieldAlert aria-hidden="true" />,
info: <ShieldAlert aria-hidden="true" />,
info: <Info className="fill-[#1880F6] stroke-white" />,
warning: <ShieldAlert aria-hidden="true" />,
loading: <Loader2 aria-hidden="true" className="animate-spin" />,
close: <span aria-hidden="true">Got it!</span>,

View file

@ -41,13 +41,13 @@ interface CodexModel {
}
const CHATGPT_MODELS: CodexModel[] = [
{ id: "gpt-5.1", name: "GPT-5.1" },
{ id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" },
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "gpt-5.4-mini", name: "GPT-5.4 Mini" },
{ id: "gpt-5.4", name: "GPT-5.4" },
{ id: "gpt-5.1", name: "GPT-5.1" },
{ id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" },
{ id: "gpt-5.2", name: "GPT-5.2" },
{ id: "gpt-5.2-codex", name: "GPT-5.2 Codex" },
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
{ id: "gpt-5.4-mini", name: "GPT-5.4 Mini" },
{ id: "gpt-5.4", name: "GPT-5.4" },
];
const DEFAULT_CODEX_MODEL = "gpt-5.4-mini";