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.
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();
});
})();
}
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('​');
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 .
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.
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.
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.
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.
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.
[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.