TypeScriptã§GPT-3.5ã䜿ã£ãŠChatGPTã¯ããŒã³ãäœã2 - Vite+Reactã§ChatGPTã¯ããŒã³
ChatGPTã«ã¯ããã¥ãŒã¹ã«ãªããªãæ¥ããªããããã®è©±é¡æ§ããããŸããããããªChatGPTãæ±ºããŠé ãã®æè¡ã§ã¯ãããŸãããå®å šã«åããã®ã¯ç¡çã§ããããã«è¿ãã¯ããŒã³ãäœãããšã¯ãã»ã©é£ãããããŸããã
ãã®èšäºã¯TypeScriptã§GPT-3.5ã䜿ã£ãŠChatGPTã¯ããŒã³ãäœã第äºåŒŸã§ãä»åã¯Vite+React+TypeScript+Tailwind CSSã§ChatGPTã¯ããŒã³ããŠã§ãã¢ããªãšããŠäœã£ãŠã¿ãŸãã
- 第äžåŒŸ: TypeScriptã§GPT-3.5ã䜿ã£ãŠChatGPTã¯ããŒã³ãäœã1 - GPTã§æ€çŽ¢ãšãŒãžã§ã³ã
第äžåŒŸã®èšäºã§ã¯å€§èŠæš¡èšèªã¢ãã«ïŒä»¥äžLLMïŒã§ããGPTã®äœ¿ãæ¹ããDenoã§OpenAI GPT APIãšGoogle Custom Searchã䜿ã£ãŠæäºãã¿ã察å¿ã§ããæ€çŽ¢ãšãŒãžã§ã³ããäœããŸãããä»åã®èšäºã¯ããããŠã§ãããã³ããšã³ãéçºè ã«éŠŽæã¿æ·±ãããã«æžããŠããŸãã
察象èªè
- Reactãã«ã¹ã¿ã ããã¯é¢æ°ã«ã€ããŠçè§£ããŠãã人ãåæãšããŠããŸãã
- GPTã»LLMã«ã€ããŠã¯ç¬¬äžåŒŸã®èšäºã§è§£èª¬ããŠããã®ã§ã詳ããç¥ãããæ¹ã¯ãã¡ããã芧ãã ããã
ãã®èšäºã§äœããã®
- Vite+Reactã§åããŠã§ãã¢ããªã±ãŒã·ã§ã³
- ãŠãŒã¶ãŒãå ¥åãã OpenAI APIããŒã䜿ã£ãŠ GPT API ãå©ã
- ChatGPTã£ãœããã®
ãšãªããŸãã
ãªãããã®èšäºã§æžããã³ãŒããããã«çºå±ããããã®ã https://github.com/erukiti/chatgpt-clone ã«å ¬éããŠããã®ã§ããã¡ãã䜵ããŠã芧ãã ããã
ãããã
GPTïŒGenerative Pre-trained TransformerïŒãšã¯ãOpenAIãéçºããŠããå€§èŠæš¡èšèªã¢ãã«ïŒLLMïŒã§ãããèšäºå·çæç¹ã§ã®ææ°ã¯ GPT-3.5 ãšåŒã°ãããã®ã§ããGPTã¯äžããæååã«ç¶ãæååã確çã«åºã¥ããŠäºæž¬ããŠè£å®ããæ©èœãæã¡ãŸãã
ããšãã°ãããããããããããšããã«ããšããæååãäžããã°ãé«ç¢ºçã§ãããããããšãã°ããããããŸããããçæãããã§ãããã
ãã®èšäºã§äœ¿ã£ãŠãã text-davinci-003 ã¢ãã«ãªã© OpenAI ã® GPT ã¯åŸé課éã§ãã詳ãã㯠https://openai.com/api/pricing/ ã® Language Models ãã芧ãã ããã
$0.0200 /â1K tokens
äžéãéšãããŠããChatGPTããæ°ããBingã»Edgeã¯ããã®GPTã䜿ã£ãã¢ããªãšèªèããŠããã°ããã§ãããã
ãã€ã³ã
- GPTã¯å€§èŠæš¡èšèªã¢ãã«ã§ãããæååãäºæž¬ããŠè£å®ãããµãŒãã¹ãæäŸãããŠãã
- ChatGPTãæ°ããBingã»Edgeã¯ãGPTã䜿ã£ãã¢ããªã±ãŒã·ã§ã³ã§ãã
Vite+React+TypeScript+Tailwind CSSãã»ããã¢ãããã
æ¬æ Œçã«äœããªãNext.jsã®æ¹ãè¯ãã§ãããä»åäœããã®ã¯ããç°¡åãªãã®ã§ãVite+Reactã®æ¹ãã»ããã¢ãããæ¥œã¡ããªã®ã§ãã¡ãã«ããŸãã
# yarnã®å Žå
yarn create vite chatgpt-clone --template react-ts
# npmã®å Žå
npx create-vite chatgpt-clone --template react-ts
chatgpt-cloneãšãããã£ã¬ã¯ããªãã§ããã®ã§ cd chatgpt-clone ã§ç§»åããŸãã
次ã«CSSãããæãã«æ±ãããã«Tailwind CSSãã»ããã¢ããããŸãã
# yarnã®å Žå
yarn add -D tailwindcss@latest postcss@latest autoprefixer@latest
yarn tailwindcss init -p
# npmã®å Žå
npm i -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
Tailwind CSSã®ããŒãžã§ã³ã«ãããšæããŸãã tailwind.config.js ããã㯠tailwind.config.cjs ãšãããã¡ã€ã«ãçæãããŸããããã Tailwind CSS ã®èšå®ãã¡ã€ã«ã§ãã
.cjs 㯠Node.js ç¬èªã®ãã®ã§ CommonJS ãšããå€ä»£ã®ã¢ãžã¥ãŒã«ã·ã¹ãã ã«æ²¿ã£ããã®ã§ãããä»åã¯ããããããããããããã§ã¯ãªãã®ã§ãçæãããã€ããã®ãŸãŸäœ¿ããŸãã
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
content é
åã« "./src/**/*.{js,ts,jsx,tsx}" ã远å ããŸãã
次㫠src/main.tsx ã« import 'tailwindcss/tailwind.css' ã远å ããŸãã
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import 'tailwindcss/tailwind.css'
import './index.css'
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Tailwind CSSã®CSSãèªã¿èŸŒãæ¹æ³ã¯è²ã
ãããŸãããäžçªãæè»œãªã®ã¯ãã®æ¹æ³ã§ãã
ããã§ Tailwind CSSã䜿ããããã«ãªããŸããã
èæ¯è²ãèšå®ããããã« src/index.css ãæžãæããŸãã
/* src/index.css */
body {
background-color: #8a9ccb;
}
ããšã¯ src/App.tsx ã倿ŽããŠåäœãèŠãŠã¿ãŸãããã
// src/App.tsx
import { FC } from 'react'
const App: FC = () => {
return <div className="text-red-500 text-9xl">hoge</div>
}
export default App
src/app.tsx ããã®ããã«ä¿®æ£ããŠåäœã確èªãããšãèµ€ããŠã§ããæåã«ãªãã®ã§CSSãåœãã£ãŠããããšã確èªã§ããŸãã
èµ·åæ¹æ³ã¯ä»¥äžã®éãã§ localhost:5173 ã§åäœã確èªã§ããŸãã
# yarnã®å Žå
yarn dev
# npmã®å Žå
npm run dev
ãããŸã§ã§ Vite+React+TypeScript+TailwindCSS ã®æºåãã§ããŸããã
ãããViteãNext.jsãDenoã§åé¡ãªãåãããã«ãªããšæã£ãŠããŸããããããæ¬è³ªã§ã¯ãªãã®ã§çœ®ããŠãããŸãã
openai npmãã€ã³ã¹ããŒã«ãã
ååã䜿ã£ã openai npm ã§ãããä»åã¯Node.jsã§ãããã npm ããã㯠yarn ã³ãã³ãã§ npm ããã±ãŒãžã®ã€ã³ã¹ããŒã«ãå¿
èŠã§ãã
#yarn
yarn add openai
#npm
npm i openai
ãã£ããã¢ããªãäœã
ããŠãä»åã®èšäºã§äœãã®ã¯ ChatGPTã¯ããŒã³ãã€ãŸãã¯ãã£ããã¢ããªã§ãããã£ãããããããã®ç»é¢ãçµã¿ç«ãŠãå¿ èŠããããŸãã
ãŸãåãã€ãã
ç»é¢ã«å
é§ããŠããã°ã®åãå®çŸ©ããŸãã以äžã®åã src/types.ts ã«ä¿åããŸãã
ãŠããªã³åã®åè
ã¯ãŠãŒã¶ãŒèªåã®çºèšã®ãã°ã§ãåŸè
ã¯è©±ãçžæïŒAIïŒã®çºèšã®ãã°ãšããŸãã
// src/types.ts
export type Log =
| {
imagePath?: never
name?: never
message: string
at: Date
}
| {
imagePath: string
name: string
message: string
at: Date
}
ãã°ã«æäœéå¿
èŠãªã®ã¯ã¢ã€ã³ã³ã衚瀺ããããã® imagePath ãšååã瀺ã name ãšã¡ãã»ãŒãžã®äžèº« message ãšãã€çºèšããã®ãã瀺ã at ã§ãããã
ãŠãŒãã£ãªãã£é¢æ°ãäœã
ãã£ããç»é¢ã«æéã衚瀺ããããã®ãŠãŒãã£ãªãã£é¢æ°ã src/utils.ts ã«çšæããŸãã
// src/utils.ts
export const formatTime = (date: Date) =>
`${date.getHours().toString().padStart(2, '0')}:${date
.getMinutes()
.toString()
.padStart(2, '0')}`
Date ãªããžã§ã¯ãããæã»åãåãåºããŠæžåŒæŽåœ¢ãããã®ã§ãã
èªåã®çºèšãæç»ãã MyMessage
ããããã¯å®éã«çºèšãæç»ããã³ã³ããŒãã³ããäœããŸãããããŸãã¯èªåã®çºèšã«äœ¿ã MyMessage ã§ãããã¡ã€ã«å㯠src/MyMessage.tsx ãšããŸãã
// src/MyMessage.tsx
import { FC } from 'react'
import { formatTime } from './utils'
type Props = {
message: string
at: Date
}
export const MyMessage: FC<Props> = ({ message, at }) => {
return (
<div className="flex gap-2 justify-end">
<div>
<div className="flex items-end gap-2">
<div>{formatTime(at)}</div>
<div className="mt-2 p-4 bg-[#5fcb76] rounded-2xl max-w-[400px] drop-shadow-md">
{message}
</div>
</div>
</div>
</div>
)
}
ããã§ã¯IEãæ»
ãã ãããã§äœ¿ããããã«ãªã£ã gap-2 ãæŽ»çšããŠããŸããflexã¬ã€ã¢ãŠããGridã¬ã€ã¢ãŠãã§ã¢ã€ãã ã®ééãããæãã«ããŠããã䟿å©CSSã§ãã
çžæã®çºèšãæç»ãã
ä»åºŠã¯è©±ãçžæã®çºèšãæç»ããã³ã³ããŒãã³ãã§ãããã¡ã㯠OtherMessage ã³ã³ããŒãã³ãã§ src/OtherMessage.tsx ã§ãã
// src/OtherMessage.tsx
import { FC } from 'react'
import { formatTime } from './utils'
type Props = {
imagePath: string
name: string
message: string
at: Date
}
export const OtherMessage: FC<Props> = ({ imagePath, name, message, at }) => {
return (
<div className="flex gap-2 justify-start">
<img src={imagePath} className="rounded-full w-12 h-12" />
<div>
<div className="font-bold">{name}</div>
<div className="flex items-end gap-2">
<div className="mt-2 p-4 bg-white rounded-2xl max-[400px] drop-shadow-md">
{message}
</div>
<div>{formatTime(at)}</div>
</div>
</div>
</div>
)
}
Messages
Logé
åã«æ ŒçŽããããã°ããèªåã®ãã®ãå€å®ããŠãåè¿°ã® MyMessage ãš OtherMessage ã§è¡šç€ºããã³ã³ããŒãã³ãã§ãããã¡ã€ã«å㯠src/Message.tsx ãšããŸãã
// src/Message.tsx
import { FC } from 'react'
import { MyMessage } from './MyMessage'
import { OtherMessage } from './OtherMessage'
import { Log } from './types'
type Props = {
logs: Log[]
}
export const Messages: FC<Props> = ({ logs }) => {
return (
<div className="flex flex-col gap-4">
{logs.map((log, i) => {
if (log.name) {
return <OtherMessage key={i} {...log} />
} else {
return <MyMessage key={i} {...log} />
}
})}
</div>
)
}
ãããŸã§ã§äŒè©±ã衚瀺ããããŒããæããŸãããããšã¯ç»é¢å šäœã®CSSãTSXãäœæããŠãå®éã«ãã°ãæç»ããŠã¿ãŸãããã
ãã°ãæç»ãã
ãã£ããã¢ããªã®è©±ãçžæã¯ãGPTã¡ããããšããååã«ããŸããGPTã¡ããã®ç»åã¯é©åœã«Stable Diffusionã«ãGPTã¡ããããšããæèšã§çæããŠããããŸããããã®ç»åã®é¡ãåãåºã㊠/public/gpt-chan.png ã«é
眮ããŠãããŸãã

