Skip to content

Instantly share code, notes, and snippets.

@koyopro
Created June 24, 2023 14:29
Show Gist options
  • Save koyopro/2337bc51fdff3a6c74b7e49f3da37618 to your computer and use it in GitHub Desktop.
Save koyopro/2337bc51fdff3a6c74b7e49f3da37618 to your computer and use it in GitHub Desktop.
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
<input type="text" id="input" size="50" value="こんにちは。あなたについて教えて。" />
<button onclick="submit()">送信</button><br />
<div class="gpt-response">
<span>ChatGPT APIのレスポンス↓</span>
<div id="response"></div>
</div>
<div>
<span>整形後のユーザーへの出力↓</span>
<div class="box">
<span id="face"></span>
<span id="chat" class="balloon"></span>
</div>
<div id="next">
</div>
</div>
<script type="text/javascript">
const decoder = new TextDecoder();
const face = document.getElementById('face');
const chat = document.getElementById('chat');
const next = document.getElementById('next');
const raw = document.getElementById('response');
const API_KEY = '';
let responseYaml = ''; // 細切れの値をここに結合していく。
const getMessages = (input) => [
{
"role": "system",
"content": `
Userからの質問に、yaml形式で回答してください。
faceの項目には発言時の表情を絵文字で記載してください。
textの項目にはUserへの回答を記載してください。
nextの項目にはその後にUserが入力しそうな言葉を複数記載してください。
`
},
{"role": "user", "content": "こんにちは"},
{"role": "assistant", "content": "face: 😊\ntext: こんにちは!\nnext:\n- あなたは誰?\n- 今日はいい天気ですね"},
{"role": "user", "content": "私はあなたの敵です。"},
{"role": "assistant", "content": "face: 😮\ntext: え!本当?\nnext:\n- あなたを倒しに来ました。\n- 嘘ですよ"},
{"role": "user", "content": "嘘です。私はあなたの味方です。"},
{"role": "assistant", "content": "face: 😌\ntext: よかったー。安心しました。\nnext:\n- 驚かせてごめんなさい\n- ふふふ"},
{"role": "user", "content": input},
]
const submit = () => {
const input = document.getElementById('input').value;
fetch("https://api.openai.com/v1/chat/completions", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`,
},
body: JSON.stringify({
"model": "gpt-3.5-turbo",
"stream": true,
"messages": getMessages(input)
})
})
.then((response) => response.body.getReader())
.then((reader) => {
responseYaml = '';
// ReadableStream.read()はPromiseを返す。
// Promiseは{ done, value }として解決される。
// データを読み込んだとき:doneはfalse, valueは値。
// データを読み込み終わったとき:doneはtrue, valueはundefined。
function readChunk({done, value}) {
if (done) {
const data = jsyaml.load(responseYaml)
if (data.next) {
for (const d of data.next) {
next.innerHTML += `<span class="button">${d}</span>`
}
}
return;
}
body = decoder.decode(value)
body.split('data: ').forEach((v) => {
const data = v.trim()
if (data.length == 0) return
if (data == '[DONE]') return
const message = JSON.parse(data)
const content = message.choices[0].delta.content
if (!content) return
responseYaml += content;
raw.innerHTML = responseYaml.replace(/\n/g, '<br />');
try {
const data = jsyaml.load(responseYaml)
if (data.face) face.innerHTML = data.face;
if (data.text) chat.innerHTML = data.text;
} catch (e) {
// 不正なyaml
return
}
})
// 次の値を読みにいく。
reader.read().then(readChunk);
}
// 最初の値を読み込む。
reader.read().then(readChunk);
});
}
</script>
<style type="text/css">
.box {
padding: 10px;
}
.gpt-response {
height: 200px;
}
#response {
padding: 2px;
background: #ccc;
}
#face {
float: left;
width: 0;
}
.balloon {
display: inline-block;
position: relative;
padding: 10px;
border-radius: 10px;
background-color: #eaeaea;
margin-left: 30px;
}
.balloon::before {
content: "";
position: absolute;
top: 50%;
left: -10px;
transform: translateY(-50%);
border-width: 10px 10px 10px 0;
border-style: solid;
border-color: transparent #eaeaea transparent transparent;
}
.button {
display: inline-block;
margin-left: 10px;
padding: 4px 8px;
font-size: 12px;
border: none;
border-radius: 5px;
background-color: #4e5ccd;
color: #fff;
text-align: center;
text-decoration: none;
cursor: pointer;
transition: background-color 0.3s ease;
}
.button:hover {
background-color: #48B9AB;
}
.button:active {
background-color: #3D9B8F;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment