Build a beautiful AI chat app using Nowa and ChatGPT under an hour without code

Hey everyone!

We just released a detailed tutorial on building an AI chat assitant app using Nowa and ChatGPT API in under an hour :wink:

You will also understand how eveything works together in Nowa (How variables, UI and Circuit all works together to enable you build powerful and customized app)

If you need any help with this usecase please let me know :slight_smile: I will be happy to clear any confusion.

Let’s start building :fire:

1 Like

I was able to create the app and it works pefectly in the designer, but when building for android, the actual debug apk on the device only shows the initial sent message, accesses the openai, but never shows the assistant response bubble.

Could i send the project source to share for you to take a look?

I could only get the app to build from Nowa. Using

Flutter build apk --debug

From a windows command line

Renders errors about MessageBubble.dart.

Hey @mcombatti Yes sure, please send me the projectID and I will take a look at it.

It’s on my desktop system, not the cloud. The cloud version was having issues compiling, so I remade the app locally to try to debug the issue.
I tried changing the Nowa code

class _ChatScreenState extends State<ChatScreen> {
  List<ChatMessageObject?>? messageList = [];

  TextEditingController TextController = TextEditingController();

  bool? isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          color: const Color(4294967295),
          borderRadius: BorderRadius.circular(0.0),
          image: DecorationImage(
              fit: BoxFit.cover, image: const AssetImage('assets/foodbg.png')),
        ),
        child: Padding(
          padding: const EdgeInsets.only(
            left: 20.0,
            right: 20.0,
            top: 10.0,
            bottom: 10.0,
          ),
          child: SafeArea(
            child: NFlex(
              direction: Axis.vertical,
              spacing: 10.0,
              children: [
                FlexSizedBox(
                  width: double.infinity,
                  flex: 1,
                  child: ListView(
                    children: messageList!
                        .map((element) => MessageBubble(
                              chatMessageParam: element,
                            ))
                        .toList(),
                    reverse: true,
                  ),
                ),
                FlexSizedBox(
                  width: double.infinity,
                  height: 60.0,
                  child: NFlex(
                    direction: Axis.horizontal,
                    spacing: 10.0,
                    children: [
                      Expanded(
                        child: TextField(
                          controller: TextController,
                          decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'Enter your message',
                          ),
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          if (TextController.text.isNotEmpty) {
                            setState(() {
                              isLoading = true;
                              messageList?.insert(
                                  0,
                                  ChatMessageObject(
                                      message: TextController.text,
                                      isUserSender: true));
                            });

                            OpenaiApi()
                                .sendMessage(message: TextController.text)
                                .then((value) {
                              messageList?.insert(
                                  0,
                                  ChatMessageObject(
                                      message:
                                          value.choices![0]?.message?.content,
                                      isUserSender: false));
                              isLoading = false;
                              setState(() {});
                            }, onError: (error) {
                              print('Error occurred: ${error}');
                            });
                            TextController.clear();
                            setState(() {});
                          }
                        },
                        child: Text(
                          createText(),
                          style: const TextStyle(color: Color(4294967295)),
                        ),
                        color: const Color(4280459876),
                      ),
                    ],
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.end,
                  ),
                )
              ],
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.start,
            ),
          ),
        ),
      ),
      appBar: AppBar(
        title: Text(
          '🧑‍🍳 Ask me for a recipe 🥙',
          style: const TextStyle(
              color: Color(4294967295), fontFamily: 'ADLaM Display'),
          textAlign: TextAlign.center,
        ),
        centerTitle: true,
        backgroundColor: const Color(4284922730),
      ),
    );
  }

  String createText() {
    if (isLoading!) {
      return '...';
    } else {
      return 'Send';
    }
  }
}

to

class _ChatScreenState extends State<ChatScreen> {
  List<ChatMessageObject?>? messageList = [];

  TextEditingController TextController = TextEditingController();

