ระบบ Chat กับ Deepseek ด้วย API

โค้ดชุดนี้เป็น HTML, CSS และ JavaScript ที่สร้างหน้าเว็บสำหรับการโต้ตอบกับ API ของ DeepSeek ซึ่งเป็น AI Chatbot โดยผู้ใช้สามารถพิมพ์ข้อความ และรับการตอบกลับจาก DeepSeek ได้ หน้าเว็บนี้ยังมีการตั้งค่า API Key, เลือก Model และแสดงจำนวน Token ที่ใช้ รวมถึงดึงข้อมูลบทความจาก NConnect Insights Feed มาแสดงผลด้วย

ส่วนประกอบหลัก

  1. HTML (โครงสร้างหน้าเว็บ):
    • <header>: ส่วนหัวของหน้าเว็บ ประกอบด้วยโลโก้ (NConnect) และเมนูหลัก (Home, About, Services, Insights, Contact)
    • <div class="container">: ส่วนหลักของเนื้อหา แบ่งเป็น
      • <div id="settings-container">: ส่วนการตั้งค่า
        • ช่องใส่ API Key (<input type="password" id="api-key">)
        • ปุ่ม Save (<button onclick="saveSettings()">)
        • ตัวเลือก Model (<select id="model-select">)
      • <div id="chat-container">: ส่วนการสนทนา
        • <div id="messages">: แสดงข้อความสนทนา
        • <div id="input-container">: ช่องพิมพ์ข้อความ (<input type="text" id="message-input">) และปุ่ม Generate (<button id="generate-btn" onclick="sendMessage()">)
      • <h1 id="insights-title">: หัวข้อ NConnect Insights Feed
      • <div id="feed-container">: แสดงบทความจาก NConnect Insights
  2. CSS (การตกแต่งหน้าเว็บ):
    • จัดรูปแบบหน้าเว็บให้สวยงาม, กำหนดสี, ฟอนต์, ระยะห่าง, และการจัดวางองค์ประกอบต่างๆ
    • ใช้ flexbox และ grid ในการจัดวางเลย์เอาต์
    • มีสไตล์สำหรับข้อความผู้ใช้ (user-message), ข้อความบอท (bot-message), ข้อความที่กำลังประมวลผล (generating), ส่วนแสดงโค้ด (code-section), ปุ่มคัดลอก (copy-btn)
    • มีสไตล์สำหรับแสดงบทความ (post), หัวข้อบทความ (post-title), ลิงก์บทความ (post-link) และรูปภาพประกอบ (post-image)
  3. JavaScript (การทำงานของหน้าเว็บ):
    • loadSettings(): โหลด API Key และ Model ที่บันทึกไว้จาก localStorage
    • saveSettings(): บันทึก API Key และ Model ลงใน localStorage
    • sendMessage(): ส่งข้อความไปยัง DeepSeek API และรับการตอบกลับ
      • ตรวจสอบว่าใส่ API Key แล้วหรือยัง
      • แสดงข้อความ “Generating…” ระหว่างรอการตอบกลับ
      • ส่งคำขอ POST ไปยัง https://api.deepseek.com/v1/chat/completions
      • รับการตอบกลับในรูปแบบ JSON
      • แสดงข้อความตอบกลับและจำนวน Token ที่ใช้
    • appendGeneratingMessage(): เพิ่มข้อความ “Generating…” ลงใน <div id="messages">
    • processResponse(text): ประมวลผลข้อความตอบกลับจาก DeepSeek
      • แยกข้อความออกเป็นส่วนๆ ตามโค้ดบล็อก (“`)
      • คืนค่าเป็นอาร์เรย์ของ object ที่มี type (code หรือ text) และ content
    • appendMessage(sender, text, tokensUsed): เพิ่มข้อความลงใน <div id="messages">
      • แยกข้อความตาม type (code หรือ text) และแสดงผลตามรูปแบบที่กำหนด
      • แสดงจำนวน Token ที่ใช้ (สำหรับข้อความจากบอท)
      • มีปุ่ม “Copy All” สำหรับคัดลอกข้อความทั้งหมด
      • มีปุ่ม “Copy Code” สำหรับคัดลอกโค้ดบล็อก
    • formatText(text): จัดรูปแบบข้อความ เช่น ตัวหนา, ตัวเอียง, โค้ด
    • copyCode(button): คัดลอกโค้ดจากโค้ดบล็อก
    • การดึงข้อมูลจาก NConnect Insights Feed:
      • ใช้ fetch ดึงข้อมูลจาก https://insights.nconnect.asia/wp-json/wp/v2/posts
      • แสดงบทความแต่ละบทความใน <div class="post">
      • แสดงรูปภาพ (ถ้ามี), หัวข้อบทความ, และลิงก์ “Read more”
    • Event Listeners:
      • กด Enter ในช่องพิมพ์ข้อความ (#message-input) จะเรียก sendMessage()
      • เปลี่ยน Model ใน #model-select จะอัปเดต selectedModel

วิธีการใช้งาน

  1. ใส่ API Key: ใส่ DeepSeek API Key ของคุณในช่อง “DeepSeek API Key” และกดปุ่ม “Save”
  2. เลือก Model: เลือก Model ที่ต้องการจากตัวเลือก (DeepSeek Chat, DeepSeek Reasoner, DeepSeek Coder)
  3. พิมพ์ข้อความ: พิมพ์ข้อความที่ต้องการส่งในช่อง “Type your message…”
  4. กด Generate: กดปุ่ม “Generate” หรือกด Enter เพื่อส่งข้อความ
  5. ดูการตอบกลับ: DeepSeek จะตอบกลับและแสดงในส่วน “Messages”
  6. ดูจำนวน Token: จำนวน Token ที่ใช้จะแสดงใต้ข้อความตอบกลับของบอท

Code

				
					<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>DeepSeek Chat Interface</title>

<style>

body {

font-family: Arial, sans-serif;

margin: 0;

padding: 20px;

background-color: #f0f0f0;

}



.container {

max-width: 1100px;

margin: 0 auto;

}



#header {

display: flex;

justify-content: space-between;

align-items: center;

background-color: black; /* เปลี่ยนพื้นหลังเป็นสีดำ */

padding: 10px 20px;

color: white;

}



#logo {

font-size: 1.5em;

font-weight: bold;

background-image: url('https://www.nconnect.asia/wp-content/uploads/2024/06/SQ.png'); /* ใส่ URL ของโลโก้ */

background-size: contain;

background-repeat: no-repeat;

width: 150px; /* กำหนดความกว้างของโลโก้ */

height: 50px; /* กำหนดความสูงของโลโก้ */

}



#menu {

display: flex;

gap: 20px;

}



#menu a {

color: white;

text-decoration: none;

}



#settings-container {

display: flex;

gap: 10px;

margin-bottom: 20px;

flex-wrap: wrap;

}



.setting-group {

background-color: white;

padding: 15px;

border-radius: 8px;

box-shadow: 0 2px 5px rgba(0,0,0,0.1);

}



#model-select {

padding: 8px;

border-radius: 5px;

border: 1px solid #ddd;

}



#api-key {

width: 250px;

padding: 8px;

margin-right: 10px;

}



#chat-container {

background-color: white;

border-radius: 10px;

box-shadow: 0 2px 10px rgba(0,0,0,0.1);

padding: 20px;

}



#messages {

height: 500px;

overflow-y: auto;

margin-bottom: 20px;

padding: 10px;

border: 1px solid #ddd;

border-radius: 5px;

}



.message {

margin-bottom: 15px;

padding: 15px;

border-radius: 8px;

max-width: 80%;

position: relative;

}



.user-message {

background-color: #e3f2fd;

margin-left: auto;

}



.bot-message {

background-color: #f5f5f5;

margin-right: auto;

}



#input-container {

display: flex;

gap: 10px;

}



#message-input {

flex-grow: 1;

padding: 10px;

border: 1px solid #ddd;

border-radius: 5px;

}



button {

padding: 10px 20px;

background-color: #007bff;

color: white;

border: none;

border-radius: 5px;

cursor: pointer;

transition: background-color 0.3s;

}



button:hover {

background-color: #0056b3;

}



.token-info {

font-size: 0.8em;

color: #666;

margin-top: 10px;

padding-top: 5px;

border-top: 1px solid #ddd;

}



.generating {

color: #666;

font-style: italic;

background-color: #f8f9fa !important;

}



#generate-btn:disabled {

background-color: #6c757d;

cursor: not-allowed;

}



.code-section {

margin: 15px 0;

border-radius: 6px;

overflow: hidden;

}



.code-header {

background-color: #2d2d2d;

padding: 8px;

display: flex;

justify-content: space-between;

align-items: center;

color: white;

}



.dark-code {

background-color: #1e1e1e;

padding: 15px;

overflow-x: auto;

color: #d4d4d4;

font-family: 'Courier New', monospace;

white-space: pre-wrap;

}



.copy-btn {

padding: 4px 8px;

background-color: #4CAF50;

color: white;

border: none;

border-radius: 4px;

cursor: pointer;

font-size: 0.8em;

transition: background-color 0.2s;

}



.copy-btn:hover {

background-color: #45a049;

}



.explanation {

margin: 10px 0;

padding: 10px;

background-color: #f8f9fa;

border-radius: 4px;

}



.reasoning-section {

background-color: #fff3e0;

padding: 15px;

border-radius: 6px;

margin: 10px 0;

}



/* New styles for the insights feed */

#feed-container {

width: 80%;

margin: 20px auto;

display: grid;

grid-template-columns: repeat(3, 1fr);

gap: 20px;

}

.post {

border: 1px solid #ccc;

padding: 10px;

margin-bottom: 10px;

border-radius: 8px;

overflow: hidden;

background-color: white;

box-shadow: 0 2px 5px rgba(0,0,0,0.1);

}

.post-title {

font-weight: bold;

margin-top: 10px;

}

.post-link {

display: block;

margin-top: 10px;

padding: 10px;

background-color: black;

color: white;

text-align: center;

border-radius: 4px;

text-decoration: none;

}

.post-image {

width: 100%;

height: auto;

border-radius: 4px;

}



/* Center the Insights Feed title */

#insights-title {

text-align: center;

}

</style>

</head>

<body>

<header id="header">

<div id="logo"></div> <!-- โลโก้จะถูกแสดงที่นี่ -->

<nav id="menu">

<a href="#">Home</a>

<a href="#">About</a>

<a href="#">Services</a>

<a href="#">Insights</a>

<a href="#">Contact</a>

</nav>

</header>

<div class="container">

<div id="settings-container">

<div class="setting-group">

<input type="password"

id="api-key"

placeholder="DeepSeek API Key">

<button onclick="saveSettings()">Save</button>

</div>


<div class="setting-group">

<select id="model-select">

<option value="deepseek-chat">DeepSeek Chat</option>

<option value="deepseek-reasoner">DeepSeek Reasoner</option>

<option value="deepseek-coder">DeepSeek Coder</option>

</select>

</div>

</div>



<div id="chat-container">

<div id="messages"></div>

<div id="input-container">

<input type="text"

id="message-input"

placeholder="Type your message...">

<button id="generate-btn" onclick="sendMessage()">Generate</button>

</div>

</div>



<h1 id="insights-title">NConnect Insights Feed</h1>

<div id="feed-container"></div>

</div>



<script>

let apiKey = '';

let selectedModel = 'deepseek-chat';

let totalTokens = 0;



function loadSettings() {

const savedApiKey = localStorage.getItem('deepseekApiKey');

const savedModel = localStorage.getItem('selectedModel');


if (savedApiKey) {

apiKey = savedApiKey;

document.getElementById('api-key').value = savedApiKey;

}


if (savedModel) {

selectedModel = savedModel;

document.getElementById('model-select').value = savedModel;

}

}



function saveSettings() {

apiKey = document.getElementById('api-key').value;

selectedModel = document.getElementById('model-select').value;


localStorage.setItem('deepseekApiKey', apiKey);

localStorage.setItem('selectedModel', selectedModel);

alert('Settings saved!');

}



async function sendMessage() {

const userInput = document.getElementById('message-input');

const message = userInput.value.trim();

const messagesContainer = document.getElementById('messages');

const generateBtn = document.getElementById('generate-btn');



if (!message) return;

if (!apiKey) {

alert('Please enter your API Key first');

return;

}



appendMessage('user', message);

const generatingDiv = appendGeneratingMessage();

userInput.value = '';

generateBtn.disabled = true;

generateBtn.textContent = 'Generating...';



try {

const response = await fetch('https://api.deepseek.com/v1/chat/completions', {

method: 'POST',

headers: {

'Content-Type': 'application/json',

'Authorization': `Bearer ${apiKey}`

},

body: JSON.stringify({

model: selectedModel,

messages: [{

role: 'user',

content: message

}]

})

});



const data = await response.json();

const botResponse = data.choices[0].message.content;

const tokensUsed = data.usage.total_tokens;

totalTokens += tokensUsed;


generatingDiv.remove();

appendMessage('bot', botResponse, tokensUsed);

} catch (error) {

generatingDiv.remove();

appendMessage('bot', 'Error: Could not get response');

} finally {

generateBtn.disabled = false;

generateBtn.textContent = 'Generate';

}

}



function appendGeneratingMessage() {

const messagesContainer = document.getElementById('messages');

const generatingDiv = document.createElement('div');

generatingDiv.className = 'message bot-message generating';

generatingDiv.textContent = 'Generating...';

messagesContainer.appendChild(generatingDiv);

messagesContainer.scrollTop = messagesContainer.scrollHeight;

return generatingDiv;

}



function processResponse(text) {

const sections = [];

const splitPattern = /(```[\s\S]*?```)/g;

const parts = text.split(splitPattern);



parts.forEach(part => {

if (part.startsWith('```')) {

const codeContent = part.replace(/```[\w\s]*\n?/, '').replace(/```$/, '');

sections.push({ type: 'code', content: codeContent.trim() });

} else if (part.trim()) {

sections.push({ type: 'text', content: part.trim() });

}

});



return sections;

}



function appendMessage(sender, text, tokensUsed = 0) {

const messagesContainer = document.getElementById('messages');

const messageDiv = document.createElement('div');

messageDiv.className = `message ${sender}-message`;



const contentDiv = document.createElement('div');


if (sender === 'bot') {

// Copy Button

const copyBtnContainer = document.createElement('div');

copyBtnContainer.className = 'copy-btn-container';


const copyBtn = document.createElement('button');

copyBtn.className = 'copy-btn';

copyBtn.textContent = 'Copy All';

copyBtn.onclick = () => {

navigator.clipboard.writeText(text)

.then(() => alert('Copied to clipboard!'))

.catch(err => console.error('Copy failed:', err));

};


copyBtnContainer.appendChild(copyBtn);

messageDiv.appendChild(copyBtnContainer);



// Content Processing

const sections = processResponse(text);


sections.forEach(section => {

if (section.type === 'code') {

const codeSection = document.createElement('div');

codeSection.className = 'code-section';


const header = document.createElement('div');

header.className = 'code-header';

header.innerHTML = `

<span>Code Block</span>

<button class="copy-btn" onclick="copyCode(this)">Copy Code</button>

`;


const codeContent = document.createElement('div');

codeContent.className = 'dark-code';

codeContent.textContent = section.content;


codeSection.appendChild(header);

codeSection.appendChild(codeContent);

contentDiv.appendChild(codeSection);

} else {

const containerClass = selectedModel === 'deepseek-reasoner'

? 'reasoning-section'

: 'explanation';

const explanationDiv = document.createElement('div');

explanationDiv.className = containerClass;

explanationDiv.innerHTML = formatText(section.content);

contentDiv.appendChild(explanationDiv);

}

});



messageDiv.appendChild(contentDiv);



// Token Info

const tokenDiv = document.createElement('div');

tokenDiv.className = 'token-info';

tokenDiv.textContent = `Tokens used: ${tokensUsed} (Total: ${totalTokens})`;

messageDiv.appendChild(tokenDiv);

} else {

contentDiv.textContent = text;

messageDiv.appendChild(contentDiv);

}



messagesContainer.appendChild(messageDiv);

messagesContainer.scrollTop = messagesContainer.scrollHeight;

}



