Back to Blog

Flutter를 사용하여 ChatGPT 메시징 애플리케이션 구축 방법

How to Build a ChatGPT Messaging Application with Flutter

요즘은 친구들과 연락을 주고받는 데 시간이 너무 많이 걸립니다. 소셜 미디어와 메시징 앱이 등장하기 전에는 실제로 사람들과 시간을 보내야 했습니다. 지금은 메시지만 보내면 되니 조금 나아졌지만, 여전히 무슨 말을 해야 할지 고민하고 소통해야 하는 것은 고통스러운 일입니다.

이제 해결책이 있습니다! 더 이상 생각할 필요 없이 친구들과 인공지능 채팅을 할 수 있습니다!

우리는 농담이에요: 친구들과 시간을 보내는 것을 절대 AI에 맡기지 마세요. 하지만 이건 여전히 재미있는 활용 사례로, 봇의 답변에 사용하거나 대화가 지루해질 때 대화를 이어가는 데 도움이 될 수 있습니다.

이 기사에서는 아고라 챗을 사용하여 자체 채팅 애플리케이션을 구축하는 방법과 ChatGPT를 추가하여 더 고급스럽게 만드는 방법을 설명합니다.

전체 코드는 여기에서 확인할 수 있습니다.

아고라 챗

아고라 챗은 친구들과 텍스트, 이미지, GIF, 이모티콘 및 기타 미디어를 포함해 소통할 수 있습니다. Agora Chat은 독립형 애플리케이션으로 사용할 수 있으며, 실시간 통신(RTC)을 사용하는 기존 애플리케이션(예: 영상 통화)에 통합할 수도 있습니다.

ChatGPT

OpenAI의 ChatGPT는 메시지에 매우 현실적인 응답을 생성할 수 있는 AI 모델입니다. OpenAI API를 사용하여 모델에 요청을 전송하고, 채팅에 삽입할 응답을 가져올 것입니다.

설정

이러한 서비스와 상호 작용하려면 Agora 및 OpenAI에 계정을 만들고 몇 가지 토큰을 가져와야 합니다.

Agora Chat 정보 가져오기

  1. Agora.io에서 계정을 만드세요.
  2. 프로젝트 만들기
  3. 채팅로 스크롤을 내리고 사용/구성
  4. 토글 버튼을 클릭하고 AppKey를 복사하여 붙여 넣으세요.
  5. 왼쪽의 Operation Management 아래에서 User를 선택하고 두 개의 사용자를 생성합니다. (사용자 ID를 기록해 두세요.)
  6. Application Information 아래의 Basic Information 탭으로 돌아가 각 사용자의 사용자 ID를 Chat User Temp Token 필드에 입력합니다. Generate를 클릭합니다. 토큰과 사용자 ID를 consts.dart 파일에 복사하여 붙여넣습니다.

OpenAI 토큰 획득 방법

  1. OpenAI에 계정을 생성합니다.
  2. 오른쪽 상단 모서리에서 이미지를 클릭하고 View API Keys
  3. 새 비밀 키를 생성하고 해당 키를 consts.dart 파일에 복사하여 붙여넣습니다.

채팅 페이지

현재 사용자의 ID, 토큰, 메시지를 보낼 사용자의 ID를 가지고 채팅 페이지로 이동해야 합니다. 이를 수행하는 방법은 여러 가지가 있지만, 이 예제에서는 각 사용자로 로그인할 수 있는 두 개의 버튼을 생성했습니다. 자세한 내용은 main.dart 파일을 참고하세요.

채팅 페이지로 이동하면 채팅을 초기화하기 위해 세 가지 작업이 필요합니다. 아 Chat SDK를 아직 설정하지 않았기 때문에 SDK를 초기화하고, 메시지 리스너를 추가한 후 현재 사용자를 로그인시켜 채팅 애플리케이션을 완전히 사용할 수 있도록 해야 합니다. 이 세 가지 기능은 initState에 호출되어야 합니다.

채팅 페이지로 이동하려면 현재 사용자의 ID, 토큰, 그리고 메시지를 보낼 사용자의 ID가 필요합니다. 이를 구현하는 방법은 여러 가지가 있지만, 이 예제에서는 각 사용자로 로그인할 수 있는 두 개의 버튼을 만들었습니다. 자세한 내용은 main.dart 파일에서 확인할 수 있습니다.

