Skip to main content

Create an AI Content Generation SaaS Like Copy.ai

· 12 min read
Ulric Musset
Co-founder

Copy.ai is an AI-powered tool that helps users generate marketing copy adjusted to specific brand voice and tone. Under the hood, Copy.ai uses AI to generate the content based on the analysis of brand voice, tone and user input.

In this guide, you will learn how to recreate the content generation functionality of Copy.ai using Marblism - a platform that takes an input and turns it into a fully functioning Next.js application.

Prerequisites

To get started, you will need:

Getting Started

Navigate to your Marblism account. If you don't have any projects, you will be prompted to create one. Otherwise, press New Project on the left sidebar.

Creating a New Project

You will have to populate the following fields:

  • Project Name
  • Project Type
  • Project Description
  • Modules
  • Theme

Enter the project name as "Copy.ai", and select the project type as SaaS. The project description should concisely and directly describe project's functionality, relations and such.

An example of a good project description we're using for this Copy.ai clone:

Project Description

Copy.ai is a SaaS product where users can:

  • Create organizations
  • Create products in organizations with a title and description
  • For each product users can generate a Twitter post, a LinkedIn post, and a blog article using OpenAI's ChatGPT

Marblism's Project Creation Wizard

An example of a project description that wouldn't get us the desired result would look something like this:

❌ Copy.ai is a SaaS product where users can generate posts based on the products they have in their organizations.

As shown in the example above, the description (the actual prompt) should be broken down, so that Marblism can understand and interconnect the puzzle pieces together, creating the desired outcome.

Leave the selected Landing Page module, then choose a Dark or Light theme.

Now, press Next and move to the Description step.

Page Description

Marblism will auto-generate pages, their paths and user stories.

note

Each project will generate a different set of pages and user stories.

User stories, as in general software development process, are generalized explanations of a feature, written from the perspective of an end user.

Marblism will convert those user stories into direct features in the built application.

As you'll see below, you can specify scenarios, that specify how actions should behave when an user initiates them.

Preview of Home and Organizaation List Pages in Marblism

Examples of Page Descriptions

You can modify the auto-generated output with the examples we're using.

Home Page
/home

- As a user, I can view a list of my organizations so that I can select one to manage.

- Scenario: Viewing the list of organizations. Given I am logged in, when I navigate to the organizations page, then I can see a list of my organizations.

After you're satisfied with the descriptions and user stories, press Next. Marblism will now generate a Data model.

Data Model

In this Marblism will generate a Data model, that corresponds to a Prisma database schema.

It will contain all the necessary fields in the database, as well as the relations betwen the tables.

note

Each generation will likely generate a different data model, so changes might be necessary.

An example we're using for the tables:

Generated User Table Shown in Marblism

You can press Change something and instruct Marblism with a prompt the changes you want. For example, you might want to have different tables, or different fields in the existing tables.

Press Next and Marblism will start generating the codebase.

Customizing the Project

After generating the project, you will be presented with the Overview section.

Optionally, you can change the Zone on the right-hand side to Europe, Asia or America

Overview Section of the Project Shown in Marblism

From here, press Launch Workspace and then Go to Workspace. You will be redirected to the Workspace platform. Wait for the repository to start.

Marblism Workspace

Changing or Adding Features

First, let's login to the app by pressing Get Started in the right-hand corner of the Preview screen.

note

Your app's user interface (UI) might look slightly different, but the essence should be the same.

App's Login Form Shown in Preview

Marblism will auto-populate test environment credentials.

See What's Missing

By testing the app, in this video, we can see a few things we're missing:

  • A nice loader that tells the user: Hey, the app didn't crash, it just takes a few seconds to generate the post!
  • A nicely formatted markdown instead of plain text.

Adding a Loader

Let's fix this by first creating a loader. On the left-hande side of the Workspace is the Michelangelo chat.

With it you can code, update theme, install libraries, ask questions, and even fix code issues.

Now, while we're on the product page (in this case /src/app/(authenticated)/organizations/[organizationId]/products/[productId]/page.tsx, but this will probably differ for you), select the Code option from the Michelangelo chat.

In Describe the changes field, enter the following prompt:

While the post is generating, show a nice loader on top of everything, with blurred background.

Press Next and Michelangelo will first tell you what it's planning to do, before commiting to generating any new code.

Code Prompting to Michelangelo Showing Proposed Changes

After accepting proposed changes, you can now see that it added a loader with the blurred background.

This is the code snippet of what Michelangelo created behind the scenes:

/src/designSystem/ui/LoaderWithBlur/index.tsx
import React from 'react'
import { Spin } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import styled from 'styled-components'

const LoaderContainer = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
`

const BlurBackground = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
`

export const LoaderWithBlur: React.FC = () => {
return (
<LoaderContainer>
<BlurBackground />
<Spin indicator={<LoadingOutlined spin />} size="large" />
</LoaderContainer>
)
}

Processing Markdown

Now, let's format that generated markdown into something nice looking.

Choose Install a library from the Michelangelo dropdown. Enter the following library name:

react-markdown

Michelangelo will prompt you this:

pnpm add react-markdown
pnpm install

Press Yes to continue with the installation and Save the changes.

Now, with the Code option from Michelangelo, send it the following prompt:

Using the react-markdown library, process the generated text from the post and format it using the previously mentioned library, and show that in a modal.

Check the proposed solution and press Continue. Proposed Code Changes