function formatText(text) {

return text

.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')

.replace(/\*(.*?)\*/g, '<em>$1</em>')

.replace(/`(.*?)`/g, '<code>$1</code>')

.replace(/\n/g, '<br>');

}



function copyCode(button) {

const codeContent = button.closest('.code-section').querySelector('.dark-code').textContent;

navigator.clipboard.writeText(codeContent)

.then(() => alert('Code copied to clipboard!'))

.catch(err => console.error('Copy failed:', err));

}



// Fetch insights feed

const feedContainer = document.getElementById('feed-container');



fetch('https://insights.nconnect.asia/wp-json/wp/v2/posts')

.then(response => {

if (!response.ok) {

throw new Error('Network response was not ok');

}

return response.json();

})

.then(posts => {

posts.forEach(post => {

// สร้าง element

const postDiv = document.createElement('div');

postDiv.classList.add('post');



const imageElement = document.createElement('img');

imageElement.classList.add('post-image');

imageElement.alt = post.title.rendered;



const titleElement = document.createElement('a');

titleElement.classList.add('post-title');

titleElement.href = post.link;

titleElement.textContent = post.title.rendered;



const linkElement = document.createElement('a');

linkElement.classList.add('post-link');

linkElement.href = post.link;

linkElement.textContent = 'Read more';



// Add image if available

if (post.featured_media) {

fetch(`https://insights.nconnect.asia/wp-json/wp/v2/media/${post.featured_media}`)

.then(mediaResponse => mediaResponse.json())

.then(media => {

imageElement.src = media.source_url; // Use the correct source URL for the image

postDiv.appendChild(imageElement); // ย้ายรูปภาพไปด้านบน Title

})

.catch(err => console.error('Error fetching media:', err));

}

postDiv.appendChild(titleElement); // ย้าย Title ขึ้นไปด้านล่างรูปภาพ

postDiv.appendChild(linkElement);



// เพิ่ม postDiv เข้าไปใน feedContainer

feedContainer.appendChild(postDiv);

});

})

.catch(error => {

console.error('There has been a problem with your fetch operation:', error);

feedContainer.innerHTML = '<p>Error loading feed.</p>';

});



// Event Listeners

document.getElementById('message-input').addEventListener('keypress', (e) => {

if (e.key === 'Enter') {

sendMessage();

}

});



document.getElementById('model-select').addEventListener('change', (e) => {

selectedModel = e.target.value;

});



// Initialize

loadSettings();

</script>

</body>

</html>