개발노트

15. [Flutter] Chat GPT 챗봇 만들기 본문

앱 개발/Flutter

15. [Flutter] Chat GPT 챗봇 만들기

mroh1226 2023. 4. 24. 14:47
반응형

Chat GPT SDK를 이용한다면 Flutter 앱에 Chat GPT를 연결하여 사용할 수 있습니다.


준비물.1

- 설치링크: https://pub.dev/packages/chat_gpt_sdk/install

 

chat_gpt_sdk | Flutter Package

create chat bot and other bot with ChatGPT SDK Support GPT-3.5 Turbo and SSE Generate Prompt

pub.dev

 

pubspec.yaml

 

준비물.2

ChatGPT API Key 받는 곳

- 링크: https://platform.openai.com/account/api-keys

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com

 

1. Create new secret key 클릭

 

2. Create secret key 클릭 후 api 키 생성, 복사


소스 설명.1

MessageModel.dart (메세지 모델로 사용할 객체)
class MessageModel {
  final bool isBot;
  final String message;
  MessageModel(this.isBot, this.message);
}

- isBoot: 봇이 보냈으면 true , User가 보냈으면 false 값을 갖습니다.

- message: 메세지 내용을 갖습니다.

 

main.dart (메인 화면)
import 'dart:collection';

import 'package:chat_gpt_sdk/chat_gpt_sdk.dart';
import 'package:flutter/material.dart';
import 'package:flutter_application_chatbotgpt/models/message_model.dart';

final List<MessageModel> _messages = [MessageModel(true, 'Hi')];
UnmodifiableListView<MessageModel> messages = UnmodifiableListView(_messages);
final txtMessage = TextEditingController();
void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final openAI = OpenAI.instance.build(
      token: '위 링크에서 생성한 챗GPT Open API를 입력해주세요.',
      baseOption: HttpSetup(receiveTimeout: const Duration(seconds: 100)),
      isLog: true);

  void sendMessage(String message) async {
    const maxTokens = 2048;
    final promptLength = message.length;
    final contextLength =
        messages.fold<int>(0, (prev, m) => prev + m.message.length);
    final remainingTokens = maxTokens - promptLength - contextLength;

    final requests = CompleteText(
      prompt: message,
      model: Model.textDavinci3,
      maxTokens: remainingTokens,
    );

    final res = await openAI.onCompletion(request: requests);
    setState(() {
      _messages.add(MessageModel(true, res?.choices.last.text ?? "Error"));
      messages = UnmodifiableListView(_messages);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('ChatGPT'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Stack(children: [
            Column(
              children: [
                Expanded(
                    flex: 1,
                    child: ListView.builder(
                      padding: const EdgeInsets.symmetric(vertical: 10),
                      itemCount: messages.length,
                      physics: const BouncingScrollPhysics(),
                      itemBuilder: (context, index) {
                        return messages[index].isBot
                            ? BotCard(index: index)
                            : UserCard(index: index);
                      },
                    )),
                const SizedBox(
                  height: 45,
                )
              ],
            ),
            SendMessage(
              sendMessage: () => sendMessage(txtMessage.text),
            )
          ]),
        ),
      ),
    );
  }
}

 

 final openAI = OpenAI.instance.build(
      token: 'API Key 입력부분',
      baseOption: HttpSetup(receiveTimeout: const Duration(seconds: 100)),
      isLog: true);

1) OpenAI 객체

- token: ChatGPT API Key 인증키를 입력합니다.

- baseOption: 대기할 최대 시간을 지정합니다. (100초)

- isLog: OpenAI의 API와 상호 작용하려면 HTTP 클라이언트를 설정해야하며, 디버깅 및 기록을 위해 로거도 true로 설정합니다.

 

2) sendMessage 메소드

유저가 메세지를 보내기 위해 버튼을 눌렀을 때 동작합니다.

 

2-1) CompleteText 위젯

ChatGPT API에 전달할 메세지 정보를 입력할 위젯

final requests = CompleteText(
      prompt: message,
      model: Model.textDavinci3,
      maxTokens: remainingTokens,
    );

- prompt: 메시지 텍스트 값을 입력합니다.

- model: OpenAI API의 모델을 입력합니다.

- maxTokens: 완성된 텍스트의 최대 길이를 입력합니다.

 

2-2) OpenAI.onCompletion

    final res = await openAI.onCompletion(request: requests);
    setState(() {
      _messages.add(MessageModel(true, res?.choices.last.text ?? "Error"));
      messages = UnmodifiableListView(_messages);
    });

- final res: CompleteText 위젯으로 요청 후, 완성된 텍스트를 반환하는 메소드를 호출하는 코드입니다.

*res에 값이 없다면 "Error" 문자를, Null이 아니면 받은 메시지를 Messages 리스트에 추가합니다.

 

3) ListView.builder (메시지를 노출할 위젯)

