본문 바로가기

Flutter

Ai Chat , 패키지 없이 List로만 GPT or Bard , Gemini . 만들기// Flutter 2024.

List 로만 만들어 보는 GPt 및 bard용 챗 ListView 입니다.

패키지 없이 리스트로 구성하면 여러 응용이 가능 합니다.

 

통신을 위해 json 변환기와 http 는 import 해줍니다.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
 

 

  1. Chatting 에 쓸 List를 위해 Model Class를 만듭니다.
  2. text 는 채팅 메시지 이고 , bool은 Ai 와 나를 구분해 추력하기 위해 사용합니다.
class ChatMessage {
  final String text;
  final bool iAm;

  ChatMessage({required this.text, required this.iAm});
}
 

GptChatLister  라는 StateFull 위젯을 생성합니다.

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

  @override
  State<GptChatLister> createState() => _GptChatListerState();
}
 

일단 Gemini를 위한 연결입니다. 무료이니까요 .  // GPT 3.5 연결은 제일 아래에 있습니다. 응용해보세요

GPT자꾸 쓰니 요금이 올라갑니다.정말입니다. ( 딸라 달라고 합니다 ~ ). 

Http 연결할 ~ . Url , 해더, 를 설정하고 , 채팅메시지를 기록할 List 도 생성합니다.

채팅 텍스트 입력을 위한 텍스트 콘트롤러를 만들고, inProgress 는 시작을 false 로 합니다. false 는 입력하는 내가 되고 . Ture면 Ai 가 됩니다.

class _GptChatListerState extends State<GptChatLister> {
  final header = {'Content-Type': 'application/json'};
  final baseUrl =
      'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${APIKey.apiKey}';
  final List<ChatMessage> _chatMessage = [];
  final TextEditingController _chatController = TextEditingController();
  bool inProgress = false;
 

텍스트 입력을 하면 항상 따라다니는 dispose textcontroller.dispose().

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _chatController.dispose();
  }
 

화면에서 Text입력후 Onpress 로 호출하는 onSendPressed 메써드 입니다.

  1. 채팅 메시지 리스트에 TextField의 값을 저장합니다. 채팅창에 표시SetState
  2. TextField 값을 sendMessageToList로 전달하고 받아오는 코드를 작성
  3. 받아온 response 값을 채팅메시지 리스트에 저장 . 채팅 데 표시 SetState
  4. 입력이 완료되었으므로 TextField를 .clear()합니다.
void onSendPressed() async {
    ChatMessage chatMessage = ChatMessage(text: _chatController.text, iAm: true);
      setState(() => _chatMessage.insert(0, chatMessage));
      
       final response = await sendMessageToList(chatMessage.text);
    
    ChatMessage chatAi = ChatMessage(text: response, iAm: false);
       setState(() => _chatMessage.insert(0, chatAi));
    
       _chatController.clear();
  }
 

버튼을 누른후 . 누른 메써드에서 값을 가져오기 위해 호출하는 Future 메써드로

String message 를 받아서 body 를 구성합니다.

 

final respon = await http.post(Uri.parse(baseUrl), headers: header, body: jsonEncode(datass));

 

받아온 response는 값이 많습니다. 그중에서 TEXT만 추출합니다.

      final tttt = result['candidates'][0]['content']['parts'][0]['text'];

 

성공시 return tttt; 로 받아온 Text 응답을 버튼을 눌러서 호출된 곳으로 돌려줍니다.

Future<String> sendMessageToList(String message) async {
    setState(() => inProgress = true);

    var datass = {
      'contents': [
        {
          'parts': [
            {'text': message}
          ]
        }
      ]
    };
    final respon = await http.post(Uri.parse(baseUrl), headers: header, body: jsonEncode(datass));

    if (respon.statusCode == 200) {
      var result = jsonDecode(respon.body);
      final tttt = result['candidates'][0]['content']['parts'][0]['text'];
      setState(() => inProgress = false);
      return tttt;
    } else {
      setState(() => inProgress = false);
      throw Exception('API request failed with status code: ${respon.statusCode}');
    }
 

이제 화면을 구성합니다 .Scaffold를 구성하며 ListView 를 만듭니다.

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Chat List')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              reverse: true,
              itemCount: _chatMessage.length,
              itemBuilder: (BuildContext context, int index) {
                return _build_Message(_chatMessage[index]);
              },
            ),
          ),
          Divider(height: 1.0),
 

뷰의 내용은 따로 메써드 위젯을 만듭니다.

_build_Message(_chatMessage[index]);

 

