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!

Hey @tony.alfredsson and @mcombatti so I found out the cause of the error. OpenAI has changed their reponse and included logprobs which is returned as null, causing the type to be dynamic which cause an Exception when running the app.

We will release a fix for it to handle dynamic types and null being returned, but as a quick fix, you just need to change its type to something else, like List of String for example as shown below.

After that, save the project, and run it / deploy it. It should be wokring then!

CleanShot 2024-07-28 at 17.32.53

All tutorials need to be just like this one where you get an explanation of what youā€™re doing and why youā€™re doing it. Please make them all just like this going forward.

1 Like

Hey @lloydpearsoniv Thanks for sharing your feedback and I am happy that you find this one useful :slight_smile:

Is there a tutorial that you didnā€™t get clearly? if yes please let me know so we redo it better

The ecommerce tutorial moves too fast and there is no explanation of steps.

I remade the applications in nowa cloud using the latest version, and its still showing ā€œ.MainActivity not found in DEXā€ after attempting to run a compiled version.

The app runs in nowa but there seems to be a bug in the android framework. The app will build, but immediately crashes on a real device (and even emulator). Google rejects all Android builds reporting the same error for all apps built.

Heres a demo project for you to look into:
/project/66a6ea036335d4cc4fc4554a

Thanks!

**on side note, Iā€™ve used Groq API instead of OpenAI for the demo above, as the api does not use logprob, but the issue still exists. It happens even with apps not using an API.

In example project additional android error is:

Checking the license for package Android SDK Platform 34 in /usr/local/share/android-sdk/licenses
License for package Android SDK Platform 34 accepted.
Preparing ā€œInstall Android SDK Platform 34 (revision: 3)ā€.
ā€œInstall Android SDK Platform 34 (revision: 3)ā€ ready.
Installing Android SDK Platform 34 in /usr/local/share/android-sdk/platforms/android-34
ā€œInstall Android SDK Platform 34 (revision: 3)ā€ complete.
ā€œInstall Android SDK Platform 34 (revision: 3)ā€ finished.
lib/HomeScreen.dart:130:49: Error: Property ā€˜choicesā€™ cannot be accessed on ā€˜SendMessageModel2?ā€™ because it is potentially null.

  • ā€˜SendMessageModel2ā€™ is from ā€˜package:mainproject/models/sendmessage_model.dartā€™ (ā€˜lib/models/sendmessage_model.dartā€™).
    Try accessing using ?. instead.
    value.choices![0]?.message?.content,
    ^^^^^^^
    Target kernel_snapshot_program failed: Exception

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ā€˜:app:compileFlutterBuildReleaseā€™.
    > Process ā€˜command ā€˜/Users/builder/programs/flutter/bin/flutterā€™ā€™ finished with non-zero exit value 1

  • Try:
    > Run with --stacktrace option to get the stack trace.
    > Run with --info or --debug option to get more log output.
    > Run with --scan to get full insights.

  • Get more help at https://help.gradle.org

BUILD FAILED in 1m 20s
Running Gradle task ā€˜bundleReleaseā€™ā€¦ 80.8s
Gradle task bundleRelease failed with exit code 1

Build failed :expressionless:
Step 6 script Flutter build aab and automatic versioning exited with status code 1

I can get the app to build locally, but it still crashes in emulator on on device and google rejects with the DEX error. From what ive found the issue has to do with the app package name, but I cannot find any inconsistencies across files.

Also - in Web builds, the app titles should be the App name, not the project name. (See ā€œAdd/Install to homescreenā€)

This was a simple fix for the web build! Thanks!

I hope thereā€™s a solution for builds? Any suggestions?

Hey @mcombatti First of all great app on the Web :slight_smile: I just tried it out. Wish you all the luck with it.

I will check the Android version and see why it is crashing and get back to you on it as soon as I can :slight_smile:

1 Like

Thank you! For all your help!

Hey @mcombatti first all thanks for your patience! I found out the problem which is that the mainActivity file for the Android is not there for some reason.

I just want to ask if you remeber doing something that impacting this, like changing the package name or deleting a file yourself from the files panel?