Let's take a look on the actual code changes.

  1. Michelangelo created a new file:

    /src/designSystem/ui/MarkdownModal/index.tsx
    import React from 'react'
    import { Modal } from 'antd'
    import ReactMarkdown from 'react-markdown'

    type MarkdownModalProps = {
    visible: boolean
    content: string
    onClose: () => void
    }

    export const MarkdownModal: React.FC<MarkdownModalProps> = ({
    visible,
    content,
    onClose,
    }) => {
    return (
    <Modal visible={visible} onCancel={onClose} footer={null}>
    <ReactMarkdown>{content}</ReactMarkdown>
    </Modal>
    )
    }
  2. Michelangelo edited a rendering component:

    /src/app/(authenticated)/organizations/[organizationId]/products/[productId]/page.tsx
    'use client'

    import { Prisma } from '@prisma/client'
    import { Button, Col, Row, Spin, Typography } from 'antd'
    import {
    TwitterOutlined,
    LinkedinOutlined,
    FileTextOutlined,
    } from '@ant-design/icons'
    import { useEffect, useState } from 'react'
    const { Title, Paragraph } = Typography
    import { useUserContext } from '@/core/context'
    import { useRouter, useParams } from 'next/navigation'
    import { useUploadPublic } from '@/core/hooks/upload'
    import { useSnackbar } from 'notistack'
    import dayjs from 'dayjs'
    import { Api } from '@/core/trpc'
    import { PageLayout } from '@/designSystem/layouts/Page.layout'
    import { MarkdownModal } from '@/designSystem/ui/MarkdownModal'

    export default function ProductPage() {
    const router = useRouter()
    const params = useParams<any>()
    const { user } = useUserContext()
    const { enqueueSnackbar } = useSnackbar()

    const productId = params.productId
    const {
    data: product,
    isLoading,
    refetch,
    } = Api.product.findUnique.useQuery({
    where: { id: productId },
    include: { organization: true },
    })

    const generateText = Api.ai.generateText.useMutation()

    const [twitterPost, setTwitterPost] = useState<string | null>(null)
    const [linkedinPost, setLinkedinPost] = useState<string | null>(null)
    const [blogArticle, setBlogArticle] = useState<string | null>(null)
    const [isModalVisible, setIsModalVisible] = useState(false)
    const [modalContent, setModalContent] = useState<string>('')

    const handleGeneratePost = async (type: 'Twitter' | 'LinkedIn' | 'Blog') => {
    if (!product) return

    let prompt = ''
    switch (type) {
    case 'Twitter':
    prompt = `Generate a Twitter post for the product titled "${product.title}" with the description "${product.description}"`
    break
    case 'LinkedIn':
    prompt = `Generate a LinkedIn post for the product titled "${product.title}" with the description "${product.description}"`
    break
    case 'Blog':
    prompt = `Generate a blog article for the product titled "${product.title}" with the description "${product.description}"`
    break
    }

    try {
    const response = await generateText.mutateAsync({ prompt })
    setModalContent(response.answer)
    setIsModalVisible(true)
    switch (type) {
    case 'Twitter':
    setTwitterPost(response.answer)
    enqueueSnackbar('Twitter post generated successfully!', {
    variant: 'success',
    })
    break
    case 'LinkedIn':
    setLinkedinPost(response.answer)
    enqueueSnackbar('LinkedIn post generated successfully!', {
    variant: 'success',
    })
    break
    case 'Blog':
    setBlogArticle(response.answer)
    enqueueSnackbar('Blog article generated successfully!', {
    variant: 'success',
    })
    break
    }
    } catch (error) {
    enqueueSnackbar('Failed to generate post. Please try again.', {
    variant: 'error',
    })
    }
    }

    if (isLoading) {
    return (
    <PageLayout layout="full-width">
    <Spin size="large" />
    </PageLayout>
    )
    }

    if (!product) {
    return (
    <PageLayout layout="full-width">
    <Title level={2}>Product not found</Title>
    </PageLayout>
    )
    }

    return (
    <PageLayout layout="full-width">
    <Row
    justify="center"
    align="middle"
    style={{ textAlign: 'center', padding: '20px' }}
    >
    <Col span={24}>
    <Title level={2}>{product.title}</Title>
    <Paragraph>{product.description}</Paragraph>
    </Col>
    <Col span={24} style={{ marginTop: '20px' }}>
    <Button
    type="primary"
    icon={<TwitterOutlined />}
    onClick={() => handleGeneratePost('Twitter')}
    style={{ margin: '0 10px' }}
    >
    Generate Twitter Post
    </Button>
    <Button
    type="primary"
    icon={<LinkedinOutlined />}
    onClick={() => handleGeneratePost('LinkedIn')}
    style={{ margin: '0 10px' }}
    >
    Generate LinkedIn Post
    </Button>
    <Button
    type="primary"
    icon={<FileTextOutlined />}
    onClick={() => handleGeneratePost('Blog')}
    style={{ margin: '0 10px' }}
    >
    Generate Blog Article
    </Button>
    </Col>
    <MarkdownModal
    visible={isModalVisible}
    content={modalContent}
    onClose={() => setIsModalVisible(false)}
    />
    </Row>
    </PageLayout>
    )
    }

This showcases the ability of changing the code, without disrupting previous functionality.

Technical Considerations

Marblism generates Next.js and NestJS code out-of-the-box, meaning you get a modern, modular, production-ready codebase.

Technical intricacies you might be interested in:

Next Steps & Resources

Congratulations! In this guide you've learned how to recreate Copy.ai on Marblism, in almost no time! This guide should serve as a base for further playtime on Marblism.

See what other's have built with Marblism.

Feeling like you're out of ideas? Check out MuckBrass.com to find and validate your startup ideas.