Erstellen Sie Ihren eigenen KI-Chatbot mit React und der ChatGPT-API

Apr 17 2023
Verwenden Sie die React- und ChatGPT-API, um einen Chatbot mit einem realistischen Schreibmaschineneffekt zu erstellen
OpenAI hat seine API geöffnet, was bedeutet, dass Sie damit Ihren eigenen Chatbot erstellen können. Dieser Artikel liefert Ihnen ein einfaches Beispiel für die Verwendung von React, einschließlich der Anforderung der echten ChatGPT-API und der Implementierung eines Schreibmaschineneffekts ähnlich der offiziellen Website.
Foto von Levart_Photographer auf Unsplash

OpenAI hat seine API geöffnet, was bedeutet, dass Sie damit Ihren eigenen Chatbot erstellen können. Dieser Artikel liefert Ihnen ein einfaches Beispiel für die Verwendung von React, einschließlich der Anforderung der echten ChatGPT-API und der Implementierung eines Schreibmaschineneffekts ähnlich der offiziellen Website. Hier ist die GitHub-Adresse , und Sie müssen Ihren API-Schlüssel eingeben , wenn Sie ihn lokal ausführen möchten.

Als nächstes werde ich seinen Motor analysieren.

API anfordern und Streaming implementieren

In der askin exportierten Methode ask.tshabe ich einfach eine fetchFunktion für die Anfrage geschrieben.

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();
    });
  })();
}

Fertiges Bildschirmfoto

Da die von der API zurückgegebenen Daten im Markdown-Format vorliegen, verwende ich react-markdownhier als Parsing-Container und füge einige verwandte Plugins sowie einige Codestile aus highlight.js hinzu .

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;
}

Damit ist die einfache Konstruktion der ChatGPT-UI-Schnittstelle abgeschlossen. Obwohl es rudimentär aussieht, verfügt es bereits über grundlegende Funktionen. Sie können weitere Funktionen und Stile hinzufügen. Ich hoffe es hilft dir.

Danke fürs Lesen. Wenn dir solche Geschichten gefallen und du mich unterstützen möchtest, erwäge bitte, Medium-Mitglied zu werden . Es kostet 5 US-Dollar pro Monat und bietet unbegrenzten Zugriff auf Medium-Inhalte. Ich bekomme eine kleine Provision, wenn du dich über meinen Link anmeldest .