Merge pull request #512 from presenton/fix/comfy_ui
fix: ComfyUI generation issue
This commit is contained in:
commit
5c0c09e623
13 changed files with 266 additions and 64 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -20,4 +20,5 @@ container.db
|
|||
.next-build
|
||||
.cursor
|
||||
.agents
|
||||
skills-lock.json
|
||||
skills-lock.json
|
||||
.codex/
|
||||
|
|
@ -111,7 +111,9 @@ class ImageGenerationService:
|
|||
"theme_prompt": prompt.theme_prompt,
|
||||
},
|
||||
)
|
||||
elif image_path.startswith("/app_data/") or image_path.startswith("/static/"):
|
||||
elif image_path.startswith("/app_data/") or image_path.startswith(
|
||||
"/static/"
|
||||
):
|
||||
return self._to_frontend_url(image_path)
|
||||
raise Exception(f"Image not found at {image_path}")
|
||||
|
||||
|
|
@ -174,14 +176,20 @@ class ImageGenerationService:
|
|||
response_parts = getattr(response, "parts", None)
|
||||
if not response_parts and getattr(response, "candidates", None):
|
||||
first_candidate = response.candidates[0] if response.candidates else None
|
||||
content = getattr(first_candidate, "content", None) if first_candidate else None
|
||||
content = (
|
||||
getattr(first_candidate, "content", None) if first_candidate else None
|
||||
)
|
||||
response_parts = getattr(content, "parts", None) if content else None
|
||||
|
||||
image_path = None
|
||||
for part in response_parts or []:
|
||||
if part.inline_data is not None:
|
||||
mime_type = getattr(part.inline_data, "mime_type", "") or ""
|
||||
ext = mime_type.split("/")[-1] if mime_type.startswith("image/") else "png"
|
||||
ext = (
|
||||
mime_type.split("/")[-1]
|
||||
if mime_type.startswith("image/")
|
||||
else "png"
|
||||
)
|
||||
image_path = os.path.join(output_directory, f"{uuid.uuid4()}.{ext}")
|
||||
if hasattr(part, "as_image"):
|
||||
part.as_image().save(image_path)
|
||||
|
|
@ -368,28 +376,89 @@ class ImageGenerationService:
|
|||
return image_path
|
||||
|
||||
def _inject_prompt_into_workflow(self, workflow: dict, prompt: str) -> dict:
|
||||
"""
|
||||
Find the prompt node in the workflow and inject the prompt text.
|
||||
Looks for a node with title 'Input Prompt' (case-insensitive).
|
||||
def norm(x) -> str:
|
||||
return str(x or "").strip().lower()
|
||||
|
||||
User must rename their prompt node to 'Input Prompt' in ComfyUI.
|
||||
"""
|
||||
for node_id, node_data in workflow.items():
|
||||
meta = node_data.get("_meta", {})
|
||||
title = meta.get("title", "").lower()
|
||||
def is_link(v) -> bool:
|
||||
return (
|
||||
isinstance(v, (list, tuple))
|
||||
and len(v) >= 2
|
||||
and isinstance(v[0], str)
|
||||
and isinstance(v[1], int)
|
||||
)
|
||||
|
||||
if title == "input prompt":
|
||||
if "inputs" in node_data and "text" in node_data["inputs"]:
|
||||
node_data["inputs"]["text"] = prompt
|
||||
print(
|
||||
f"Injected prompt into node {node_id}: {meta.get('title', '')}"
|
||||
)
|
||||
return workflow
|
||||
preferred_keys = (
|
||||
"text", "value", "prompt", "string", "content", "instruction", "input", "query"
|
||||
)
|
||||
|
||||
# string inputs that are usually NOT prompt text
|
||||
ignore_keys = {
|
||||
"filename_prefix", "ckpt_name", "clip_name", "vae_name", "unet_name",
|
||||
"sampler_name", "scheduler", "type", "device", "model", "lora_name"
|
||||
}
|
||||
|
||||
visited = set()
|
||||
|
||||
def try_set(node_id: str) -> bool:
|
||||
node_id = str(node_id)
|
||||
if node_id in visited:
|
||||
return False
|
||||
visited.add(node_id)
|
||||
|
||||
node = workflow.get(node_id)
|
||||
if not isinstance(node, dict):
|
||||
return False
|
||||
|
||||
inputs = node.setdefault("inputs", {})
|
||||
|
||||
# 1) preferred prompt-like keys
|
||||
for k in preferred_keys:
|
||||
if k in inputs and isinstance(inputs[k], str):
|
||||
inputs[k] = prompt
|
||||
return True
|
||||
|
||||
# 2) fallback: exactly one unambiguous writable string field
|
||||
string_candidates = [
|
||||
k for k, v in inputs.items()
|
||||
if isinstance(v, str) and k not in ignore_keys
|
||||
]
|
||||
if len(string_candidates) == 1:
|
||||
inputs[string_candidates[0]] = prompt
|
||||
return True
|
||||
|
||||
# 3) follow links from ANY input key (node-type agnostic)
|
||||
for v in inputs.values():
|
||||
if is_link(v):
|
||||
if try_set(v[0]):
|
||||
return True
|
||||
elif isinstance(v, list):
|
||||
for item in v:
|
||||
if is_link(item) and try_set(item[0]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
input_prompt_nodes = [
|
||||
node_id
|
||||
for node_id, node_data in workflow.items()
|
||||
if norm(node_data.get("_meta", {}).get("title")) == "input prompt"
|
||||
]
|
||||
|
||||
if not input_prompt_nodes:
|
||||
raise ValueError(
|
||||
"Could not find node with title 'Input Prompt'. Rename your prompt node to 'Input Prompt'."
|
||||
)
|
||||
|
||||
for nid in input_prompt_nodes:
|
||||
if try_set(nid):
|
||||
return workflow
|
||||
|
||||
raise ValueError(
|
||||
"Could not find a node with title 'Input Prompt' in the workflow. Please rename your prompt node to 'Input Prompt' in ComfyUI."
|
||||
"Found 'Input Prompt', but no writable prompt string field was found directly or through linked nodes."
|
||||
)
|
||||
|
||||
|
||||
|
||||
async def _submit_comfyui_workflow(
|
||||
self, session: aiohttp.ClientSession, comfyui_url: str, workflow: dict
|
||||
) -> str:
|
||||
|
|
|
|||
|
|
@ -121,10 +121,11 @@ const DashboardSidebar = () => {
|
|||
aria-label={itemLabel}
|
||||
title={itemLabel}
|
||||
>
|
||||
<div className="flex items-center ">
|
||||
{/* <div className="flex items-center ">
|
||||
<img src={imageProviderIcon} alt="image provider" className="w-5 h-5 rounded-full object-cover border border-[#EDEEEF]" />
|
||||
<img src={textProviderIcon} alt="text provider" className="w-5 h-5 rounded-full object-cover border border-[#EDEEEF]" />
|
||||
</div>
|
||||
</div> */}
|
||||
<Settings className={`h-4 w-4 ${isActive ? "text-[#5146E5]" : "text-slate-600"}`} />
|
||||
<span className="text-[11px] text-slate-800">{itemLabel}</span>
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ const Header = () => {
|
|||
|
||||
const backHref = backToUpload ? "/upload" : backToTemplates ? "/templates" : "/dashboard";
|
||||
const backLabel = backToUpload
|
||||
? "Back to upload"
|
||||
? "Back"
|
||||
: backToTemplates
|
||||
? "Back to templates"
|
||||
: "Go to your dashboard";
|
||||
? "Back"
|
||||
: "Back";
|
||||
|
||||
return (
|
||||
<div className="w-full sticky top-0 z-50 py-7 "
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ const SettingSideBar = ({ mode, setMode, selectedProvider, setSelectedProvider }
|
|||
<div className='relative w-6 h-6 rounded-full overflow-hidden border border-[#EDEEEF] flex items-center justify-center bg-white'>
|
||||
<Shield className='w-3.5 h-3.5 text-[#5146E5]' />
|
||||
</div>
|
||||
<p className='text-[#191919] text-xs font-medium'>Privacy</p>
|
||||
<p className='text-[#191919] text-xs font-medium'>Usage Analytics</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ const PresentationHeader = ({
|
|||
)}
|
||||
>
|
||||
{isEditingTitle ? (
|
||||
<div className="flex items-stretch gap-0.5 rounded-[14px] border border-[#E4E2EB] bg-white pl-3.5 pr-1 py-1 shadow-[0_2px_12px_rgba(17,3,31,0.06)] ring-2 ring-[#5141e5]/15">
|
||||
<div className="flex items-stretch w-[450px] gap-0.5 rounded-[14px] border border-[#E4E2EB] bg-white pl-3.5 pr-1 py-1 shadow-[0_2px_12px_rgba(17,3,31,0.06)] ring-2 ring-[#5141e5]/15">
|
||||
<input
|
||||
ref={titleInputRef}
|
||||
value={draftTitle}
|
||||
|
|
@ -364,7 +364,7 @@ const PresentationHeader = ({
|
|||
"disabled:pointer-events-none disabled:opacity-100 disabled:hover:bg-transparent"
|
||||
)}
|
||||
>
|
||||
<h2 className="min-w-0 flex-1 font-unbounded text-lg leading-snug text-[#101323]">
|
||||
<h2 className="min-w-0 flex-1 font-unbounded text-lg w-[450px] leading-snug text-[#101323]">
|
||||
<MarkdownRenderer
|
||||
content={presentationData?.title || "Presentation"}
|
||||
className="mb-0 min-w-0 overflow-hidden text-ellipsis line-clamp-1 text-sm text-[#101323] prose-p:my-0 prose-headings:my-0"
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ const SidePanel = ({
|
|||
className="w-full h-[calc(100vh-120px)] hide-scrollbar overflow-hidden slide-theme "
|
||||
>
|
||||
|
||||
<p className="text-xl font-normal pb-3.5 text-[#000000]">Slides ({presentationData?.slides?.length})</p>
|
||||
<p className="text-xl font-normal font-syne pb-3.5 text-[#000000]">Slides ({presentationData?.slides?.length})</p>
|
||||
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ const page = () => {
|
|||
<div className="relative">
|
||||
<Header />
|
||||
<div className="flex flex-col items-center justify-center mb-8">
|
||||
<h1 className="text-[64px] font-normal font-unbounded text-[#101323] ">
|
||||
<h1 className="text-[42px] font-normal font-unbounded text-[#101323] ">
|
||||
Generate Presentation
|
||||
</h1>
|
||||
<p className="text-xl font-syne text-[#101323CC]">Choose a design, set preferences, and generate polished slides.</p>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
|||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { getApiUrl } from "@/utils/api";
|
||||
import { MixpanelEvent, trackEvent } from "@/utils/mixpanel";
|
||||
|
||||
interface CodexConfigProps {
|
||||
codexModel: string;
|
||||
|
|
@ -118,6 +119,8 @@ export default function CodexConfig({
|
|||
|
||||
const handleSignIn = async () => {
|
||||
try {
|
||||
|
||||
trackEvent(MixpanelEvent.Codex_SignIn_API_Call);
|
||||
onInputChange('codex', 'LLM');
|
||||
|
||||
const res = await fetch(getApiUrl("/api/v1/ppt/codex/auth/initiate"), {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export enum MixpanelEvent {
|
|||
Home_SaveConfiguration_API_Call = 'Home Save Configuration API Call',
|
||||
Home_CheckOllamaModelPulled_API_Call = 'Home Check Ollama Model Pulled API Call',
|
||||
Home_DownloadOllamaModel_API_Call = 'Home Download Ollama Model API Call',
|
||||
Codex_SignIn_API_Call = 'Codex Sign In API Call',
|
||||
Outline_Generate_Presentation_Button_Clicked = 'Outline Generate Presentation Button Clicked',
|
||||
Outline_Select_Template_Button_Clicked = 'Outline Select Template Button Clicked',
|
||||
Outline_Add_Slide_Button_Clicked = 'Outline Add Slide Button Clicked',
|
||||
|
|
|
|||
|
|
@ -261,26 +261,85 @@ class ImageGenerationService:
|
|||
return image_path
|
||||
|
||||
def _inject_prompt_into_workflow(self, workflow: dict, prompt: str) -> dict:
|
||||
"""
|
||||
Find the prompt node in the workflow and inject the prompt text.
|
||||
Looks for a node with title 'Input Prompt' (case-insensitive).
|
||||
def norm(x) -> str:
|
||||
return str(x or "").strip().lower()
|
||||
|
||||
User must rename their prompt node to 'Input Prompt' in ComfyUI.
|
||||
"""
|
||||
for node_id, node_data in workflow.items():
|
||||
meta = node_data.get("_meta", {})
|
||||
title = meta.get("title", "").lower()
|
||||
def is_link(v) -> bool:
|
||||
return (
|
||||
isinstance(v, (list, tuple))
|
||||
and len(v) >= 2
|
||||
and isinstance(v[0], str)
|
||||
and isinstance(v[1], int)
|
||||
)
|
||||
|
||||
if title == "input prompt":
|
||||
if "inputs" in node_data and "text" in node_data["inputs"]:
|
||||
node_data["inputs"]["text"] = prompt
|
||||
print(
|
||||
f"Injected prompt into node {node_id}: {meta.get('title', '')}"
|
||||
)
|
||||
return workflow
|
||||
preferred_keys = (
|
||||
"text", "value", "prompt", "string", "content", "instruction", "input", "query"
|
||||
)
|
||||
|
||||
# string inputs that are usually NOT prompt text
|
||||
ignore_keys = {
|
||||
"filename_prefix", "ckpt_name", "clip_name", "vae_name", "unet_name",
|
||||
"sampler_name", "scheduler", "type", "device", "model", "lora_name"
|
||||
}
|
||||
|
||||
visited = set()
|
||||
|
||||
def try_set(node_id: str) -> bool:
|
||||
node_id = str(node_id)
|
||||
if node_id in visited:
|
||||
return False
|
||||
visited.add(node_id)
|
||||
|
||||
node = workflow.get(node_id)
|
||||
if not isinstance(node, dict):
|
||||
return False
|
||||
|
||||
inputs = node.setdefault("inputs", {})
|
||||
|
||||
# 1) preferred prompt-like keys
|
||||
for k in preferred_keys:
|
||||
if k in inputs and isinstance(inputs[k], str):
|
||||
inputs[k] = prompt
|
||||
return True
|
||||
|
||||
# 2) fallback: exactly one unambiguous writable string field
|
||||
string_candidates = [
|
||||
k for k, v in inputs.items()
|
||||
if isinstance(v, str) and k not in ignore_keys
|
||||
]
|
||||
if len(string_candidates) == 1:
|
||||
inputs[string_candidates[0]] = prompt
|
||||
return True
|
||||
|
||||
# 3) follow links from ANY input key (node-type agnostic)
|
||||
for v in inputs.values():
|
||||
if is_link(v):
|
||||
if try_set(v[0]):
|
||||
return True
|
||||
elif isinstance(v, list):
|
||||
for item in v:
|
||||
if is_link(item) and try_set(item[0]):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
input_prompt_nodes = [
|
||||
node_id
|
||||
for node_id, node_data in workflow.items()
|
||||
if norm(node_data.get("_meta", {}).get("title")) == "input prompt"
|
||||
]
|
||||
|
||||
if not input_prompt_nodes:
|
||||
raise ValueError(
|
||||
"Could not find node with title 'Input Prompt'. Rename your prompt node to 'Input Prompt'."
|
||||
)
|
||||
|
||||
for nid in input_prompt_nodes:
|
||||
if try_set(nid):
|
||||
return workflow
|
||||
|
||||
raise ValueError(
|
||||
"Could not find a node with title 'Input Prompt' in the workflow. Please rename your prompt node to 'Input Prompt' in ComfyUI."
|
||||
"Found 'Input Prompt', but no writable prompt string field was found directly or through linked nodes."
|
||||
)
|
||||
|
||||
async def _submit_comfyui_workflow(
|
||||
|
|
|
|||
103
servers/nextjs/package-lock.json
generated
103
servers/nextjs/package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@babel/standalone": "^7.28.2",
|
||||
"@babel/traverse": "^7.29.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
|
|
@ -117,12 +118,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.28.5",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
|
|
@ -130,33 +131,56 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.29.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
||||
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/types": "^7.29.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||
"dev": true,
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
|
||||
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.0"
|
||||
"@babel/types": "^7.29.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
|
|
@ -183,15 +207,46 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.28.6",
|
||||
"@babel/parser": "^7.28.6",
|
||||
"@babel/types": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
||||
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@babel/template": "^7.28.6",
|
||||
"@babel/types": "^7.29.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
|
||||
"integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
|
||||
"dev": true,
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
||||
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
"@babel/helper-validator-identifier": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
|
@ -6605,6 +6660,18 @@
|
|||
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/standalone": "^7.28.2",
|
||||
"@babel/traverse": "^7.29.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue