-
-
Save trtd56/fe19ecb333a3b5d9a465e39783e3c0ce to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="ja"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>APIストリーミング表示</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* Interフォントを適用 */ | |
body { | |
font-family: 'Inter', sans-serif; | |
} | |
/* スクロール可能な出力エリア */ | |
#output { | |
height: 400px; /* 高さを固定 */ | |
overflow-y: auto; /* 縦方向のスクロールを有効化 */ | |
border: 1px solid #e5e7eb; /* 境界線 */ | |
padding: 1rem; /* 内側余白 */ | |
border-radius: 0.375rem; /* 角丸 */ | |
background-color: #f9fafb; /* 背景色 */ | |
white-space: pre-wrap; /* 改行とスペースを保持 */ | |
word-wrap: break-word; /* 長い単語を折り返す */ | |
} | |
</style> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet"> | |
</head> | |
<body class="bg-gray-100 p-8"> | |
<div class="max-w-2xl mx-auto bg-white p-6 rounded-lg shadow-md"> | |
<h1 class="text-2xl font-bold mb-4 text-center text-gray-800">dLLM Demo</h1> | |
<div class="mb-4"> | |
<label for="apiKey" class="block text-sm font-medium text-gray-700 mb-1">APIキー:</label> | |
<input type="password" id="apiKey" name="apiKey" placeholder="ここにAPIキーを入力してください" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
<p class="text-xs text-gray-500 mt-1">APIキーはこのブラウザ内でのみ使用され、外部には送信されません。</p> | |
</div> | |
<div class="mb-4"> | |
<label for="question" class="block text-sm font-medium text-gray-700 mb-1">質問:</label> | |
<input type="text" id="question" name="question" value="" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
</div> | |
<button id="startButton" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150 ease-in-out disabled:opacity-50"> | |
生成 | |
</button> | |
<div id="loading" class="mt-4 text-center text-gray-600" style="display: none;"> | |
<svg class="animate-spin h-5 w-5 mr-3 inline-block text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> | |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> | |
</svg> | |
読み込み中... | |
</div> | |
<div id="error" class="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded-md" style="display: none;"></div> | |
<h2 class="text-xl font-semibold mt-6 mb-2 text-gray-700">応答:</h2> | |
<div id="output" class="whitespace-pre-wrap break-words text-gray-800"></div> | |
</div> | |
<script> | |
const startButton = document.getElementById('startButton'); | |
const outputDiv = document.getElementById('output'); | |
const apiKeyInput = document.getElementById('apiKey'); | |
const questionInput = document.getElementById('question'); | |
const loadingDiv = document.getElementById('loading'); | |
const errorDiv = document.getElementById('error'); | |
// 1秒待機する非同期関数 | |
function sleep(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
startButton.addEventListener('click', async () => { | |
const apiKey = apiKeyInput.value.trim(); | |
const question = questionInput.value.trim(); | |
const apiUrl = 'https://api.inceptionlabs.ai/v1/chat/completions'; | |
if (!apiKey) { | |
showError('APIキーを入力してください。'); | |
return; | |
} | |
if (!question) { | |
showError('質問を入力してください。'); | |
return; | |
} | |
// UIリセット | |
outputDiv.textContent = ''; // 出力エリアをクリア | |
loadingDiv.style.display = 'block'; // ローディング表示 | |
errorDiv.style.display = 'none'; // エラー非表示 | |
startButton.disabled = true; // ボタンを無効化 | |
// *** 変更点: 全文保持変数は不要になったため削除 *** | |
// let fullResponseText = ''; | |
try { | |
const response = await fetch(apiUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${apiKey}` // APIキーを設定 | |
}, | |
body: JSON.stringify({ | |
model: "mercury-coder-small", // モデル名 | |
messages: [ | |
{ "role": "user", "content": question } // ユーザーの質問 | |
], | |
max_tokens: 200, | |
stream: true, | |
diffusing: true | |
}) | |
}); | |
if (!response.ok) { | |
const errorData = await response.json().catch(() => ({ message: `HTTPエラー: ${response.status}` })); | |
throw new Error(errorData.message || `HTTPエラー: ${response.status}`); | |
} | |
if (!response.body) { | |
throw new Error('レスポンスボディがありません。'); | |
} | |
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); | |
while (true) { | |
const { value, done } = await reader.read(); | |
if (done) { | |
console.log('ストリームが完了しました。'); | |
break; | |
} | |
const lines = value.split('\n'); | |
let chunkContent = ''; // 現在のチャンクで受信した内容を保持 | |
lines.forEach(line => { | |
if (line.startsWith('data: ')) { | |
try { | |
const jsonStr = line.substring(6).trim(); | |
if (jsonStr && jsonStr !== '[DONE]') { | |
const jsonData = JSON.parse(jsonStr); | |
const content = jsonData?.choices?.[0]?.delta?.content ?? ''; | |
if (content) { | |
chunkContent += content; // チャンクの内容を一時変数に追加 | |
} | |
} | |
} catch (e) { | |
console.error('JSONのパースに失敗:', line, e); | |
} | |
} | |
}); | |
// *** 変更点: 現在のチャンク内容のみを表示 *** | |
if (chunkContent) { | |
// fullResponseText += chunkContent; // 全文への追加はしない | |
outputDiv.textContent = chunkContent; // 現在のチャンクの内容で表示を上書き | |
outputDiv.scrollTop = outputDiv.scrollHeight; // 自動スクロール | |
} else { | |
// 内容が空のチャンクが来た場合、表示をクリアする(必要に応じて) | |
// outputDiv.textContent = ''; | |
} | |
// 各チャンク処理後に1秒待機 | |
await sleep(1000); | |
} | |
} catch (error) { | |
console.error('ストリーミングエラー:', error); | |
showError(`エラーが発生しました: ${error.message}`); | |
} finally { | |
loadingDiv.style.display = 'none'; | |
startButton.disabled = false; | |
} | |
}); | |
// エラーメッセージ表示関数 | |
function showError(message) { | |
errorDiv.textContent = message; | |
errorDiv.style.display = 'block'; | |
loadingDiv.style.display = 'none'; | |
startButton.disabled = false; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment