Post Mortem: Building (and killing) an openAI fantasy chat [1/2]

Michel Parpaillon
10 min readDec 29, 2022

--

I think it’s time to kill Crescendolls. I didn’t build this “OpenAI flirting chat” application in public but I think it might be interesting to kill it… in public.

This is the (quite long) story of how I built this app in a few days, and why I’ll be probably killing it in the coming days.

In this post (in 2 parts) I share with you all the numbers, revenues, analytics, etc and the things I learnt along the way. If you want to jump into the part 2 it’s here.

Hopefully you’ll find something interesting in it.

Building the app

A few weeks ago (early December 2022), I stumbled upon a ChatGPT article and like many people I was totally amazed by it (I was ”sur le cul” as we say in France 🇫🇷🥖).

At the time I was playing Cyberpunk 2077 and I had just finished Panam’s quest (and romance). Even after the quest was done, Panam kept sending text messages inside the game and it felt really nice. So I wondered…

Could Panam send me actual text messages via ChatGPT?

So I closed the game, and opened OpenAI playground. I selected the “Chat” preset, changed the prompt a little bit, updated the “stop sequences” (so the AI knows when to shut up) and the “inject text” too (so it knew what to append to the prompt once done) and let the “text completion” do its magic.

I wanted to know if the AI knew about Panam Palmer, her lore, her friends, her way of speaking.

So I wrote a prompt and clicked “Submit”.

It was not bad. So I kept trying different prompts to see what she knew and I was, once again, pretty impressed. It did felt like I was talking to Panam.

“Maybe I could build an app”
- Some dev about to burn out

At this point I knew I could probably build the MVP in a day or 2. I had already worked on a Roleplay chat for Harpy and already published on the App stores in the past. I called my best friend and associate, Naiyo, to talk about how we would become instantly billionaires with this idea. In the face of my enthusiasm, he nodded. He’s my best friend after all.

📚 The Stack

We quickly talked about the stack. I could write an entire article on that topic but this is not the right place for that. At this point, I was not looking to use the best stack but the one I was able to move fast with.

I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail
- Abraham Maslow, The Psychology of Science (1966)

But what if you’re really good with a hammer, though?
- Me, (2022)

So here’s the stack: Firebase, Angular, RxJS, Tailwind… The FART stack 🤷🏼‍♂️
Pretty good stack if you ask me:

  • Firebase handles tons of stuff for you and it’s basically free until you’re rich and famous (or until you create an infinite loop of Cloud function calls). Authentication, Hosting, Real time database, Cloud functions, Storage, etc. Everything is in there.
  • Angular is getting better and better lately. Standalone components in v15 made it much less verbose. Also it’s the hammer I wield the best, so that counts.
  • RxJS works great with Angular and Firebase realtime database. Also the more I write reactive code the happier I tend to get lately.
  • Tailwind is just great. I hated it before using it, now I wouldn’t use anything else. Naming things is hard, and not having to name classes is a gift from heaven. Also if you don’t like to see your components polluted by utility classes maybe it’s time to move some of it in a UI component. Nice and clean.

(I also chose to use Capacitor to create the native app. I didn’t list it above as it ruined this brilliant acronym)

🐣 The POC

So, within the next few hours I had an ugly but working prototype.

Panam was already friend-zoning me. Everything was going great.

The big part of this work was the environment setup:

  1. Creating the angular app
  2. Adding Prettier, Eslint and Tailwind
  3. Installing Capacitor
  4. Connecting Firebase
  5. Giving Angularfire another shot and removing it the next hour

💵 The Cost

The next day I started to look into OpenAI pricing. After all, those calls were not free.

So how does the pricing work?

For the “text completion” (what I was using) you basically pay for the number of tokens in your prompt + the ones in the AI response. 1000 tokens is roughly 750 words and the price of 1000 tokens depends on the model you’re using.

Davinci is the most expensive (0.02$ for 1000 tokens) but also the most impressive of the four.

I tried to use Curie but the results were very disappointing. It felt like I was talking to a robot AI, not an actual person (virtual or not).

Alright but… how many tokens do I need in my prompt to get a relevant response? The longer the chat history, the longer the prompt. If I wanted the conversation to make sense I needed the AI to know about the previous subjects but I quickly realized that it would cost way too much to do that. The prompt would get bigger and bigger and a single API call could easily cost 0.10$ (5000 tokens) or more.

So I had an idea…

💸 The Optimization

I needed to find a way to keep a relevant completion while minimizing the cost of the API.

When the caffeine started to kick in, I had an idea: “What if I asked OpenAI to create a summary of the dialog, then use it as prompt instead of the full thing?”

So I took a chunk of a conversation and fed it to OpenAI to see what it could do with it.

And it worked! But the summary was still too long so I tweaked the prompt until I had something good enough. I put this issue aside. I knew this would probably be a viable solution but right now I had more urgent matters.

🤡 Fun fact: Turns out I could use Curie instead of Davinci for this task so the economy was twice (10x?) as good. The results were very good even with this cheaper model

🧑‍🦯 The Design