채팅 페이지로 진입하자마자 채팅을 초기화하기 위해 세 가지 작업이 필요합니다. Agora Chat SDK를 아직 설정하지 않았기 때문에 SDK를 초기화한 후 메시지 리스너를 추가하고, 마지막으로 현재 사용자를 로그인시켜 채팅 애플리케이션을 완전히 사용할 수 있도록 해야 합니다. 이 세 가지 기능은 initState에서 호출되어야 합니다.

void _initSDK() async {
    ChatOptions options = ChatOptions(
        appKey: widget.chatKey,
        autoLogin: false,
    );
    await ChatClient.getInstance.init(options);
    await ChatClient.getInstance.startCallback();
}

void _addChatListener() {
    ChatClient.getInstance.chatManager.addMessageEvent(
        "UNIQUE_HANDLER_ID",
        ChatMessageEvent(
            onSuccess: (msgId, msg) {
            debugPrint(
                "send message succeed: ${(msg.body as ChatTextMessageBody).content}");
            _addMessage(
                DemoMessage(
                    text: (msg.body as ChatTextMessageBody).content,
                    senderId: widget.userId),
            );
            },
            onProgress: (msgId, progress) {
            debugPrint("send message succeed");
            },
            onError: (msgId, msg, error) {
            debugPrint(
                "send message failed, code: ${error.code}, desc: ${error.description}",
            );
            },
        ));

ChatClient.getInstance.chatManager.addEventHandler(
        "UNIQUE_HANDLER_ID",
        ChatEventHandler(onMessagesReceived: onMessagesReceived),
    );
}

void _signIn() async {
    try {
        await ChatClient.getInstance.loginWithAgoraToken(
        widget.userId,
        widget.agoraToken,
        );
        debugPrint("login succeed, userId: ${widget.userId}");
    } on ChatError catch (e) {
        debugPrint("login failed, code: ${e.code}, desc: ${e.description}");
    }
}

여기서 로그인 방법을 구현했기 때문에 로그아웃 방법도 만들어 보겠습니다. 이 방법은 dispose 메서드에서 호출됩니다.

void _signOut() async {
    ChatClient.getInstance.chatManager.removeMessageEvent("UNIQUE_HANDLER_ID");
    ChatClient.getInstance.chatManager.removeEventHandler("UNIQUE_HANDLER_ID");
    try {
        await ChatClient.getInstance.logout(true);
        debugPrint("sign out succeed");
    } on ChatError catch (e) {
        debugPrint(
        "sign out failed, code: ${e.code}, desc: ${e.description}");
    }
}

채팅 리스너에서 호출되는 몇 가지 함수가 표시되지 않는 것을 확인할 수 있습니다:

  • _addMessage수신된 메시지를 표시되는 메시지 목록에 추가합니다.
  • debugPrint수신된 메시지를 콘솔 로그에 추가합니다.
  • onMessagesReceived메시지가 수신될 때 호출되는 함수입니다. (우리는 또한 _addMessage를 사용하여 양쪽의 메시지 목록을 유지할 것입니다.)

아래는 해당 함수의 정의입니다.

void onMessagesReceived(List<ChatMessage> messages) {
    for (var msg in messages) {
        switch (msg.body.type) {
        case MessageType.TXT:
            {
            ChatTextMessageBody body = msg.body as ChatTextMessageBody;
            debugPrint(
                "receive text message: ${body.content}, from: ${msg.from}",
            );
            _addMessage(
                DemoMessage(text: body.content, senderId: msg.from),
            );
            }
            break;
        default:
            break;
        }
    }
}

void _addMessage(DemoMessage message) {
    _messages.add(message);
    setState(() {
        scrollController.jumpTo(scrollController.position.maxScrollExtent + 40);
    });
}

채팅 표시

ListView.builder_messages 목록에 있는 모든 메시지를 표시합니다. 화면 하단에 메시지를 전송할 수 있는 TextField를 추가해야 합니다.

