What is the first thing you follow when you are learning something new—be it cooking or learning how to code? Tutorials, right?
You’re not alone—25.6% of internet users consume tutorials. Also, 57% of the content you see on the internet is generated by AI. Safe to say that you can accomplish tasks faster than ever with AI as an assistant.
In this tutorial, you will learn how to build an application that generates tutorials in Markdown format using DigitalOcean GenAI Platform and the latest Anthropic model support, capable of generating technical guides in Markdown format when you enter a topic. Here’s a demo of what you will be building:
DigitalOcean account - For this demo, you will be using DigitalOcean’s App Platform to host the application and the GenAI Platform to build the AI capabilities.
Working knowledge of React, TypeScript, TailwindCSS, and how to use APIs.
The GenAI Platform is an all-in-one solution that can help you build AI agents and use the APIs to create all kinds of applications. It supports models from DeepSeek, Anthropic, Meta, and Mistral AI, along with advanced features such as retrieval-augmented generation (RAG), function routing, agent customization, and guardrails to help you create more secure, context-aware applications.
The GenAI Platform supports multiple models that you can use to build and deploy AI-driven applications. Here is the complete list of models supported by the GenAI Platform. We recently launched Anthropic Claude 3.5 (Sonnet & Haiku), Claude 3 Opus, and DeepSeek R1-distill-llama-70B, which you can use to create any AI application you want.
Now that you know about the GenAI Platform, let’s see how you can use it to build a tutorial generator.
The application will follow a simple flow, and you will be using DigitalOcean’s App Platform to deploy the application and the GenAI Platform to add the AI capabilities.
Here’s how the application will look:
The application consists of two main parts: the history management and the UI.
Let’s start with understanding how you will build the main blocks:
First, let’s look at the state management for our tutorials.
You will use React’s useState hook to manage the list of tutorials and the currently selected tutorial:
interface Tutorial {
topic: string;
timestamp: string;
content?: string;
isLoading?: boolean;
error?: string;
}
function App() {
const [tutorials, setTutorials] = useState<Tutorial[]>([]);
const [selectedTutorial, setSelectedTutorial] = useState<Tutorial | null>(null);
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
}
This manages:
Next, you will understand how tutorial generation logic is handled. The handleSubmit
function is used to:
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!topic.trim()) return;
setIsLoading(true);
const newTutorial: Tutorial = {
topic,
timestamp: new Date().toLocaleString(),
isLoading: true
};
setTutorials([newTutorial, ...tutorials]);
try {
const content = await generateTutorial(topic);
const completedTutorial = { ...newTutorial, content, isLoading: false };
setTutorials(prevTutorials => [
completedTutorial,
...prevTutorials.slice(1)
]);
setSelectedTutorial(completedTutorial);
} catch (error) {
const failedTutorial = {
...newTutorial,
error: (error as Error).message,
isLoading: false
};
setTutorials(prevTutorials => [
failedTutorial,
...prevTutorials.slice(1)
]);
setSelectedTutorial(failedTutorial);
}
};
You want to be able to access our previously generated articles; for that, you will need to add a sidebar that shows the tutorial history using the code below:
{/* Sidebar Toggle Button */}
<button
onClick={() => setIsSidebarOpen(true)}
className="fixed top-4 left-4 p-2 bg-white rounded-lg shadow-md hover:bg-gray-50 z-50"
>
<Menu className="w-6 h-6 text-gray-700" />
</button>
{/* Sidebar Component */}
<div
className={`fixed inset-y-0 left-0 w-80 bg-white shadow-2xl transform transition-transform duration-300 ease-in-out z-50 ${
isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}
>
<div className="p-4">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold text-gray-900">Tutorial History</h2>
<button
onClick={() => setIsSidebarOpen(false)}
className="p-2 hover:bg-gray-100 rounded-lg"
>
<X className="w-5 h-5 text-gray-600" />
</button>
</div>
{/* Tutorial List */}
<div className="space-y-3">
{tutorials.map((tutorial, index) => (
<button
key={index}
onClick={() => {
setSelectedTutorial(tutorial);
setIsSidebarOpen(false);
}}
className={`w-full text-left p-3 rounded-lg transition-colors ${
selectedTutorial === tutorial
? 'bg-indigo-50 text-indigo-700'
: 'hover:bg-gray-50'
}`}
>
<h3 className="font-medium truncate">{tutorial.topic}</h3>
<p className="text-sm text-gray-500">{tutorial.timestamp}</p>
</button>
))}
</div>
</div>
</div>
For the final part, you want to have a content area that displays the selected tutorial using Markdown rendering.
This is how the code looks like:
{selectedTutorial ? (
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
<div className="flex items-start gap-4">
<div className="p-2 bg-indigo-100 rounded-lg">
<BookOpen className="w-6 h-6 text-indigo-600" />
</div>
<div className="flex-1">
<h3 className="text-xl font-semibold text-gray-900 mb-2">
{selectedTutorial.topic}
</h3>
<p className="text-sm text-gray-500">
Generated on {selectedTutorial.timestamp}
</p>
{selectedTutorial.isLoading ? (
<div className="mt-4 p-4 bg-gray-50 rounded-lg">
{/* Loading skeleton */}
<div className="h-4 bg-gray-200 rounded w-3/4 animate-pulse" />
<div className="h-4 bg-gray-200 rounded w-1/2 mt-2 animate-pulse" />
</div>
) : (
<div
className="mt-4 p-4 bg-gray-50 rounded-lg prose prose-indigo max-w-none"
dangerouslySetInnerHTML={{
__html: marked(selectedTutorial.content || '')
}}
/>
)}
</div>
</div>
</div>
) : (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<BookOpen className="w-12 h-12 mx-auto" />
</div>
<h3 className="text-lg font-medium text-gray-900">
No tutorial selected
</h3>
<p className="text-gray-500">
Generate a new tutorial or select one from the history
</p>
</div>
)}
Log in to your DigitalOcean account and navigate to the GenAI Platform.
Click on Create agent and give your agent a name.
The agent will help you create tutorials, so give it instructions similar to Your task is to generate a nice in-depth tutorial based on the input provided. Use simple language, and provide the article in markdown format
. You can choose any model to do the task. You will use Anthropic Claude 3.5 Sonnet for this example.
Note: To use Anthropic models, you will need an API key that you can generate for free here.
If you want, you can add a knowledge base. For this example, you can skip it and move forward with creating the agent.
Once the agent has been deployed, go to Playground to test and see if the agent worked as per your expectations.
If your agent needs improvement with the type of response, you can try updating the instruction and adjusting the temperature from the Settings tab, as shown below.
Now that the agent has been created and works as expected, we must integrate it with the front end. For that, follow the steps below:
Copy the endpoint URL and generate an endpoint access key, as shown in the images below.
Note: Please make sure to save the access key somewhere, as you will need it in the next steps.
Now, go back to the frontend code you wrote and push it to a GitHub repository (similar to what has been done in this Github repository). Then, go to the DigitalOcean App Platform and deploy the application.
This tutorial on Build and Deploy Apps on DigitalOcean App Platform with Custom Domain teaches you how to deploy an application on the app platform.
Once the application has been deployed, go to the Settings tab.
Then scroll down to the App-Level Environment Variables section and click on Edit to add the following variables:
AGENT_BASE_URL: <Your agent endpoint URL>
SECURE_AGENT_KEY: <The endpoint access key that you saved in the first step>
For the final step, you will integrate the OpenAI SDK into the application, we will use the code below:
import OpenAI from "openai";
const SECURE_AGENT_KEY = import.meta.env.VITE_SECURE_AGENT_KEY;
const AGENT_BASE_URL = import.meta.env.VITE_AGENT_BASE_URL;
const AGENT_ENDPOINT = `${AGENT_BASE_URL}/api/v1/`;
if (!SECURE_AGENT_KEY || !AGENT_BASE_URL) {
throw new Error("Missing agent access key or base URL! Ensure VITE_SECURE_AGENT_KEY and VITE_AGENT_BASE_URL are set in .env");
}
const client = new OpenAI({
apiKey: SECURE_AGENT_KEY,
baseURL: AGENT_ENDPOINT,
dangerouslyAllowBrowser: true
});
export async function generateTutorial(topic: string): Promise<string> {
try {
console.log("Starting tutorial generation for topic:", topic);
const prompt = `Your task is to generate a nice in-depth tutorial based on the input provided. Use simple language and provide the article in markdown format.\n\nTopic: ${topic}\n\nPlease create a comprehensive tutorial that includes:\n1. Introduction\n2. Prerequisites (if any)\n3. Step-by-step instructions\n4. Code examples (if applicable)\n5. Best practices\n6. Common pitfalls\n7. Conclusion`;
const response = await client.chat.completions.create({
model: "Anthropic Claude 3.5 Sonnet",
messages: [
{ role: "system", content: "You are an expert technical writer who creates clear, concise, and comprehensive tutorials." },
{ role: "user", content: prompt }
],
temperature: 0.7,
max_tokens: 2000
});
console.log("OpenAI response received:", response);
const content = response.choices[0]?.message?.content || "No response from AI";
return content;
} catch (error) {
console.error("Error generating tutorial:", error);
let errorMessage = "Unknown error occurred.";
if (error instanceof OpenAI.RateLimitError) {
errorMessage = "Rate limit exceeded. Please try again later.";
} else if (error instanceof Error) {
errorMessage = error.message;
}
throw new Error(`OpenAI SDK Error: ${errorMessage}`);
}
}
And that’s a wrap! You can now use this application to ideate or write your tutorials.
After you have followed all the steps, this is how your application will look like:
You can also try the deployed tutorial generator live app.
In this tutorial, you learned how to use the GenAI Platform to build AI-driven applications based on your use case and how to integrate it with third-party APIs.
In the meantime, if you want to learn more about the GenAI Platform, here are some helpful resources:
If you have any questions, you can contact us on Discord.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!