This is not a normal post. I’m going to walk you through the chat session I had with ChatGPT 41 to create the Ollama sample app. It’s going to be mostly a transcript of the session with some annotations.
I did not expect it to build the final “production” version of the app. Rather, I wanted to avoid all the monotony of building the UI and API interfaces so I could concentrate on the AI work. Of course there was some cleanup and tweaks to the code later on, but this general approach works really well and saves a ton of time.
So in interest of showing how I use ChatGPT 4 with no extra tooling to build applications, here’s a transcript of a chat session where I had ChatGPT write the code for me…
Me:
I want to build a flask, jinja2, and bootstrap application. The specifications are as follows:
There are three pages in the application: Home, Summarize, and Generate. There should be a menu bar at the top that allows jumping between the pages.
The home page will just have explanatory text. I will edit the page with the text later.
The Summarize page is designed to allow the user to summarize the contents of a document.
The page should be split vertically into two sections.
The left side of the page will be about 2/3rds of the width of the page and contain the input and output areas.
The right side of the page will be the remainder of the page and will hold buttons and miscellaneous controls.
For the left side, it will have a text area at the top that holds the input and an EasyMDE Markdown control that displays the output. The two areas should each be roughly 50% of the page. The textarea should expand vertically to fill its space even if there's no text in it. If there is more text in either control they should scroll.
The right hand size of the page will have the following buttons, stacked vertically:
Summarize, which calls a function on the server that will summarize the contents of the text area and send the results to the output area.
Reset, which clears the input and output areas after verifying, via a simple pop-up, that the user wants to clear the content.
Below the buttons, on the right hand size, should be a radio button group which describes the length of summarization desired: short, medium, or long.
The generate page has the same general layout as the Summarize page.
The left hand side will have the same two types of controls (textarea and EasyMDE) with the same dimensions and styling.
The right hand side will have a set of controls stacked vertically:
Message type: a Dropdown choice between `email` and `slack`
A first name and last name field for the recipient
A company name field, which is optional, for the company of the recipient.
Style: a dropdown choice with `friendly`, `business causual`, and `formal` choices
A first name and last name field for the sender
A company name field for the sender
An email address field for the sender
A phone number field for the sender.
A button labeled `Generate`, which sends the input and all the fields on the right-hand side to the server for processing.
A button labeld `Reset`, which clears all the fields after a browser pop-up verifies that's what they want to do.
Note the following:
The application is a demonstration program, and so it does not need to be concerned about security.
It will run locally on the demonstrator's laptop, so there are no networking issues.
For the server code, just create stubs and I will fill out the code.
I will ask you to write the JS code to do everything in this specification, including the EasyMDE integration.
Just plain styling using bootstrap defaults.
No special server setup.
All bootstrap and EasyMDE assets will be pulled from CDNs.
Is there anything else you need to know or want to ask before we build this application?
Commentary on this: This is a huge prompt, and there’s a lot of weird specificity in it. I’ve developed a lot of boilerplate over time, so I know e.g. that ChatGPT gets fussy about security unless I tell it I have it covered. I also had a pretty firm idea about what components I wanted to use (I use EasyMDE for a rich text editor that supports markdown, as LLMs love to send and receive Markdown).
Note that rather than asking it to just start, I ask it if there’s anything else that’s needed. I don’t want ChatGPT making bad assumptions. And if I discover something was missing from my initial prompt, I add it to my boilerplate for the next time!
You might notice that the first prompt is huge, but the rest of my inputs are single line, just asking it to take the next step. I’d love to have a one-and-done prompt, but ChatGPT cannot always generate the length of output I’d need and so I have to string it along a bit.
ChatGPT:
It looks like we have a clear plan for building your application! I'm ready to help you get started with the setup and initial coding for your Flask, Jinja2, and Bootstrap application. Let's begin by setting up the basic Flask structure, including the initial routes and templates for the Home, Summarize, and Generate pages. I'll provide you with the basic HTML structure for these pages using Bootstrap for the layout and navigation.
Shall we start with the Flask setup and the basic routing?
Me:
Yes, please write that.
Commentary: It’s unusual to get ChatGPT to spit out all of the code all at once, so you have to tease it out of it in pieces.
ChatGPT:
Let's begin by setting up your Flask application. Here are the steps you'll follow, including the initial project structure, Flask setup, and the basic routing for the Home, Summarize, and Generate pages.
Project Structure
Here’s how you can structure your project:
/your-application
|-- /static
| |-- /css
| |-- /js
|-- /templates
| |-- home.html
| |-- summarize.html
| |-- generate.html
|-- app.py
Step 1: Setup Flask
First, ensure you have Flask installed. You can install Flask using pip if you haven't already:
pip install Flask
Step 2: Create app.py
This will be your main Flask application file. Here’s a basic setup:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return render_template('home.html')
@app.route('/summarize')
def summarize():
return render_template('summarize.html')
@app.route('/generate')
def generate():
return render_template('generate.html')
if __name__ == '__main__':
app.run(debug=True)
Step 3: Create HTML Templates
Now, let's create the HTML templates with basic Bootstrap navigation.
home.html
, summarize.html
, and generate.html
For simplicity, I’ll provide the structure for home.html
. You can use a similar structure for the other pages, modifying the content as necessary.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Home</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">MyApp</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/summarize">Summarize</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/generate">Generate</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<h1>Welcome to MyApp</h1>
<p>This is the home page. Use the navigation to visit other pages.</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
This template uses Bootstrap 5 from a CDN for styling and responsiveness. You should replicate and adapt this template for summarize.html
and generate.html
, adding specific content and controls as needed for each page.
With this setup, your Flask app will be able to render three pages with basic navigation. Let me know if you'd like to proceed with more detailed implementations for the Summarize and Generate pages!
Me:
Please write the summarize.html page for me.
Commentary: I got a lot up front, but now it’s time to ask it for the missing bits…
ChatGPT:
Let's create the summarize.html
template with the layout and functionality you described. This page will be split vertically into two sections with the left side holding input and output areas, and the right side containing buttons and controls for summarization options.
Here’s the HTML code for summarize.html
incorporating Bootstrap for layout and some inline styles for sizing:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css" rel="stylesheet">
<title>Summarize Document</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">MyApp</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/summarize">Summarize</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/generate">Generate</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-3">
<div class="row">
<div class="col-md-8">
<textarea id="inputText" class="form-control" style="height: 50%; resize: none;" placeholder="Enter text here to summarize..."></textarea>
<div id="outputMarkdown" style="height: 50%; overflow-y: scroll;"></div>
</div>
<div class="col-md-4">
<button class="btn btn-primary mb-2" onclick="summarizeText()">Summarize</button>
<button class="btn btn-secondary mb-2" onclick="resetText()">Reset</button>
<div class="mb-3">
<label class="form-label">Summarization Length:</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="summarizeLength" id="short" value="short" checked>
<label class="form-check-label" for="short">Short</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="summarizeLength" id="medium" value="medium">
<label class="form-check-label" for="medium">Medium</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="summarizeLength" id="long" value="long">
<label class="form-check-label" for="long">Long</label>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<script>
var easyMDE = new EasyMDE({element: document.getElementById('outputMarkdown')});
function summarizeText() {
var inputText = document.getElementById('inputText').value;
var summarizeLength = document.querySelector('input[name="summarizeLength"]:checked').value;
// Call to server to process summarization here (stubbed)
easyMDE.value('Summarized text goes here...'); // Update this part with actual server response
}
function resetText() {
if (confirm('Are you sure you want to clear all text?')) {
document.getElementById('inputText').value = '';
easyMDE.value('');
}
}
</script>
</body>
</html>
This template includes:
A text area for input and an EasyMDE Markdown editor for output.
Buttons for "Summarize" and "Reset" functionalities.
Radio buttons to select the summarization length.
Please note that the JavaScript functions (summarizeText
and resetText
) are placeholders. You will need to implement the actual server calls and logic for summarization. The summarizeText
function needs to be connected to your Flask backend, which you mentioned you'd be handling.
Let me know
Commentary: I think its answer ran too long so it was cut off. No matter…
Me:
Please write the javascript to call the server. Assume the endpoint is /api/summarize
Commentary: It told me what it didn’t do, so now I’m telling it to do those things.
ChatGPT:
o integrate the JavaScript needed to call the server endpoint /api/summarize
for the summarization functionality on your Flask application, you will need to modify the summarizeText
function to make an AJAX request. I'll demonstrate how you can do this using the fetch
API, which is a modern way to make HTTP requests in JavaScript.
Here’s how you can update the JavaScript in your summarize.html
to include the AJAX call:
function summarizeText() {
var inputText = document.getElementById('inputText').value;
var summarizeLength = document.querySelector('input[name="summarizeLength"]:checked').value;
// Define the request payload
var data = {
text: inputText,
length: summarizeLength
};
// Make a POST request to the server
fetch('/api/summarize', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json()) // Convert response to JSON
.then(data => {
// Update the EasyMDE editor with the summarized text from the server
easyMDE.value(data.summary);
})
.catch((error) => {
console.error('Error:', error);
});
}
Explanation:
Fetch API: The
fetch
function is used to make the AJAX call. It's configured to send aPOST
request to/api/summarize
.Headers: The request includes headers that specify the content type as JSON.
Body: The body of the request is a JSON string created from an object that contains the text to be summarized and the desired summarization length.
Response Handling: The response from the server is expected to be in JSON format. It’s converted to a JavaScript object, and then the
summary
field from this object is used to update the text in the EasyMDE editor.Error Handling: In case of an error (e.g., network issues, server errors), it logs an error message to the console.
Server-side Implementation:
Ensure your server-side code can handle this endpoint (/api/summarize
). It should accept JSON data, process it, and return a JSON object that includes a summary
field with the summarized text.
Here’s a simple example of what the Flask endpoint could look like:
from flask import Flask, request, jsonify
@app.route('/api/summarize', methods=['POST'])
def api_summarize():
data = request.get_json()
text = data['text']
length = data['length']
# Your summarization logic here
summarized_text = "This is a stubbed response." # Replace with actual summarization result
return jsonify(summary=summarized_text)
This setup ensures your Summarize page interacts with the Flask backend via AJAX, making your application more dynamic and responsive. If you need further details on how to handle the server-side processing or have any more questions, let me know!
Me:
Please write the generate.html page. Assume the generate button calls /api/generate. Write the javascript that calls that as part of the page.
Commentary: OK, so I have to tell it to write the JavaScript too.
ChatGPT:
Here's the generate.html
template for your Flask application, which includes the layout and controls for generating messages based on user inputs, and the JavaScript needed to call the server endpoint /api/generate
.
generate.html
This page will have a similar structure to your summarize.html
but includes additional form fields to capture various attributes of a message request.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css" rel="stylesheet">
<title>Generate Message</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="/">MyApp</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/summarize">Summarize</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/generate">Generate</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-3">
<div class="row">
<div class="col-md-8">
<textarea id="inputText" class="form-control" style="height: 50%; resize: none;" placeholder="Enter additional context here..."></textarea>
<div id="outputMarkdown" style="height: 50%; overflow-y: scroll;"></div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="messageType" class="form-label">Message Type:</label>
<select id="messageType" class="form-select">
<option value="email">Email</option>
<option value="slack">Slack</option>
</select>
</div>
<!-- Additional fields for recipient -->
<div class="mb-3">
<label for="recipientFirstName" class="form-label">Recipient First Name:</label>
<input type="text" class="form-control" id="recipientFirstName" placeholder="First Name">
</div>
<div class="mb-3">
<label for="recipientLastName" class="form-label">Recipient Last Name:</label>
<input type="text" class="form-control" id="recipientLastName" placeholder="Last Name">
</div>
<div class="mb-3">
<label for="recipientCompany" class="form-label">Recipient Company (optional):</label>
<input type="text" class="form-control" id="recipientCompany" placeholder="Company Name">
</div>
<!-- Style dropdown -->
<div class="mb-3">
<label for="messageStyle" class="form-label">Style:</label>
<select id="messageStyle" class="form-select">
<option value="friendly">Friendly</option>
<option value="business_casual">Business Casual</option>
<option value="formal">Formal</option>
</select>
</div>
<!-- Additional fields for sender -->
<div class="mb-3">
<label for="senderFirstName" class="form-label">Sender First Name:</label>
<input type="text" class="form-control" id="senderFirstName" placeholder="First Name">
</div>
<div class="mb-3">
<label for="senderLastName" class="form-label">Sender Last Name:</label>
<input type="text" class="form-control" id="senderLastName" placeholder="Last Name">
</div>
<div class="mb-3">
<label for="senderCompany" class="form-label">Sender Company:</label>
<input type="text" class="form-control" id="senderCompany" placeholder="Company Name">
</div>
<div class="mb-3">
<label for="senderEmail" class="form-label">Sender Email:</label>
<input type="email" class="form-control" id="senderEmail" placeholder="Email Address">
</div>
<div class="mb-3">
<label for="senderPhone" class="form-label">Sender Phone Number:</label>
<input type="tel" class="form-control" id="senderPhone" placeholder="Phone Number">
</div>
<button class="btn btn-primary mb-2" onclick="generateMessage()">Generate</button>
<button class="btn btn-secondary" onclick="resetFields()">Reset</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<script>
var easyMDE = new EasyMDE({element: document.getElementById('outputMarkdown')});
function generateMessage() {
var inputText = document.getElementById('inputText').value;
var data = {
messageType: document.getElementById('messageType').value,
recipientFirstName: document.getElementById('recipientFirstName').value,
recipientLastName: document.getElementById('recipientLastName').value,
recipientCompany: document.getElementById('recipientCompany').value,
messageStyle: document.getElementById('messageStyle').value,
senderFirstName: document.getElementById('senderFirstName').value,
senderLastName: document.getElementById('senderLastName').value,
senderCompany: document.getElementById('senderCompany').value,
senderEmail: document.getElementById('senderEmail').value,
senderPhone: document.getElementById('senderPhone').value,
additionalContext: inputText
};
fetch('/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
easyMDE.value(data.generatedMessage);
})
.catch((error) => {
console.error('Error:', error);
});
}
function resetFields() {
if (confirm('Are you sure you want to clear all fields?')) {
document.querySelectorAll('.form-control').forEach(input => input.value = '');
easyMDE.value('');
}
}
</script>
</body>
</html>
Explanation:
HTML Form: This includes inputs for both the sender and recipient details, along with the type and style of the message. The form is designed to capture all necessary information for message generation.
JavaScript Functions:
generateMessage()
: Collects all the form data, creates a JSON object, and sends a POST request to/api/generate
. The response (expected to contain a generated message) is then displayed in the EasyMDE editor.resetFields()
: Resets all input fields and the markdown editor after confirming with the user.
This template is fully functional for the UI part, and you'll need to ensure that your Flask backend is set up to handle the /api/generate
endpoint properly, processing the input data and returning a suitable response. If you have any more adjustments or additions you'd like to see, let me know!
Me:
Now please write the server code for /api/generate with a stub for actual processing.
ChatGPT:
Below is a simple Flask server-side implementation for handling the /api/generate
endpoint. This code includes a stub function where you can integrate the logic for generating messages based on the details provided through the form in your generate.html
.
Flask Server Code for /api/generate
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/generate', methods=['POST'])
def generate_message():
# Extract data from POST request
data = request.get_json()
message_type = data.get('messageType')
recipient_first_name = data.get('recipientFirstName')
recipient_last_name = data.get('recipientLastName')
recipient_company = data.get('recipientCompany')
message_style = data.get('messageStyle')
sender_first_name = data.get('senderFirstName')
sender_last_name = data.get('senderLastName')
sender_company = data.get('senderCompany')
sender_email = data.get('senderEmail')
sender_phone = data.get('senderPhone')
additional_context = data.get('additionalContext')
# Here you would implement your message generation logic
# For now, let's just return a simple generated message based on the provided data
generated_message = f"Hello {recipient_first_name}, this is a message from {sender_first_name} at {sender_company}."
# Send back the generated message in a JSON response
return jsonify(generatedMessage=generated_message)
if __name__ == '__main__':
app.run(debug=True)
Explanation
POST Request Handling: The server receives JSON data via a POST request, which it parses to extract message parameters.
Message Generation Stub: The function
generate_message
currently just constructs a very simple message string. You should replace this with whatever logic is required to construct the message according to the message type and style.JSON Response: The response is a JSON object containing the
generatedMessage
, which is then displayed on the client side using the EasyMDE editor.
Integration Points
For a real-world application, you would need to integrate actual message composition logic which might involve templates, natural language processing, or other dynamic content generation techniques depending on the complexity and requirements of your application.
Running the Server
To run this server, ensure that Flask is installed and your app structure is correct, and then run the Python script. Navigate to your generate.html
in a browser to test the full functionality.
If you need further customization or additional features, feel free to let me know!
Commentary: At this point I had the complete application, minus the parts that call out to Ollama which I wrote by hand. But with 6 questions to ChatGPT 4 I was able to get the UI more or less built and running without having to write the code myself. There was, of course, some fit and finish work I did by hand. But by carefully specifying my requirements, I was able to coax it into building a first draft of the app.
I used ChatGPT 4 for building the app mostly because I pay for the service so I might as well use the better. But a quick test with the free ChatGPT 3.5 reveals it does just fine at the task as well, so I’d say ChatGPT 4 is not a must-have.