ListView.builder(
                      padding: const EdgeInsets.symmetric(vertical: 10),
                      itemCount: messages.length,
                      physics: const BouncingScrollPhysics(),
                      itemBuilder: (context, index) {
                        return messages[index].isBot
                            ? BotCard(index: index)
                            : UserCard(index: index);
                      },
                    )

3-1)BouncingScrollPhysics 은 오버스크롤이 발생 했을 때 부드럽게 멈춰줍니다. (IOS 스크롤방식)

3-2) isBot 속성을 이용하여 true 일경우 BotCard, flase 일 경우 UserCard를  List Item에 빌드합니다.


소스 설명.2

SendMessage 위젯 (메세지 보내기 기능 위젯으로 분리시킴)
class SendMessage extends StatelessWidget {
  final VoidCallback sendMessage;
  const SendMessage({
    required this.sendMessage,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.bottomCenter,
      child: Container(
        height: MediaQuery.of(context).size.height * .08,
        width: double.maxFinite,
        padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
        child: TextField(
          controller: txtMessage,
          decoration: InputDecoration(
              suffixIcon: GestureDetector(
                onTap: () {
                  sendMessage();
                  _messages
                      .add(MessageModel(false, txtMessage.text.toString()));
                  txtMessage.clear();
                },
                child: const Icon(
                  Icons.send,
                  color: Colors.blue,
                ),
              ),
              hintText: '...A',
              disabledBorder: const OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.grey),
                  borderRadius: BorderRadius.all(Radius.circular(20))),
              enabledBorder: const OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.grey),
                  borderRadius: BorderRadius.all(Radius.circular(20)))),
        ),
      ),
    );
  }
}

 

1. TextField 위젯 (유저 메시지를 입력 받을 위젯)

final txtMessage = TextEditingController();
TextField(
          controller: txtMessage,
          decoration: InputDecoration(
              suffixIcon: GestureDetector(
                onTap: () {
                  sendMessage();
                  _messages
                      .add(MessageModel(false, txtMessage.text.toString()));
                  txtMessage.clear();
                },
                child: const Icon(
                  Icons.send,
                  color: Colors.blue,
                ),
              ),
              hintText: '...A',
)

- controller: TextEditionfController를 할당하면 입력된 값을 가져올 수 있습니다.

- suffixIcon: TextField에 아이콘을 추가할 수 있습니다.

- hintText: 입력창에 문자가 없을 때 보여지는 Text 값입니다.

 


소스 설명.3

  botCard 위젯 (Chat GPT가 보내는 메시지)
//챗봇 카드 위젯
class BotCard extends StatelessWidget {
  final int index;
  const BotCard({
    required this.index,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            CircleAvatar(
              child: Icon(
                Icons.person,
                size: 35,
                color: Colors.cyan,
              ),
            ),
            Text('챗 봇'),
          ],
        ),
        Container(
          padding: const EdgeInsets.symmetric(
            horizontal: 10,
          ),
          width: 300,
          decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: const BorderRadius.all(Radius.circular(15)),
              boxShadow: [
                BoxShadow(
                    color: Colors.black.withOpacity(0.12),
                    offset: const Offset(2, 2))
              ]),
          child: Transform.translate(
            offset: const Offset(0, -15),
            child: Text(
              messages[index].message,
              softWrap: true,
            ),
          ),
        ),
      ],
    );
  }
}
UserCard 위젯 (유저가 보내는 메시지)
//유저 카드 위젯
class UserCard extends StatelessWidget {
  final int index;
  const UserCard({
    required this.index,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        const Align(
          alignment: Alignment.centerRight,
          child: Column(
            children: [
              CircleAvatar(
                child: Icon(
                  Icons.hail,
                  size: 30,
                ),
              ),
              Text('구굴러')
            ],
          ),
        ),
        Positioned(
          right: 50,
          bottom: 15,
          child: Column(
            children: [
              Container(
                  padding: const EdgeInsets.all(5),
                  decoration: BoxDecoration(
                      color: Colors.amber,
                      borderRadius: const BorderRadius.all(Radius.circular(10)),
                      boxShadow: [
                        BoxShadow(
                            color: Colors.black.withOpacity(.12),
                            offset: const Offset(-1, 1),
                            blurRadius: 2)
                      ]),
                  child: Text(
                    messages[index].message,
                    softWrap: true,
                    style: const TextStyle(fontWeight: FontWeight.w500),
                  ))
            ],
          ),
        )
      ],
    );
  }
}

 

단순히 UI를 그리는 위젯이라 설명은 생략하겠습니다.

*Text 위젯 속성에 softWrap = true 로 두면 텍스트 길이에 따라 Text Widget 크기가 적용됩니다.


 

빌드된모습.

 


- 참고링크: 

https://www.youtube.com/watch?v=qUEUMxGW_0Q 

 

반응형
Comments