Zbuduj własnego chatbota AI za pomocą API React i ChatGPT

Użyj API React i ChatGPT, aby stworzyć chatbota z realistycznym efektem maszyny do pisania

Zdjęcie Levart_Photographer na Unsplash

OpenAI otworzyło swoje API, co oznacza, że ​​możesz go użyć do zbudowania własnego chatbota. W tym artykule znajdziesz prosty przykład użycia React, w tym żądanie prawdziwego API ChatGPT i implementację efektu maszyny do pisania, podobnego do oficjalnej strony internetowej. Oto adres GitHub i musisz podać swój klucz API , jeśli chcesz uruchomić go lokalnie.

Następnie przeanalizuję jego silnik.

Żądaj API i implementuj przesyłanie strumieniowe

W askmetodzie wyeksportowanej w ask.ts, po prostu napisałem fetchfunkcję dla żądania.

const res = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: '', // Fill your OpenAI key
  },
  body: JSON.stringify({
    stream: true,
    max_tokens: 1000,
    model: 'gpt-3.5-turbo',
    temperature: 0.8,
    top_p: 1,
    presence_penalty: 1,
    messages,
  }),
});

const parseChunk = createParseChunkFn((event) => {
  if (event.type === 'event') {
    onMessage(event.data);
  }
});

const reader = res.body?.getReader();
if (reader) {
  void (function read() {
    reader.read().then(({ done, value }) => {
      if (done) {
        return;
      }
      const chunk = new TextDecoder().decode(value);
      parseChunk(chunk);
      read();
    });
  })();
}

Gotowy zrzut ekranu

Ponieważ dane zwracane przez API są w formacie Markdown, używam react-markdowntutaj jako kontenera analizującego i dodaję kilka powiązanych wtyczek, a także niektóre style kodu z highlight.js .

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import 'highlight.js/styles/vs2015.css';

<ReactMarkdown
  remarkPlugins={[remarkGfm]}
  rehypePlugins={[rehypeHighlight]}
>
  {l.answering ? answeringContent : l.content}
</ReactMarkdown>

import { useCallback, useState } from 'react';
import { Button, Input, Space, Typography } from 'antd';

import './App.css';
import ask from './ask';

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import 'highlight.js/styles/vs2015.css';

const { Text } = Typography;

const enum Role {
  Assistant = 'assistant',
  User = 'user',
}

export interface Message {
  role: Role;
  content: string;
}

interface Log extends Message {
  id: string;
  answering?: boolean;
}

export default () => {
  const [question, setQuestion] = useState('');
  const [logs, setLogs] = useState<Log[]>(() => []);

  const [answeringContent, setAnsweringContent] = useState('');

  const askQuestion = useCallback((messages: Message[]) => {
    setAnsweringContent('&ZeroWidthSpace;');

    let contents = '';
    ask((str) => {
      if (!str) return;
      if (str === '[DONE]') {
        setAnsweringContent('');
        setLogs((prev) =>
          prev.map((i) => {
            const { answering, ...rest } = i;
            if (answering) {
              return {
                ...rest,
                content: contents,
              };
            }
            return i;
          })
        );

        return;
      }

      let content = str;
      try {
        const data = JSON.parse(str);
        content = data.choices?.reduce((acc: string, cur: unknown) => {
          // @ts-expect-error
          acc += cur?.delta?.content ?? '';
          return acc;
        }, '');
      } catch {
        // Ignore
        // console.error(err);
      }

      contents += content;
      setAnsweringContent(contents);
    }, messages);
  }, []);

  const isAnswering = Boolean(answeringContent);

  const onSubmit = () => {
    if (isAnswering) return;
    const messages = logs.concat({
      id: crypto.randomUUID(),
      role: Role.User,
      content: question,
    });

    askQuestion(messages.map((i) => ({ role: i.role, content: i.content })));
    setQuestion('');
    setLogs([
      ...messages,
      {
        content: '',
        answering: true,
        role: Role.Assistant,
        id: crypto.randomUUID(),
      },
    ]);
  };

  return (
    <main className='main'>
      <div className='chat'>
        <div className='logs'>
          {logs.map((l) => (
            <div key={l.id} className='log'>
              <Text strong>{l.role === Role.User ? 'User:' : 'ChatGPT:'}</Text>
              <div className={l.answering ? 'streaming' : ''}>
                <ReactMarkdown
                  remarkPlugins={[remarkGfm]}
                  rehypePlugins={[rehypeHighlight]}
                >
                  {l.answering ? answeringContent : l.content}
                </ReactMarkdown>
              </div>
            </div>
          ))}
        </div>
        <Space.Compact>
          <Input
            allowClear
            value={question}
            onPressEnter={onSubmit}
            placeholder='Enter your question'
            onChange={(e) => setQuestion(e.target.value)}
          />
          <Button type='primary' onClick={onSubmit} loading={isAnswering}>
            {isAnswering ? 'Answering' : 'Submit'}
          </Button>
        </Space.Compact>
      </div>
    </main>
  );
};