  bool? isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          color: const Color(4294967295),
          borderRadius: BorderRadius.circular(0.0),
          image: DecorationImage(
              fit: BoxFit.cover, image: const AssetImage('assets/foodbg.png')),
        ),
        child: Padding(
          padding: const EdgeInsets.only(
            left: 20.0,
            right: 20.0,
            top: 10.0,
            bottom: 10.0,
          ),
          child: SafeArea(
            child: NFlex(
              direction: Axis.vertical,
              spacing: 10.0,
              children: [
                FlexSizedBox(
                  width: double.infinity,
                  flex: 1,
                  child: ListView(
                    children: messageList!
                        .map((element) => MessageBubble(
                              chatMessageParam: element,
                            ))
                        .toList(),
                    reverse: true,
                  ),
                ),
                FlexSizedBox(
                  width: double.infinity,
                  height: 60.0,
                  child: NFlex(
                    direction: Axis.horizontal,
                    spacing: 10.0,
                    children: [
                      Expanded(
                        child: TextField(
                          controller: TextController,
                          decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'Enter your message',
                          ),
                        ),
                      ),
                      ElevatedButton(
                        onPressed: () {
                          if (TextController.text.isNotEmpty) {
                            setState(() {
                              isLoading = true;
                              messageList?.insert(
                                  0,
                                  ChatMessageObject(
                                      message: TextController.text,
                                      isUserSender: true));
                            });

                            OpenaiApi().sendMessage(message: TextController.text).then((value) {
                              String? responseMessage = value.choices![0]?.message?.content;
                              print('Response: $responseMessage');
                              
                              if (responseMessage != null && responseMessage.isNotEmpty) {
                                setState(() {
                                  messageList?.insert(
                                      0,
                                      ChatMessageObject(
                                          message: responseMessage,
                                          isUserSender: false));
                                  isLoading = false;
                                });
                              } else {
                                print('Received empty response');
                                setState(() {
                                  isLoading = false;
                                });
                              }
                            }, onError: (error) {
                              print('Error occurred: $error');
                              setState(() {
                                isLoading = false;
                              });
                            });

                            TextController.clear();
                            setState(() {});
                          }
                        },
                        child: Text(
                          createText(),
                          style: const TextStyle(color: Color(4294967295)),
                        ),
                        style: ElevatedButton.styleFrom(primary: const Color(4280459876)),
                      ),
                    ],
                    mainAxisSize: MainAxisSize.min,
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.end,
                  ),
                )
              ],
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.start,
            ),
          ),
        ),
      ),
      appBar: AppBar(
        title: Text(
          '🧑‍🍳 Ask me for a recipe 🥙',
          style: const TextStyle(
              color: Color(4294967295), fontFamily: 'ADLaM Display'),
          textAlign: TextAlign.center,
        ),
        centerTitle: true,
        backgroundColor: const Color(4284922730),
      ),
    );
  }

  String createText() {
    if (isLoading!) {
      return '...';
    } else {
      return 'Send';
    }
  }
}

but the response message still never appears. The app still works and loads with my changes, in the Nowa IDE - so I’m not sure what the cause is.

Hey @mcombatti So I won’t recommend altering the code manually for now, because if the new code you wrote was not understandable by Nowa, it won’t render inside correctly.

If it’s possible, can you share with me the projectID to the cloud project that had issues compiling? since it will be the same problem.

Or share with me please the project files itself, but the generated version from Nowa, not the one you modified yourself.

I will then see what went wrong there and come back to you with a fix, thanks!

I have the same issue; it works perfectly during testing, but there is no response after sending the question. I built and installed a debug version for Android.

Some searches suggest it has to do with CORS headers. I have worked with that on web servers, but I’m unsure if it’s the issue and how to implement it in something like this since I’m new to mobile and development in general.

Suggestions are welcome. Other than that, I’m having a blast with it :slight_smile:

Hey @tony.alfredsson , so the CORS might be the reason in case you run the app in the previewer and your browser doesn’t allow it.

But if the problem still after you debug the app on Android, then it seems it might be another reason.

In case you have the project on the web, can you share the projectID with me? in case it’s a local project, can you compress the project and share it with me? You can upload it and send it to me privatly here or on Discord :slight_smile:

Thanks @anas. I’ll send you the ID on Discord.

1 Like

Will be waiting for you :slight_smile: You can also upload it on your google drive and share the link with me. Thanks!