見出し画像

AIチャットボットのAPIをつなぐコード

エンジニア用
AIでチャットボットを作る時のコードです。

<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>●●タイトル●●</title>
    <style>
        body {
            background-color: #004896;
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            height: 100vh;
            box-sizing: border-box;
        }
        .container {
            background-color: #004896; /* 全体の背景色 */
            flex: 1;
            display: flex;
            flex-direction: column;
            padding: 10px;
            box-sizing: border-box;
            overflow: hidden;
        }
        .form-group {
            display: flex;
            align-items: center;
            background-color: #004896; /* 入力エリアの背景色 */
            padding: 10px 0; /* 縦のパディングを10pxに設定、横は0 */
            border-radius: 4px;
            margin-bottom: 0;
            justify-content: space-between; /* 入力欄を最大限広げてボタンを右端に配置 */
        }
        #question {
            flex-grow: 1; /* 入力欄をできるだけ広げる */
            height: 50px;
            font-size: 16px;
            padding: 10px 0.5em; /* 縦のパディングを10px、横を0.5文字分に設定 */
            box-sizing: border-box;
            border: 1px solid #ccc;
            border-radius: 4px;
            margin-right: 10px; /* ボタンとの間隔を調整 */
            color: gray; /* プレースホルダー用の色 */
        }
        #question:focus {
            color: black; /* 入力時に黒に変更 */
        }
        #response {
            flex: 1;
            font-size: 16px;
            padding: 10px;
            box-sizing: border-box;
            border: 1px solid #ccc;
            border-radius: 4px;
            background-color: #ffffff; /* 出力エリアの背景色 */
            white-space: pre-wrap;
            overflow-y: auto;
            margin-bottom: 10px;
        }
        .citation {
            margin-top: 10px;
            padding: 10px;
            border-top: 1px solid #ccc;
        }
        .input-container {
            background-color: #004896; /* 入力エリア全体の背景色 */
            padding: 10px 0;
            box-sizing: border-box;
        }
        .user-query, .ai-response {
            margin-bottom: 10px;
        }
        .user-query {
            color: blue;
        }
        .ai-response {
            color: black;
        }
        button {
            height: 50px;
            font-size: 16px;
            padding: 10px 20px;
            cursor: pointer;
            border: 1px solid #ccc;
            border-radius: 4px;
            background-color: #ffeb3b; /* ボタンの背景色を黄色に変更 */
            color: black; /* テキストの色を黒に変更 */
            font-weight: bold; /* テキストを太字に変更 */
            margin-left: auto; /* ボタンを右端に寄せる */
        }
    </style>
    <script>
        function clearPlaceholder() {
            const questionField = document.getElementById('question');
            if (questionField.value === "ここにいれて") {
                questionField.value = "";
                questionField.style.color = "black";
            }
        }

        function restorePlaceholder() {
            const questionField = document.getElementById('question');
            if (questionField.value === "") {
                questionField.value = "ここにいれて";
                questionField.style.color = "gray";
            }
        }

        async function getAIResponse() {
            const query = document.getElementById('question').value;
            const responseField = document.getElementById('response');

            // ユーザーの質問を表示
            const userQueryDiv = document.createElement('div');
            userQueryDiv.className = 'user-query';
            userQueryDiv.innerHTML = `あなた:${query}`;
            responseField.appendChild(userQueryDiv);

            const data = {
                "inputs": {},
                "query": query,
                "response_mode": "streaming",
                "conversation_id": "",
                "user": "abc-123",
                "files": [
                    {
                        "type": "image",
                        "transfer_method": "remote_url",
                        "url": "https://cloud.dify.ai/logo/logo-site.png"
                    }
                ]
            };

            try {
                const response = await fetch('https://●●●●●●●●●●/chat-messages', {
                    method: 'POST',
                    headers: {
                        'Authorization': '●●API●●Bearer app-●●',
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });

                if (!response.ok) {
                    throw new Error(`エラー: ${response.status} ${response.statusText}`);
                }

                const reader = response.body.getReader();
                const decoder = new TextDecoder('utf-8');
                let result = '';
                const aiResponseDiv = document.createElement('div');
                aiResponseDiv.className = 'ai-response';

                let isFirstChunk = true;  // 最初のチャンクかどうかを判定する

                while (true) {
                    const { done, value } = await reader.read();
                    if (done) break;
                    result += decoder.decode(value, { stream: true });

                    const jsonChunks = result.split('data: ').filter(chunk => chunk.trim() !== '');
                    result = '';
                    jsonChunks.forEach(chunk => {
                        try {
                            const jsonData = JSON.parse(chunk);
                            if (jsonData.answer) {
                                let formattedAnswer = jsonData.answer.replace(/\n/g, '<br>');
                                if (isFirstChunk) {
                                    aiResponseDiv.innerHTML = `●名前●:${formattedAnswer}`;
                                    responseField.appendChild(aiResponseDiv);
                                    isFirstChunk = false;
                                } else {
                                    aiResponseDiv.innerHTML += formattedAnswer;
                                }
                                // スクロールを下に
                                responseField.scrollTop = responseField.scrollHeight;
                            }
                            if (jsonData.retriever_resources && jsonData.retriever_resources.length > 0) {
                                jsonData.retriever_resources.forEach(resource => {
                                    const citationElement = document.createElement('div');
                                    citationElement.className = 'citation';
                                    citationElement.innerHTML = `引用元: <a href="${resource.url}" target="_blank">${resource.url}</a>`;
                                    aiResponseDiv.appendChild(citationElement);
                                });
                            }
                        } catch (e) {
                            console.error('JSONの解析エラー:', e);
                        }
                    });
                }

                // 全てのデータが読み込まれた後に置換処理を実行
                aiResponseDiv.innerHTML = aiResponseDiv.innerHTML.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');

            } catch (error) {
                const errorDiv = document.createElement('div');
                errorDiv.className = 'ai-response';
                errorDiv.innerHTML = `エラー: ${error.message}`;
                responseField.appendChild(errorDiv);
                // スクロールを下に
                responseField.scrollTop = responseField.scrollHeight;
            }
        }

        function clearInput() {
            document.getElementById('question').value = 'ここにいれて';
            document.getElementById('question').style.color = "gray";
        }

        // ページロード時に初期メッセージとプレースホルダーを設定
        window.onload = function() {
            const responseField = document.getElementById('response');
            const initialMessageDiv = document.createElement('div');
            initialMessageDiv.className = 'ai-response';
            initialMessageDiv.innerHTML = '●●スタートメッセージ●●例、ボウサイ:私は防災のデータを大量学習した専門家のAIの「ボウサイ」です。南海トラフなど、防災や地震の事を私に相談してください。';
            responseField.appendChild(initialMessageDiv);
            // スクロールを下に
            responseField.scrollTop = responseField.scrollHeight;

            // プレースホルダーを設定
            const questionField = document.getElementById('question');
            questionField.value = "ここにいれて";
            questionField.style.color = "gray";
            questionField.addEventListener('focus', clearPlaceholder);
            questionField.addEventListener('blur', restorePlaceholder);
        };
    </script>
</head>
<body>
    <div class="container">
        <div id="response"></div>
        <div class="input-container">
            <div class="form-group">
                <textarea id="question" name="question" rows="2" required></textarea>
                <button onclick="getAIResponse(); clearInput();">●●例、相談する●●</button>
            </div>
        </div>
    </div>
</body>
</html>

いいなと思ったら応援しよう!

村井宗明(ITエンジニア・元文部科学大臣政務官)
ありがとうございます。 よろしくお願いします。