Crea il tuo chatbot AI con React e l'API ChatGPT

Apr 17 2023
Usa l'API React e ChatGPT per creare un chatbot con un effetto macchina da scrivere realistico
OpenAI ha aperto la sua API, il che significa che puoi usarla per creare il tuo chatbot. Questo articolo ti fornirà un semplice esempio utilizzando React, inclusa la richiesta della vera API ChatGPT e l'implementazione di un effetto macchina da scrivere simile al sito Web ufficiale.
Foto di Levart_Photographer su Unsplash

OpenAI ha aperto la sua API, il che significa che puoi usarla per creare il tuo chatbot. Questo articolo ti fornirà un semplice esempio utilizzando React, inclusa la richiesta della vera API ChatGPT e l'implementazione di un effetto macchina da scrivere simile al sito Web ufficiale. Ecco l' indirizzo GitHub e devi inserire la tua chiave API se vuoi eseguirlo localmente.

Successivamente, analizzerò il suo motore.

Richiedi l'API e implementa lo streaming

Nel askmetodo esportato in ask.ts, ho semplicemente scritto una fetchfunzione per la richiesta.

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

Schermata completata

Poiché i dati restituiti dall'API sono in formato Markdown, utilizzo react-markdownqui come contenitore di analisi e aggiungo alcuni plug-in correlati, nonché alcuni stili di codice da 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;
}

Questo completa la semplice costruzione dell'interfaccia utente di ChatGPT. Sebbene sembri rudimentale, ha già funzionalità di base. Puoi scegliere di aggiungere più funzionalità e stili ad esso. Spero che ti aiuti.

Grazie per aver letto. Se ti piacciono queste storie e vuoi supportarmi, considera di diventare un membro di Medium . Costa $ 5 al mese e offre accesso illimitato a contenuti medi. Riceverò una piccola commissione se ti iscrivi tramite il mio link .