inProgress로 진행중임을 프로그래스 바로 보여주게 됩니다.

  inProgress ? LinearProgressIndicator() : Container(),
 

채팅 입력을 아 onpress로 채팅메시지를 전달하는 화면 아래 텍스트필드와 버튼입니다.

Container(
            decoration: BoxDecoration(color: Theme.of(context).cardColor),
            child: Row(
              children: <Widget>[
                Expanded(
                  child: TextField(
                    controller: _chatController,
                    decoration: InputDecoration(
                      contentPadding: EdgeInsets.all(10.0),
                      hintText: 'Type here',
                      border: InputBorder.none
                    ),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.send),
                  onPressed: onSendPressed,
                ),
 

 

iAm 의 값을 True 냐 False 냐에 따라 . 좌우 배치를 하고 채팅말머리를 만들어 채팅 매시지의

리스트 내용을 보여 줍니다.

Widget _build_Message(ChatMessage message) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 10.0),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
        child: Column(
          crossAxisAlignment: message.iAm ? CrossAxisAlignment.end : CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              message.iAm ? 'Me' : 'Ai',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            Text(message.text),
          ],
        ),
      ),
    );
  }
 

전체 코드

class ChatMessage {
  final String text;
  final bool iAm;

  ChatMessage({required this.text, required this.iAm});
}

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

  @override
  State<GptChatLister> createState() => _GptChatListerState();
}

class _GptChatListerState extends State<GptChatLister> {
  final header = {'Content-Type': 'application/json'};
  final baseUrl =
      'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${APIKey.apiKey}';
  final List<ChatMessage> _chatMessage = [];
  final TextEditingController _chatController = TextEditingController();
  bool inProgress = false;

  Future<String> sendMessageToList(String message) async {
    setState(() => inProgress = true);

    var datass = {
      'contents': [
        {
          'parts': [
            {'text': message}
          ]
        }
      ]
    };

    final respon = await http.post(Uri.parse(baseUrl), headers: header, body: jsonEncode(datass));

    if (respon.statusCode == 200) {
      var result = jsonDecode(respon.body);
      final tttt = result['candidates'][0]['content']['parts'][0]['text'];
      print(tttt);
      //  _chatMessage.insert(0, tttt);
      setState(() => inProgress = false);
      return result['candidates'][0]['content']['parts'][0]['text'];
    } else {
      setState(() => inProgress = false);
      throw Exception('API request failed with status code: ${respon.statusCode}');
    }
  }

  void onSendPressed() async {
    ChatMessage chatMessage = ChatMessage(text: _chatController.text, iAm: true);
      setState(() => _chatMessage.insert(0, chatMessage));

       final response = await sendMessageToList(chatMessage.text);

    ChatMessage chatAi = ChatMessage(text: response, iAm: false);
       setState(() => _chatMessage.insert(0, chatAi));

       _chatController.clear();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _chatController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Chat List')),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              reverse: true,
              itemCount: _chatMessage.length,
              itemBuilder: (BuildContext context, int index) {
                return _build_Message(_chatMessage[index]);
              },
            ),
          ),
          Divider(height: 1.0),
          inProgress ? LinearProgressIndicator() : Container(),
          Container(
            decoration: BoxDecoration(color: Theme.of(context).cardColor),
            child: Row(
              children: <Widget>[
                Expanded(
                  child: TextField(
                    controller: _chatController,
                    decoration: InputDecoration(
                      contentPadding: EdgeInsets.all(10.0),
                      hintText: 'Type here',
                      border: InputBorder.none
                    ),
                  ),
                ),
                IconButton(
                  icon: Icon(Icons.send),
                  onPressed: onSendPressed,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _build_Message(ChatMessage message) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 10.0),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
        child: Column(
          crossAxisAlignment: message.iAm ? CrossAxisAlignment.end : CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              message.iAm ? 'Me' : 'Ai',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            Text(message.text),
          ],
        ),
      ),
    );
  }
  //
}
 

Gpt3.5 사용할때는 http를

아래처럼 구성합니다. ${APIKey.apiKey} GPT키로 입력 하거나 클래스를 만들어서 사용합니다.

Uri uri = Uri.parse("https://api.openai.com/v1/chat/completions");

    Map<String, dynamic> body = {
      "model": "gpt-3.5-turbo",
      "messages": [
        {"role": "user", "content": message}
      ],
      "max_tokens": 500,
    };

    final response = await http.post(
      uri,
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer ${APIKey.apiKey}",
      },
      body: json.encode(body),
    );

   Map<String, dynamic> reponse = json.decode(response.body);

   String receiveMsg = reponse['choices'][0]['message']['content'];
   return receiveMsg;