ListView.builder(
    controller: scrollController,
    itemCount: _messages.length,
    itemBuilder: (_, index) {
    //show first 10 characters
    return Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
        const SizedBox(height: 2),
        if (widget.userId != _messages[index].senderId)
            Container(
            padding: const EdgeInsets.all(10),
            decoration: BoxDecoration(
                color: Colors.lightBlue[100],
                borderRadius: BorderRadius.circular(10),
            ),
            child: Text(
                _messages[index].text!,
            ),
            )
        else
            Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
                Flexible(
                child: Container(
                    padding: const EdgeInsets.all(10),
                    decoration: BoxDecoration(
                    color: Colors.lightGreen[100],
                    borderRadius: BorderRadius.circular(10),
                    ),
                    child: Text(
                    _messages[index].text!,
                    ),
                ),
                ),
            ],
            ),
        ],
    );
},),

메시지 보내기

메시지를 보내려면 Agora Chat SDK에서 sendMessage 함수를 호출해야 합니다. 이 함수는 ChatMessage 객체를 인수로 받습니다. 또한 메시지를 보낼 대상의 사용자 ID도 전달해야 합니다.

void _sendMessage(String sentTo, String? message) async {
    if (message == null) {
        debugPrint("single chat id or message content is null");
        return;
    }

    var msg = ChatMessage.createTxtSendMessage(
        targetId: sentTo,
        content: message,
    );

    ChatClient.getInstance.chatManager.sendMessage(msg);

    setState(() {
        _messageController.text = "";
    });
}

이제 완전히 작동하는 아고라 챗 애플리케이션이 준비되었습니다. 마지막 단계는 AI 생성 응답을 가능하게 하기 위해 ChatGPT를 통합하는 것입니다.

ChatGPT

ChatGPT를 사용하려면 먼저 API에 연결해야 합니다. 또한 API로부터 응답을 기다리는 동안 ‘보내기’ 버튼을 비활성화하기 위해 isWaitingResponse 변수를 선언해야 합니다.

변수가 선언되면 다른 사용자의 마지막 메시지에 대해 행복한 응답이나 화난 응답을 전송하는 기능을 설정할 수 있습니다.

final openAI = OpenAI.instance.build(
    token: openAIToken,
    baseOption: HttpSetup(receiveTimeout: const Duration(seconds: 30)),
    isLog: true);
var _isWaitingResponse = false;

void _onTapSendHappyMessage() async {
    final List<DemoMessage> otherMessages = _messages
        .where((element) => element.senderId != widget.userId)
        .toList();
    _reset();
    _sendAIMessage(
        "Give me a happy response to the message: ${otherMessages.last.text}",
    ).then((value) {
        setState(() {
        _isWaitingResponse = false;
        _messageController.text = value.trim();
        });
    });
}

void _onTapSendAngryMessage() async {
    final List<DemoMessage> otherMessages = _messages
        .where((element) => element.senderId != widget.userId)
        .toList();
    _reset();
    _sendAIMessage(
        "Give me an angry response to the message: ${otherMessages.last.text}",
    ).then((value) {
        setState(() {
        _isWaitingResponse = false;
        _messageController.text = value.trim();
        });
    });
}

void _reset() {
    setState(() {
        _isWaitingResponse = true;
    });
}

Future<String> _sendAIMessage(String message) async {
    final request = CompleteText(
        prompt: message,
        model: Model.textDavinci3,
        maxTokens: 200,
    );

    final response = await openAI.onCompletion(
        request: request,
    );
    return response!.choices.first.text;
}

이것이 우리 애플리케이션의 전체 기능입니다. 이제 해당 응답을 생성하는 버튼을 추가하고 이를 TextController.valueTextField에 배치하여 사용자가 이를 전송할 수 있도록 할 수 있습니다.

읽어주셔서 감사합니다. 여기를 클릭하여 아고라에 대해 더 알아보고 배우세요.

RTE Telehealth 2023
Join us for RTE Telehealth - a virtual webinar where we’ll explore how AI and AR/VR technologies are shaping the future of healthcare delivery.

Learn more about Agora's video and voice solutions

Ready to chat through your real-time video and voice needs? We're here to help! Current Twilio customers get up to 2 months FREE.

Complete the form, and one of our experts will be in touch.

Try Agora for Free

Sign up and start building! You don’t pay until you scale.
Try for Free