ã㊠App.tsx ã§ãã°ãæç»ããŠã¿ãŸãããã
// src/App.tsx
import { FC } from 'react'
import { Log } from './types'
import { Messages} from "./Messages"
const App: FC = () => {
const logs: Log[] = [
{
name: 'GPTã¡ãã',
message: 'GPTã¡ããã§ããæ¥œããã話ãããŸãããã',
imagePath: '/gpt-chan.png',
at: new Date(),
},
{
message: 'ChatGPTã«ã€ããŠæããŠ',
at: new Date(),
},
]
return (
<div className="w-full h-screen flex justify-center">
<div className="pt-10 pb-12 w-full min-h-full px-5 max-w-[600px]">
<Messages logs={logs} />
</div>
</div>
)
}
export default App

ã©ããã§ã¿ããããªç»é¢ã«èŠããŸãããã£ãšæ°ã®ããã§ãã
äŒè©±ãçµã¿ç«ãŠã
ç»é¢ã¯ããããåºæ¥äžãã£ãã®ã§ã次ã¯äŒè©±ãçµã¿ç«ãŠãŠãããŸãããã
ãŸã completeText 颿°ãäœã
ååã®èšäº ã§ã¯ OpenAI GPT API ãå©ããŠèªç¶èšèªåŠçããããšããã®ããæåã§ãã£ãŠã¿ãŸããã
ããã³ãããè©Šãæ€èšŒéçšãªãå¥ã«ããã§ãããã®ãããããŸããããæµç³ã«ã¢ããªãšããŠã¯äœ¿ãç©ã«ãªããªãã®ã§ã䟿å©ã«äœ¿ãã completeText ãšãã颿°ãäœã£ãŠã¿ãŸãããã®é¢æ°ã¯ async ã䜿ã£ãéåæé¢æ°ã§ prompt ãäžãããããã®æååãè£å®ãããã®ãè¿ããšãã仿§ã«ããŸãã
ååã®èšäºã«ãæžããéããGPT API ã®çµæã¯
choices: [
{
text: 'ã©ãŒã¡ã³ã¯ãèé¡ãéèãªã©çŽ æã倿§ãªãããæ é€ã®å¥åº·ç¶æã«ã¯å€§å€æçã§ãããã ããããªãæ²¹ã£ãœããå¡©åãå€ããªã©ã®æ³šæãå¿
èŠã§ãã',
index: 0,
logprobs: null,
finish_reason: "stop"
}
],
}
ãšãããããªãªããžã§ã¯ãã§ãã
choices[0].text ã«ã¯è£å®ããããã¹ããå
¥ã£ãŠããŸãããã ããªãã·ã§ã³ã® max_tokens ãè¶
ããããã¹ããçæããããšããå Žå text 㯠max_tokens ã«åãŸãããã«ã¶ã€åãã«ããã finish_reason ã« length ãå
¥ã£ãŠè¿ã£ãŠããŸãã
ãã®å ŽåãçæããããšãããŸã§ãããã³ããã«æååçµåãããŠæãçŽã㊠finish_reason ã stop ã«ãªããŸã§ç¹°ãè¿ãå¿
èŠããããŸãã
ãŸã choices ã¯é
åã§ãè€æ°åž°ã£ãŠããå¯èœæ§ããããŸãã
GPTã®æ§è³ªãæãåºããŠãã ãããããã³ãããšããŠäžããæç« ã«ç¶ãããæååãè£å®ããè€æ°ã®å¯èœæ§ãããå Žåã©ã³ãã ã«ããããéžã³åããŸãããªãã·ã§ã³ã§ n: 2 以äžãæå®ãããšãããè€æ°ã®å¯èœæ§ã®ãã¡äžäœã®nåãè¿ã£ãŠããŸãã
åœç¶ã®ããšãªããããéãnåãããããç¹æ®ãªäºæ
ããªããã° n: 1 ã«åºå®ããŠããã°ããã§ãããã
ååã®èšäºã§ã¯Denoã ã£ãã®ã§ããã£ããåèçšãšããŠDenoçãèŠãŠãããŸãã
Denoç completeText
import 'https://deno.land/std@0.171.0/dotenv/load.ts'
import { Configuration, OpenAIApi } from 'npm:openai'
const apiKey = Deno.env.get('OPENAI_API_KEY')!
const configuration = new Configuration({ apiKey })
const openai = new OpenAIApi(configuration)
/** prompt ã§äžããæç« ãè£å®ãã */
export const completeText = async (prompt: string): Promise<string> => {
const { data } = await openai.createCompletion({
prompt,
model: 'text-davinci-003',
max_tokens: 200,
})
const choice = 0
const isContinue = data.choices[choice].finish_reason === 'length'
const text = data.choices[choice].text || ''
if (!isContinue) {
return prompt + text
}
return completeText(prompt + text)
}
Denoçã¯ãã ã®é¢æ°ã§ãã
-
openai.createCompletionã§çµæãååŸãã - ååŸããçµæã®
finish_reasonãlengthãªãã°ååž°åŒã³åºããç¹°ãè¿ã
Reactç useCompleteText
ä»åäœãã¢ããªã§ã¯ãŠãŒã¶ãŒãå ¥åããAPIããŒã䜿ãããã«ããŸãã
ãŠã§ããµãŒãã¹æäŸè ãååŸããAPIããŒã䜿ãå ŽåããŠã§ãã¢ããªã®ã¿ã§äœããšãAPIããŒããŠãŒã¶ãŒã«äžžèŠãã«ãªã£ãŠããŸãããã®ããŒã䜿ã£ãŠãããã§ãOpenAI APIã«ã¢ã¯ã»ã¹ã§ããŠããŸã£ãŠå°ããããä»åã¯ãŠãŒã¶ãŒãå ¥åããããŒã䜿ããŸãã
æ®éã«ãŠã§ããµãŒãã¹ãšããŠããå Žåã¯ãããã¯ãšã³ãåŽã«APIããŒã眮ããšè¯ãã§ãããã
ãã®ããã« useCompleteText ãšããã«ã¹ã¿ã ããã¯é¢æ°ãäœããŸãããã仿§ãšããŠã¯Denoçãšã¯éã£ãŠ apiKey ãæå®ãããŠããã° completeText 颿°ã«è©²åœãããã®ãè¿ã颿°ãšããŸãããã¡ã€ã«å㯠src/completeText.ts ãšããŸãã
// src/completeText.ts
import { Configuration, OpenAIApi } from 'openai'
import { useMemo } from 'react'
/** prompt ã§äžããæç« ãè£å®ãã completeText ãè¿ã */
export const useCompleteText = ({ apiKey }: { apiKey: string }) => {
return useMemo(() => {
if (!apiKey) {
return undefined
}
const configuration = new Configuration({ apiKey })
const openai = new OpenAIApi(configuration)
const completeText = async (prompt: string): Promise<string> => {
const { data } = await openai.createCompletion({
prompt,
model: 'text-davinci-003',
max_tokens: 200,
})
const choice = 0
const isContinue = data.choices[choice].finish_reason === 'length'
const text = data.choices[choice].text || ''
if (isContinue) {
return completeText(prompt + text)
} else {
return prompt + text
}
}
return completeText
}, [apiKey])
}
ãã£ãããããããã³ãããäœã
OpenAI ãäŸç€ºãããã£ãããµã³ãã« ã®ããã³ããã¯ä»¥äžã§ããããã³ããã¯LLMã«èªç¶èšèªåŠçãããŠãããããã«äžããæååã®ããšã§ãã
The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.
Human: Hello, who are you?
AI: I am an AI created by OpenAI. How can I help you today?
Human: I'd like to cancel my subscription.
AI:
倧äœããã§åé¡ã¯ãªãã§ãããããã ãã ãšèšæ¶ãæã€ããšãã§ããŸããã
ããã§äŒè©±ããŒã¿ãå·®ã蟌ãããã«ãããã®ã TypeScript ã§è¡šçŸããŠã¿ãŸãã
const prompt = `The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.
${memory.trim()}
Human: ${userInputText}
AI: `
memory ã¯äŒè©±å±¥æŽãåãã string ã§ userInputText ã¯ãŠãŒã¶ãŒãå
¥åããããã¹ãã§ string ã§ãã
æãåå§çãªããæ¹ã§ã¯ãããŸãããèšæ¶ãå®çŸããããã³ããã«ãªããŸãããGPTææ°ã¢ãã«ã§ãã text-davinci-003 ã ãšåŠçã§ããããŒã¯ã³æ°ã¯ 4000 ãæå€§ã§ããè±èªã ãš1ã¯ãŒãã1ã3ããŒã¯ã³ãããã§ãããæ¥æ¬èªã ãšå€§éæã«ãã£ãŠ4000æåã«æºããªããããã ãšæã£ãŠããã°ãããšæããŸãããã¿ã«èšæ¶ãããšãã®4000ããŒã¯ã³ãèšæ¶ã®éçãšããããšã«ãªããŸãã
ä»åã®èšäºã§ã¯åãäžããŸããããèšæ¶ã工倫ãããã¯ããã¯ããããŸããGPTã«äŒè©±ã®å±¥æŽãèŠçŽããŠãããããšã§ããŒã¯ã³æ°ãæžããã°è¯ãã®ã§ãã
å ¥åã³ã³ããŒãã³ããäœæãã
ãŠãŒã¶ãŒããæåãå
¥åããã³ã³ããŒãã³ã㯠textarea ã§æååãå
¥åãã€ã€ Enter ãæŒãããéä¿¡ãã仿§ãšããŸãã onKeyDown ã§ KeyboardEvent<HTMLTextAreaElement> ãåŠçããŸãã
ãã®ãšã IME ã§å ¥åäžã¯ Enter ãç¡èŠãã仿§ã«ããªããš IME ã®ç¢ºå®ã®ããã« Enter ãæŒããæã®å ¥åãæŸã£ãŠããŸããŸãããã€ã¯ ChatGPT ã®åæã®é ã«åããã°ããããŸããã
// src/Input.tsx
import { FC, useCallback, useState } from 'react'
type Props = {
onSubmit: (text: string) => void
}
export const Input: FC<Props> = ({ onSubmit }) => {
// å
¥åæåå
const [userInputText, setUserInputText] = useState('')
// IME ã§å
¥åäžãã©ããã®ãã©ã°
const [isComposition, setIsComposition] = useState(false)
const handleKeyDown = useCallback(
(ev: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (ev.code === 'Enter' && !isComposition && userInputText.trim()) {
ev.preventDefault()
onSubmit(userInputText.trim())
setUserInputText('')
}
},
[isComposition, onSubmit, userInputText]
)
const handleChange = useCallback(
(ev: React.ChangeEvent<HTMLTextAreaElement>) => {
setUserInputText(ev.target.value)
},
[]
)
return (
<textarea
className="w-full p-2 max-w-[800px] rounded-lg h-24 resize-none border-0 outline-none drop-shadow-lg"
onChange={handleChange}
onKeyDown={handleKeyDown}
onCompositionStart={() => setIsComposition(true)}
onCompositionEnd={() => setIsComposition(false)}
value={userInputText}
/>
)
}
ãã£ããã¢ããªãçµã¿ç«ãŠã
ããŠå€§äœã®ãã®ãæã£ãŠããã®ã§ãããšã¯ãã£ããã¢ããªãçµã¿ç«ãŠãŸãããã
ãã°ãçµã¿ç«ãŠããã«ããŒãäœã
AIã®ãã°ãäœã createAiLog ãšèªåã®ãã°ãäœã createMyLog ãäœããŸãã
const AI_NAME = 'GPTã¡ãã'
const AI_IMAGE_PATH = '/gpt-chan.png'
export const createAiLog = (message: string): Log => {
return {
name: AI_NAME,
imagePath: AI_IMAGE_PATH,
message,
at: new Date(),
}
}
export const createMyLog = (message: string): Log => {
return {
message,
at: new Date(),
}
}
ç¹çãã¹ãããšã¯ç¹ã«ãªãã§ãããã message ã® string ãåãåã£ãŠ Log ãè¿ãã ãã®é¢æ°ã§ãã
ãã£ããã¢ããªã®ã¹ããŒããæºåãã
ãããã㯠src/App.tsx ã® App ã³ã³ããŒãã³ãã®äžèº«ã«ãªããŸãã
ãŸãã¯ã¹ããŒããæºåããŸãã
// æç»ãããã°ããŒã¿
const [logs, setLogs] = useState<Log[]>([
createAiLog('APIããŒãå
¥åããŠãã ããã'),
])
// completeTextãå®è¡ããããã®OpenAI APIããŒ
const [apiKey, setApiKey] = useState('')
// completeText颿°
const completeText = useCompleteText({ apiKey })
// äŒè©±ã®èšæ¶
const [memory, setMemory] = useState('')
ãã°ã®åæå€ãšããŠãGPTã¡ããã«ãAPIããŒãå ¥åããŠãã ããããšããçºèšãå ¥ããŠããŸãã
ãŠãŒã¶ãŒãæååãå ¥åãããšãã®ãã³ãã©ãäœã
handleSubmit 颿°ã¯ Input ã³ã³ããŒãã³ãã® onSubmit ã«é£ãããããã®ã³ãŒã«ããã¯ã§ãã
const handleSubmit = useCallback(
async (text: string) => {
const appendLog = (log: Log) => {
setLogs((prev) => [...prev, log])
}
const appendMemory = (name: string, message: string) => {
setMemory((prev) => `${prev}${name}: ${message.trim()}\n`)
}
const sayAi = (message: string): void => {
appendLog(createAiLog(message))
appendMemory('AI', message)
}
logã¹ããŒãã«ãã°ã远å ããããã® appendLog 颿°ãšãäŒè©±ã®èšæ¶ã远å ãã appendMemory 颿°ãšãAIãçºèšãããšãã«äœ¿ã sayAi 颿°ãçšæããŸããã
if (!apiKey) {
setApiKey(text)
sayAi('GPTã¡ããã§ãããªãã§ãèããŠãã ãããæ¥œããã話ãããŸãããã')
return
}
APIããŒãæªå
¥åã®ãšãã«ããŠãŒã¶ãŒãå
¥åããã¡ãã»ãŒãžã¯ãAPIããŒãšããŠæ±ããŸããã²ãšãŸãæ£ãããã®ã§ãããšä»®å®ããäžã§äŒè©±ãå§ããããã«GPTã¡ããã«çºèšããããŸããããã§æ©æãªã¿ãŒã³ãããããããã®æç¹ã§ã¯ completeText ã¯å®è¡ããŸããã
if (!completeText) {
return
}
completeText ã undefined ã ã£ãå Žåã«ã¯äœãããªãããšããã³ãŒããå
¥ããŠãŸãããã®ã³ãŒãã¯é çªãéèŠãšãªããŸããAPIããŒããªããšãã®åŠçã®é åºãèããå¿
èŠããããŸãã
æå㯠apiKey ã空æååã§ãã completeText ã undefined ã§ãããã®ç¶æ
ã§ handleSubmit ã§æååãå
¥åããããšãŸã setApiKey(text) ãå®è¡ããããã®çµæ apiKey ã空æååãããªããªã completeText ã«é¢æ°ãå
¥ããŸãã
-
apiKeyã空æååã§ããcompleteTextãundefined - ãŠãŒã¶ãŒãæååãå
¥åããŠ
setApiKey(text)ãå®è¡ããã -
apiKeyã«APIããŒãå ¥ã£ãŠcompleteTextã«é¢æ°ãå ¥ã
apiKey ã空ãããªããŠã〠completeText ã空ãšããã¿ã€ãã³ã°ã¯ç¡ãã¯ãã§ãããå®å
šçãšããŠå
¥ããŠãããæ¹ãè¯ãã§ãããã
const prompt = `The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.
${memory.trim()}
Human: ${text}
AI: `
console.log(prompt)
appendLog(createMyLog(text))
appendMemory('Human', text)
const response = await completeText(prompt).catch(() => {
setApiKey('')
appendLog(createAiLog('æå¹ãªAPIããŒãå
¥åããŠãã ããã'))
})
response && sayAi(response.slice(prompt.length))
},
[apiKey, completeText, memory]
)
console.log(prompt) ããŠãããšããã³ããã®ãããã°ãããããã§ãã
ããšã¯ãã®ããã³ããã completeText ãå®è¡ã㊠GPT ã«æããŸãã
æåããæã¯æååãè¿ã£ãŠããŠã倱æããå Žå㯠.catchãã®äžãå®è¡ãããŠæååã空ã«ãªããŸãããã®ãã response ã空ãããªãã£ãã sayAi ãå®è¡ããããã«ããŠããŸãã
response.slice(prompt.length) ãšããŠããã®ã¯ãè¿ã£ãŠããæååã¯ããã¹ããè£å®ãããå
šäœãè¿ã£ãŠããããã§ãã
ç»é¢ãäœã
ãã£ãå®éšã§äœã£ãç»é¢ã«ãå ¥åãŠã£ã³ããŠã®è¿œå ãšæšªå¹ ã®èª¿æŽããããã®ã§ã ãã¶ãããã£ãŠããŸãã
return (
<div className="flex justify-center">
<div className="max-w-[800px] w-full pt-10">
<Messages logs={logs} />
<div className="w-full h-40" />
</div>
<div className="fixed bottom-0 left-0 w-full h-40 flex justify-center items-center bg-transparent">
<Input onSubmit={handleSubmit} />
</div>
</div>
)
}
export default App
<div className="w-full h-40" /> ã§å
¥åãŠã£ã³ããŠã®ããã«ç©ºçœé åãèšå®ã㊠<div className="fixed bottom-0 left-0 w-full h-40 flex justify-center items-center bg-transparent"> ã§ç»é¢ã®äžã«å
¥åé åãåºå®ããŠããŸãã

ã¡ãããšOpenAI GPT APIã«ããã³ãããæããŠãã®è¿çãæç»ããŠããŸãã
æåŸã«
ä»åäœã£ãã®ãšåæ§ã®ãã®ã https://github.com/erukiti/chatgpt-clone ã«ãŠå ¬éããŠããŸãã
æ¹ä¿®æ¡
- èšæ¶ã®æã¡æ¹ã工倫ãã
- ããã³ããã工倫ããŠãã£ã©ä»ãããã
- äŒè©±ã®èšé²ã localStorage ãªã©ã§ä¿æã§ããããã«ãã
- React Suspense ã§AIã®çºèšäžã¢ãã¡ãŒã·ã§ã³ãå®è£ ãã
- completeText ãä¿®æ£ããŠãããŒã¯ã³æ°ãååŸã§ããããã«ããŠããããè²»çšãäºæž¬ãã
ãªã©ãæ¹ä¿®æ¡ãšããŠèãããããšããã§ããããã
次ã®èšäº
次ã®èšäºã§ã¯ãã£ãšããã³ããã«èžã¿èŸŒãã§ã¿ãããšæã£ãŠããŸãã
Discussion