@keyframes blink {
  to {
    visibility: hidden;
  }
}

.streaming > :not(ol):not(ul):not(pre):last-child:after,
.streaming > ol:last-child li:last-child:after,
.streaming > pre:last-child code:after,
.streaming > ul:last-child li:last-child:after {
  -webkit-animation: blink 1s steps(5, start) infinite;
  animation: blink 1s steps(5, start) infinite;
  content: '▋';
  margin-left: 0.25rem;
  vertical-align: baseline;
}

To kończy prostą konstrukcję interfejsu użytkownika ChatGPT. Chociaż wygląda prymitywnie, ma już podstawową funkcjonalność. Możesz dodać do niego więcej funkcji i stylów. Mam nadzieję, że Ci to pomoże.

Dziękuje za przeczytanie. Jeśli lubisz takie historie i chcesz mnie wesprzeć, rozważ zostanie członkiem Medium . Kosztuje 5 USD miesięcznie i zapewnia nieograniczony dostęp do treści Medium. Dostanę niewielką prowizję, jeśli zarejestrujesz się przez mój link .

Suggested posts

Unikanie niszczycielskich konsekwencji udaru mózgu: nauka z rodzinnej podróży.

Unikanie niszczycielskich konsekwencji udaru mózgu: nauka z rodzinnej podróży.

Wstęp Udar to nagły przypadek medyczny, który może dotknąć każdego w dowolnym momencie. Występuje, gdy przepływ krwi do części mózgu zostaje zakłócony, co prowadzi do nagłej utraty funkcji mózgu.

Rewolucja bez kodu: wyzwalanie dobrobytu biznesowego dzięki Webflow, Framer, Bubble.io,

Rewolucja bez kodu: wyzwalanie dobrobytu biznesowego dzięki Webflow, Framer, Bubble.io,

Wyrusz w ekscytującą podróż pełną innowacji i sukcesu, wykorzystując niewykorzystany potencjał tych zaskakujących platform bez kodu. Pozwólcie, że podzielę się z wami historią o Sapnie, niezwykłej kobiecie-przedsiębiorcy.

Related posts

Kłamstwo kowbojskie

Jak megakorporacje sprzedały Amerykanom mocne kłamstwo

Kłamstwo kowbojskie

Jest wspaniale złoty poranek na falistych wzgórzach zachodu, gdy pierwsze promienie słońca oświetlają oszronioną trawę na równinach. Stado spokojnego bydła rasy Angus, czarnego jak smoła i masywne, rysuje się na tle wschodu słońca, gdy zmierza utartym szlakiem do miejsca, które wszyscy dobrze znają; bar śniadaniowy.

Co wpływa na podcast?

Co wpływa na podcast?

W zeszłym tygodniu zauważyłem komunikat prasowy, wysłany za pośrednictwem PressGazette (brytyjski serwis informacyjny). W artykule ogłoszono, że Acast, skandynawska firma hostingowa i reklamowa podcastów, będzie przewodzić konsorcjum „prawdopodobnie najbardziej wpływowych” wydawców podcastów.

Finansowanie usuwania dwutlenku węgla w RO24

Podział wniosków o środki Carbon180 na różne ścieżki

Finansowanie usuwania dwutlenku węgla w RO24

Autor: Alyson Lee, doradca zarządzający ds. spraw rządowych Coroczny proces przyznawania środków przez rząd federalny ma kluczowe znaczenie dla naszych wysiłków na rzecz sfinansowania usuwania dwutlenku węgla (CDR). Każdego roku Carbon180 przesyła prośby do różnych biur kongresowych, aby pomóc wskazać, gdzie będą płynąć dolary w przyszłym roku.

Zabójstwo Jordana Neely'ego i problem „patriotycznego” rasizmu

Czy Stany Zjednoczone mogą zrobić zasadniczy krok, by nie utożsamiać zachowania rasistowskiego z byciem dobrym Amerykaninem?

Zabójstwo Jordana Neely'ego i problem „patriotycznego” rasizmu

[Ostrzeżenie: ten esej zawiera zrzuty ekranowe ludzi wychwalających mordercę Jordana Neely'ego.] Funkcjonariusze organów ścigania w końcu - i być może niechętnie - oskarżyli Daniela Penny'ego, człowieka, który dusił Jordana Neely'ego aż do śmierci, o to zabójstwo.