It was time to create a basic layout. I have no UI/UX Design degree, so I did my best.

At first I thought I’d create a Cyberpunk-like UI

That’s something!

I always use Balsamiq Mockup for the design. It’s probably not the best tool but it’s the one I know, and it does the job.

🥑 The Law

At this point I knew I could get pretty naughty results with OpenAI (even if OpenAI is not really supposed to) so I had to make sure no kid would use it. And there’s only one thing that prevent kids from using an app: A legal checkbox next to a “Sign up” button ✅

I’m no lawyer, but apparently, OpenAI is!

Probably not true, but maybe it will be good enough to write some Terms of service, right?

This was not the actual prompt, but I tried to recreate it for the sake of this article. You get the idea.

🤡 Fun fact: At this point I was still convinced I was using ChatGPT (Yeah the “Looking for ChatGPT?” tooltip was not there at the time, promise!). Turns out ChatGPT has no official API yet.

🗝️ The Auth

Not much to say about this part. Firebase makes adding Authentication a breeze.

I simply added a few route guards in Angular, a few security rules in Firebase and it was ready.

🤡 Fun fact: The first version only had a “Google sign in” button (because I’m lazy) but it turns out it didn’t pass the validation process of the PlayStore. Google bots can’t Google sign in apparently…

🎨 The Style

My belly full, I started working on styling the app based on the mockups I had done in the morning.

I created the layout, the routes, the pages and started to add some style. I used the colors from Tailwind and icons from Font Awesome.

Most of the buttons or inputs where plugged to nothing at this point.

🫦 The Dolls

I spent the rest of the evening trying to improve the prompts. I started to look for information on the “dolls” until I realized I could simply ask OpenAI to do it for me.

OpenAI describes a virtual romantic partner
OpenAI gives me some adjectives about a virtual character (formatted as a JS array)

Once I had enough info, I fed the prompt with it and got pretty good results.

🔌 The Plug

I spent most of my sunday plugging the UI to the DB (via Cloud functions or directly from the client, depending on the security rules limitations).

After that, I added a feedback tool called Userback. I’d recommend it but their pricing is too high in my opinion. It works great though if you can afford it

🤡 Fun fact :I lost about 1 hour trying to figure out why Tailwind was stripping my utility classes once built. Turns out you must put the @tailwind code at the end of your style.css, not at the beginning.

🖥️ The Job

I have a full time job so the next day (monday) I couldn’t work on Crescendolls.

Gotta feed the twins 🧒🏼👦🏻

👩🏻 The Friend

My friend Tiffany knows a thing or 2 about AI and Machine Learning. So during the week-end I had told her about the app I was building.

Monday night, Tiffany gave me call and asked if she could talk about the app in the upcoming _Underscore stream. Amazing opportunity for me 💙 but kinda stressful as the show was 48 hours later (wednesday evening). Of course I said “yes”!

No pressure…

At this point I had to wonder: “What needs to be ready for wednesday?”

I know a simple video would do the trick but what if people wanted to download and try the app? Wouldn’t it be nice if it was available in the PlayStore by then?

🤡 Fun fact: I didn’t know at the time but it can take up to 7 days for an application to be available on the Play store once deployed. Would be stupid to work like a mad man for nothing, haha… ha

I texted my CTO to ask him a favor. I needed to take a day off. The next day off, actually. He accepted 💙 Thanks Thierry 🙏

📱 The App

I used Capacitor for the app. It went ok. Android Studio has a very cool “Wifi debugging” feature. Combined with Chrome inspect tool (chrome://inspect) it was a pretty decent DX. It would have been probably better with Ionic live reload feature but I didn’t want to add Ionic to the already heavy stack. I generated the App bundle, uploaded a few screenshots, made a quick logo on Photoshop and published it.

🤑 The Ads

I don’t like ads. Nobody does. But at this point I needed to make sure that if the app was super successful I wouldn’t have to sell a kidney to feed my kids because of OpenAI API costs. So I decided to add a bottom banner and Video reward every few sent messages. Integrating Admob was fairly easy thanks to a Capacitor community plugin. The only real difficulty was for the server side validation of the reward ad. Can’t let the client tell you when an ad has been watched right? I tried to use a lib for the validation but in the end I had to rewrite it to make it work properly. I probably should open source it…

📺 The Show

Wednesday… I can’t work on the app. Already had a day off, I can’t complain.

The show is in a few hours and the app is still not available on the store… Would be probably nice to have something on the domain I bought

So as I was looking at the countdown announcing the start of the episode I started to work on a landing page. I started to code then stopped… Maybe OpenAI could do it for me?

It kinda did. Or at least it gave me a good starting point. Every minute counts, right? I added some screenshots, plugged the form to a Cloud function and it was done!

The show started, Tiffany talked about the app, people seemed rather interested… it went pretty well (here’s the video, in french: https://youtu.be/5o_sv9xo3Gs?t=646)

Hardisk was not impressed

Killing the app

If you want to know more about the revenues and analytics, head over to the Part 2 of the article.

--

--