Conversation
…d add pydantic to requirements
- add expiration parameter to generate_public_url method - replace static public URLs with presigned URLs in relevant methods - update logic to handle presigned URLs in filename parsing - modify S3 upload method to return file key instead of static URL
- remove redundant onClick from AppImage component - add onClick to outer div for better event handling
…issing - handle missing artwork title by normalizing prompt into a valid title - simplify null/undefined checks with optional chaining
- replace ModelsAccordion with dynamic feature filtering and rendering - refactor mobile modal logic and active feature label - improve feature item rendering and icon styling with React.cloneElement - remove unused Model and features-related legacy code
- move ImagineSettings and ModelSelect to topBar section - adjust ImagineMenu styles with margin-bottom addition - add topBar styles for alignment and spacing in ImagineBase - refactor ModelSelect initialization and enhance compatibility filtering - add triggerClassName support to InputSelect component
…t and morpheus-admin Replace all SCSS module files with Tailwind inline classes, remove SASS dependencies and build configuration, and add typography color modifier classes to globals.css via @layer components. Fix visual regressions across auth forms, navbar, gallery, artwork cards, avatar fallbacks, and the about page to match original styling. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…eus-server Eliminates the separate morpheus-data wheel package by moving all its code directly into morpheus-server. morpheus-worker was already independent. - Move models, schemas, repositories, registry, utils, and migrations into morpheus-server/app/ and morpheus-server/migrations/ - Update all imports from morpheus_data.* to app.* - Flatten Settings inheritance into morpheus-server/app/config.py - Remove datalib service from docker-compose.yaml - Remove wheel build steps from both Dockerfiles - Add alembic.ini to morpheus-server; migrations now run via api service - Add diffusers to morpheus-server/requirements.txt (previously from wheel) - Update CLAUDE.md to reflect new structure Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…S3 uploads - Add @lru_cache to get_settings() to avoid re-reading env file on every call - Change default scheduler from DDPMScheduler to DPMSolverMultistepScheduler which converges at ~20 steps vs 50+, halving inference time - Reduce default num_inference_steps from 50 to 20 - Parallelize S3 uploads with ThreadPoolExecutor so multi-image requests upload all results concurrently instead of sequentially Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…eloading Previously every request created a new ephemeral SD actor, which loaded the full model from disk on every generation (30-120s overhead per request). Now handlers call _get_or_create_generator() which looks up an actor by name in the 'morpheus' namespace. On the first request for a given model/pipeline combo the actor is created with lifetime='detached' so it persists. All subsequent requests reuse the warm actor and skip model loading entirely. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…t startup Each SD actor now overrides _warmup() and runs a minimal 1-step pipeline inference with dummy inputs immediately after model load. This moves the CUDA JIT compilation overhead from the first real user request to actor startup, so the first actual generation is as fast as subsequent ones. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…oyer pattern start.sh previously only ran 'ray start --block' without ever deploying Ray Serve, leaving port 8000 dead. A separate worker-ray-deployer container was supposed to fix this via 'ray job submit -- serve deploy models.yaml', but RAY_ADDRESS set in the deployer container is not forwarded into submitted Ray jobs, so serve deploy had no way to reach the Serve controller. Replace with: ray start (non-blocking) then serve run /opt/models.yaml which connects to the running cluster, deploys the app, and keeps the container alive. Also bump APIIngress to 2 replicas to handle concurrent requests. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- delete `celery/config.py`, `mlmodels`, `utils`, and `tasks` modules - remove `daemon-set-workers-diffusion.yaml` and `daemon-set-workers-prompt.yaml` Helm templates
- create main.css for defining Excalidraw-specific styles - add themes, color pickers, context menus, buttons, and dialog styles - optimize for dark and light themes, RTL support, and responsive design
- add *-secrets.yaml and *secrets*.yaml to ignore patterns
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
| useEffect(() => { | ||
| if (!router.pathname.endsWith("paint")) { | ||
| const currentModelSupportsFeature = | ||
| selectedModel && | ||
| selectedModel.categories && | ||
| selectedModel.categories.some( | ||
| (category) => category.name === imagineOptionPath | ||
| ); | ||
| const currentModelSupportsFeature = selectedModel?.categories?.some( | ||
| (c) => c.name === imagineOptionPath | ||
| ); | ||
|
|
||
| if (currentModelSupportsFeature) { | ||
| setActiveLink({ | ||
| model: selectedModel, | ||
| feature: imagineOptionPath as ModelCategory, | ||
| }); | ||
| } else { | ||
| const compatibleModel = findValidModelForFeature(imagineOptionPath as ModelCategory); | ||
| setActiveLink({ | ||
| model: findValidModelForFeature(imagineOptionPath as ModelCategory), | ||
| model: compatibleModel, | ||
| feature: imagineOptionPath as ModelCategory, | ||
| }); | ||
| } | ||
| } | ||
| }, [imagineOptionPath]); |
There was a problem hiding this comment.
Suggestion: The useEffect dependency array is missing several dependencies that are used within the effect: router.pathname, selectedModel, setActiveLink, and findValidModelForFeature. This can lead to stale closures and unexpected behavior when these dependencies change but the effect doesn't re-run. [possible issue, importance: 8]
| useEffect(() => { | |
| if (!router.pathname.endsWith("paint")) { | |
| const currentModelSupportsFeature = | |
| selectedModel && | |
| selectedModel.categories && | |
| selectedModel.categories.some( | |
| (category) => category.name === imagineOptionPath | |
| ); | |
| const currentModelSupportsFeature = selectedModel?.categories?.some( | |
| (c) => c.name === imagineOptionPath | |
| ); | |
| if (currentModelSupportsFeature) { | |
| setActiveLink({ | |
| model: selectedModel, | |
| feature: imagineOptionPath as ModelCategory, | |
| }); | |
| } else { | |
| const compatibleModel = findValidModelForFeature(imagineOptionPath as ModelCategory); | |
| setActiveLink({ | |
| model: findValidModelForFeature(imagineOptionPath as ModelCategory), | |
| model: compatibleModel, | |
| feature: imagineOptionPath as ModelCategory, | |
| }); | |
| } | |
| } | |
| }, [imagineOptionPath]); | |
| useEffect(() => { | |
| if (!router.pathname.endsWith("paint")) { | |
| const currentModelSupportsFeature = selectedModel?.categories?.some( | |
| (c) => c.name === imagineOptionPath | |
| ); | |
| if (currentModelSupportsFeature) { | |
| setActiveLink({ | |
| model: selectedModel, | |
| feature: imagineOptionPath as ModelCategory, | |
| }); | |
| } else { | |
| const compatibleModel = findValidModelForFeature(imagineOptionPath as ModelCategory); | |
| setActiveLink({ | |
| model: compatibleModel, | |
| feature: imagineOptionPath as ModelCategory, | |
| }); | |
| } | |
| } | |
| }, [imagineOptionPath, router.pathname, selectedModel, setActiveLink, findValidModelForFeature]); |
| <div className="w-10 min-w-[40px] max-w-[40px] h-10 min-h-[40px] max-h-[40px] cursor-pointer rounded-full flex justify-center items-center" style={getImageStyles()}> | ||
| <img | ||
| src={user.avatar || "/images/avatar.png"} | ||
| alt="avatar" | ||
| style={getImageStyles()} | ||
| loading="lazy" | ||
| className="w-full h-full rounded-full object-cover" | ||
| onError={(e) => { (e.target as HTMLImageElement).src = "/images/avatar.png"; }} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Suggestion: The getImageStyles() function is called twice unnecessarily - once on the container div and once on the img element. Since the function is meant to style the image based on size, it should only be applied to the img element. [general, importance: 5]
| <div className="w-10 min-w-[40px] max-w-[40px] h-10 min-h-[40px] max-h-[40px] cursor-pointer rounded-full flex justify-center items-center" style={getImageStyles()}> | |
| <img | |
| src={user.avatar || "/images/avatar.png"} | |
| alt="avatar" | |
| style={getImageStyles()} | |
| loading="lazy" | |
| className="w-full h-full rounded-full object-cover" | |
| onError={(e) => { (e.target as HTMLImageElement).src = "/images/avatar.png"; }} | |
| /> | |
| </div> | |
| <div className="w-10 min-w-[40px] max-w-[40px] h-10 min-h-[40px] max-h-[40px] cursor-pointer rounded-full flex justify-center items-center"> | |
| <img | |
| src={user.avatar || "/images/avatar.png"} | |
| alt="avatar" | |
| style={getImageStyles()} | |
| loading="lazy" | |
| className="w-full h-full rounded-full object-cover" | |
| onError={(e) => { (e.target as HTMLImageElement).src = "/images/avatar.png"; }} | |
| /> | |
| </div> |
| const [showModelsModal, setShowModelsModal] = useState(false); | ||
| const [showMobileModal, setShowMobileModal] = useState(false); | ||
| const { isMobile } = useWindowDimensions(); | ||
| const lastShownAlertRef = useRef<string | null>(null); |
There was a problem hiding this comment.
Suggestion: The lastShownAlertRef is declared but never used in the component. This creates unnecessary code that could confuse other developers. If it's intended for future use, consider adding a comment or removing it until needed. [general, importance: 3]
| const lastShownAlertRef = useRef<string | null>(null); | |
| // Remove the unused ref or add a comment explaining its future use | |
| // const lastShownAlertRef = useRef<string | null>(null); |
| findValidModelForFeature, | ||
| } = useModels(); | ||
| const { models, selectedModel, activeLink, setActiveLink, findValidModelForFeature } = useModels(); | ||
| const { showInfoAlert } = useToastContext(); |
There was a problem hiding this comment.
Suggestion: The showInfoAlert function is imported from the ToastContext but never used in the component. Unused imports can lead to code bloat and confusion. Remove it if it's not needed or add a comment explaining its intended future use. [general, importance: 3]
| const { showInfoAlert } = useToastContext(); | |
| // Remove the unused function or add a comment explaining its future use | |
| // const { showInfoAlert } = useToastContext(); |
| useEffect(() => { | ||
| if (props.artwork && props.artwork.title) { | ||
| if (props.artwork?.title) { | ||
| setTitle({ value: props.artwork.title, validators: [] }); | ||
| } else if (props.artwork?.prompt?.prompt) { | ||
| const normalized = props.artwork.prompt.prompt | ||
| .replace(/[^a-zA-Z0-9 ]/g, " ") | ||
| .replace(/\s+/g, " ") | ||
| .trim() | ||
| .slice(0, 64); | ||
| setTitle({ value: normalized, validators: [] }); | ||
| } | ||
|
|
||
| if (props.artwork && props.artwork.collection_id) { | ||
| if (props.artwork?.collection_id) { | ||
| setSelectedCollectionId(props.artwork.collection_id); | ||
| } | ||
| }, [props.artwork]); |
There was a problem hiding this comment.
Suggestion: Add null/undefined checks for props.artwork before accessing its properties to prevent potential runtime errors. The current implementation only checks for specific properties but doesn't verify that the artwork object itself exists. [possible issue, importance: 7]
| useEffect(() => { | |
| if (props.artwork && props.artwork.title) { | |
| if (props.artwork?.title) { | |
| setTitle({ value: props.artwork.title, validators: [] }); | |
| } else if (props.artwork?.prompt?.prompt) { | |
| const normalized = props.artwork.prompt.prompt | |
| .replace(/[^a-zA-Z0-9 ]/g, " ") | |
| .replace(/\s+/g, " ") | |
| .trim() | |
| .slice(0, 64); | |
| setTitle({ value: normalized, validators: [] }); | |
| } | |
| if (props.artwork && props.artwork.collection_id) { | |
| if (props.artwork?.collection_id) { | |
| setSelectedCollectionId(props.artwork.collection_id); | |
| } | |
| }, [props.artwork]); | |
| useEffect(() => { | |
| if (!props.artwork) return; | |
| if (props.artwork.title) { | |
| setTitle({ value: props.artwork.title, validators: [] }); | |
| } else if (props.artwork?.prompt?.prompt) { | |
| const normalized = props.artwork.prompt.prompt | |
| .replace(/[^a-zA-Z0-9 ]/g, " ") | |
| .replace(/\s+/g, " ") | |
| .trim() | |
| .slice(0, 64); | |
| setTitle({ value: normalized, validators: [] }); | |
| } | |
| if (props.artwork.collection_id) { | |
| setSelectedCollectionId(props.artwork.collection_id); | |
| } | |
| }, [props.artwork]); |
| useEffect(() => { | ||
| if (models && models.length > 0) { | ||
| if (localSelectedModel && localSelectedModel !== selectedModel.name) { | ||
| const selected = models.find( | ||
| (m: Model) => m.name === localSelectedModel | ||
| ); | ||
| setSelectedModel(selected as Model); | ||
| } | ||
| if (!localSelected || localSelected === selectedModel?.name) return; | ||
| const model = models.find((m: Model) => m.name === localSelected); | ||
| if (model) { | ||
| setActiveLink({ model, feature: activeLink.feature }); | ||
| } | ||
| }, [localSelectedModel]); | ||
| }, [localSelected]); |
There was a problem hiding this comment.
Suggestion: Add missing dependencies to the useEffect dependency array. The current implementation is missing models, selectedModel, activeLink, and setActiveLink which could lead to stale closures and unexpected behavior. [possible issue, importance: 8]
| useEffect(() => { | |
| if (models && models.length > 0) { | |
| if (localSelectedModel && localSelectedModel !== selectedModel.name) { | |
| const selected = models.find( | |
| (m: Model) => m.name === localSelectedModel | |
| ); | |
| setSelectedModel(selected as Model); | |
| } | |
| if (!localSelected || localSelected === selectedModel?.name) return; | |
| const model = models.find((m: Model) => m.name === localSelected); | |
| if (model) { | |
| setActiveLink({ model, feature: activeLink.feature }); | |
| } | |
| }, [localSelectedModel]); | |
| }, [localSelected]); | |
| useEffect(() => { | |
| if (!localSelected || localSelected === selectedModel?.name) return; | |
| const model = models.find((m: Model) => m.name === localSelected); | |
| if (model) { | |
| setActiveLink({ model, feature: activeLink.feature }); | |
| } | |
| }, [localSelected, models, selectedModel, activeLink, setActiveLink]); |
| <div className="group overflow-hidden rounded-lg cursor-pointer relative hover:border hover:border-[#312E47]" style={initialStyles} onClick={handleClick}> | ||
| {props.artwork?.image ? ( | ||
| <AppImage | ||
| onClick={handleClick} | ||
| src={props.artwork.image} | ||
| alt={props.artwork.title} | ||
| /> |
There was a problem hiding this comment.
Suggestion: The onClick handler has been removed from the AppImage component but not added to its parent div. This could cause click events to only work on the div's margins but not on the image itself. Add the onClick={handleClick} to the AppImage component to ensure clicks work properly. [possible issue, importance: 5]
| <div className="group overflow-hidden rounded-lg cursor-pointer relative hover:border hover:border-[#312E47]" style={initialStyles} onClick={handleClick}> | |
| {props.artwork?.image ? ( | |
| <AppImage | |
| onClick={handleClick} | |
| src={props.artwork.image} | |
| alt={props.artwork.title} | |
| /> | |
| <div className="group overflow-hidden rounded-lg cursor-pointer relative hover:border hover:border-[#312E47]" style={initialStyles} onClick={handleClick}> | |
| {props.artwork?.image ? ( | |
| <AppImage | |
| src={props.artwork.image} | |
| alt={props.artwork.title} | |
| onClick={handleClick} | |
| /> |
| const Page400 = () => { | ||
| const router = useRouter(); |
There was a problem hiding this comment.
Suggestion: The component name Page400 doesn't match the HTTP status code for "Page Not Found" which should be 404. Rename the component to Page404 for consistency with the file name and HTTP status code. [general, importance: 8]
| const Page400 = () => { | |
| const router = useRouter(); |
| <div className="w-[46%] h-[100px] flex flex-row border border-[#312E47] rounded-lg cursor-pointer max-md:w-full max-md:mb-3" onClick={handleCollectionClick}> | ||
| <div className="w-[100px] h-[100px] object-cover rounded-2xl flex justify-center items-center"> | ||
| <img | ||
| src={props.collection.image || "/images/avatar.png"} | ||
| alt={"Collection Image"} | ||
| loading="lazy" | ||
| className="w-full h-full object-cover rounded-lg" | ||
| onError={(e) => { (e.target as HTMLImageElement).src = "/images/avatar.png"; }} | ||
| /> |
There was a problem hiding this comment.
Suggestion: There's a mismatch between the parent div's border radius (rounded-lg) and the image container's border radius (rounded-2xl). This creates an inconsistent UI where the image container has a different corner radius than its parent. Change the image container to use rounded-lg to match the parent. [general, importance: 6]
| <div className="w-[46%] h-[100px] flex flex-row border border-[#312E47] rounded-lg cursor-pointer max-md:w-full max-md:mb-3" onClick={handleCollectionClick}> | |
| <div className="w-[100px] h-[100px] object-cover rounded-2xl flex justify-center items-center"> | |
| <img | |
| src={props.collection.image || "/images/avatar.png"} | |
| alt={"Collection Image"} | |
| loading="lazy" | |
| className="w-full h-full object-cover rounded-lg" | |
| onError={(e) => { (e.target as HTMLImageElement).src = "/images/avatar.png"; }} | |
| /> | |
| <div className="w-[46%] h-[100px] flex flex-row border border-[#312E47] rounded-lg cursor-pointer max-md:w-full max-md:mb-3" onClick={handleCollectionClick}> | |
| <div className="w-[100px] h-[100px] object-cover rounded-lg flex justify-center items-center"> | |
| <img | |
| src={props.collection.image || "/images/avatar.png"} | |
| alt={"Collection Image"} | |
| loading="lazy" | |
| className="w-full h-full object-cover rounded-lg" | |
| onError={(e) => { (e.target as HTMLImageElement).src = "/images/avatar.png"; }} | |
| /> |
…draw package Remove the ~250-file vendored copy of Excalidraw from morpheus-client/excalidraw/ and replace it with the official @excalidraw/excalidraw npm package (^0.18.0). - Add ExcalidrawCanvas component that wraps the package's Excalidraw component, exports the canvas as a PNG blob, and sets it as the img2img input via context - Simplify Excalidraw.tsx to just lazy-load ExcalidrawCanvas with next/dynamic - Remove excalidraw/main.css import from _app.tsx (package ships its own styles) - Remove excalidraw/ glob from tailwind.config.js content paths Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- update settings for Firebase credentials and storage configuration - implement FirebaseClient for managing Firebase Storage operations - add FirebaseImagesRepository to handle file repository operations - extend file repository handlers to support Firebase storage
- pin accelerate to 0.34.2 to avoid circular import issue in 1.x - add pyopenssl>=23.0.0 for compatibility with cryptography>=40 - add google-cloud-storage and google-auth for Google Cloud support
- ignore common Python cache files and directories - add environment and secrets files to ignore list
…requests - update logging for all SD actors to exclude image, palette_image, and mask fields
- add generation timeout setting to prevent GPU hangs - implement actor eviction to free GPU memory for new models - switch storage client dynamically between S3 and Firebase - enhance logging by excluding sensitive fields and specifying timeout
- throw descriptive error when blob fetch fails (e.g., due to CORS issues)
- remove immutable and sass dependencies - update chokidar and source-map-js entries
- add table listing URLs for frontend, admin panel, API, Ray Dashboard, and PGAdmin
PR Type
Enhancement, Other
Description
Migrate SCSS/SASS to Tailwind CSS
Refactor ImagineMenu for better feature handling
Improve ImagineBase component structure
Normalize artwork titles from prompts
Simplify UI components and styling
Changes walkthrough 📝
8 files
Refactor feature rendering and improve UI handlingImprove avatar handling and convert to TailwindAdd error handling for images and convert to TailwindAdd topBar with ModelSelect and ImagineSettingsImprove model compatibility filtering and UIAdd title normalization from promptMove ImagineSettings to topBar and update stylingMove click handler to container div11 files
Convert SCSS styles to Tailwind classesReplace SCSS with Tailwind classesConvert page styling to Tailwind classesReplace SCSS module with Tailwind classesConvert component styling to TailwindConvert SCSS to Tailwind classesReplace SCSS with Tailwind classesConvert component styling to TailwindReplace SCSS with Tailwind classesConvert SCSS to Tailwind classesReplace SCSS with Tailwind classes101 files