467363d651
Made-with: Cursor
219 lines
12 KiB
HTML
219 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="th">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>ตั้งค่า AI แชท — Game</title>
|
|
<link rel="stylesheet" href="/Game/css/style.css">
|
|
<style>
|
|
body { max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
|
|
h1 { font-size: 1.25rem; margin-bottom: 0.5rem; }
|
|
.form-group { margin-bottom: 1rem; }
|
|
.form-group label { display: block; margin-bottom: 0.35rem; font-size: 0.9rem; }
|
|
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 0.5rem; border-radius: 6px; border: 1px solid #414868; background: #24283b; color: #c0caf5; box-sizing: border-box; }
|
|
.form-group textarea { min-height: 80px; resize: vertical; }
|
|
.form-group .hint, .form-group small { font-size: 0.8rem; color: #a9b1d6; margin-top: 0.25rem; display: block; }
|
|
button { padding: 0.5rem 1rem; border-radius: 6px; border: none; background: #7aa2f7; color: #1a1b26; cursor: pointer; }
|
|
button:hover { background: #89b4fa; }
|
|
.error { color: #f7768e; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
.success { color: #9ece6a; font-size: 0.9rem; margin-top: 0.5rem; }
|
|
#settings-panel { display: none; }
|
|
#login-panel { display: block; }
|
|
a { color: #7aa2f7; }
|
|
.tabs { display: flex; background: #1a1b26; border-bottom: 2px solid #414868; border-radius: 8px 8px 0 0; overflow: hidden; }
|
|
.tab { flex: 1; padding: 12px 16px; text-align: center; cursor: pointer; background: #24283b; border: none; font-size: 0.95rem; font-weight: 600; color: #a9b1d6; transition: all 0.2s; }
|
|
.tab:hover { background: #414868; color: #c0caf5; }
|
|
.tab.active { background: #1a1b26; color: #7aa2f7; border-bottom: 2px solid #7aa2f7; }
|
|
.tab-content { display: none; padding: 1.25rem; background: #1a1b26; border: 1px solid #414868; border-top: none; border-radius: 0 0 8px 8px; }
|
|
.tab-content.active { display: block; }
|
|
.condition-box { background: #24283b; border-radius: 6px; padding: 0.75rem 1rem; margin-top: 1rem; font-size: 0.85rem; color: #a9b1d6; border-left: 3px solid #7aa2f7; }
|
|
.condition-box strong { color: #c0caf5; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>ตั้งค่า AI แชท (Room Lobby)</h1>
|
|
<p style="font-size:0.9rem;color:#a9b1d6;">ตั้งค่า OpenAI API Key และ Model สำหรับฟีเจอร์ "คุยกับ AI" ในห้องโถง</p>
|
|
|
|
<div id="login-panel">
|
|
<p style="font-size:0.85rem;color:#a9b1d6;">รหัสผ่านเริ่มต้น: <code>game-ai-admin</code> (ตั้ง env <code>GAME_AI_ADMIN_PASSWORD</code> เพื่อเปลี่ยน)</p>
|
|
<form id="login-form">
|
|
<input type="text" name="username" id="admin-username" value="admin" autocomplete="username" tabindex="-1" aria-hidden="true" style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0;">
|
|
<div class="form-group">
|
|
<label for="admin-password">รหัสผ่าน Admin</label>
|
|
<input type="password" id="admin-password" name="password" placeholder="รหัสผ่าน" autocomplete="current-password" required>
|
|
</div>
|
|
<button type="submit">เข้าสู่ระบบ</button>
|
|
<div id="login-error" class="error"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="settings-panel">
|
|
<div class="tabs">
|
|
<button type="button" class="tab active" data-tab="settings">การตั้งค่าระบบ</button>
|
|
<button type="button" class="tab" data-tab="intent-rag">Intent & RAG</button>
|
|
</div>
|
|
<form id="settings-form">
|
|
<div id="settings-tab" class="tab-content active">
|
|
<h2 style="font-size:1.1rem;margin-bottom:1rem;color:#c0caf5;">การตั้งค่าระบบ</h2>
|
|
<div class="form-group">
|
|
<label for="openai-key">OpenAI API Key</label>
|
|
<input type="password" id="openai-key" name="openai_api_key" placeholder="sk-..." autocomplete="off">
|
|
<small>เว้นว่างถ้าไม่ต้องการเปลี่ยน</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="openai-model">Model</label>
|
|
<select id="openai-model" name="model"></select>
|
|
</div>
|
|
</div>
|
|
<div id="intent-rag-tab" class="tab-content">
|
|
<h2 style="font-size:1.1rem;margin-bottom:1rem;color:#c0caf5;">Intent & RAG</h2>
|
|
<div class="form-group">
|
|
<label for="ai-intent">Intent / System Prompt</label>
|
|
<textarea id="ai-intent" name="intent" placeholder="บทบาทหรือคำสั่งให้ AI (เช่น คุณคือผู้ช่วยในห้องเกม ตอบสั้น ใช้ภาษาธรรมดา)"></textarea>
|
|
<small><strong>เงื่อนไข:</strong> ถ้ามีข้อความ จะถูกส่งเป็น system message ให้โมเดลใช้เป็นแนวทางในการตอบ</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label><input type="checkbox" id="rag-enabled" name="rag_enabled" style="width:auto;margin-right:8px;"> เปิดใช้ RAG (ใส่บริบทเพิ่มเติม)</label>
|
|
<small><strong>เงื่อนไข:</strong> เมื่อเปิด RAG เท่านั้น ระบบจะแนบ RAG Context ด้านล่างเข้า system prompt</small>
|
|
</div>
|
|
<div class="form-group" id="rag-context-wrap">
|
|
<label for="rag-context">RAG Context / ความรู้เพิ่มเติม</label>
|
|
<textarea id="rag-context" name="rag_context" placeholder="ข้อความหรือความรู้ที่ต้องการให้ AI อ้างอิง (ยัดเข้า system prompt)"></textarea>
|
|
<small>เมื่อเปิด RAG ข้อความนี้จะถูกแนบเป็นบริบทให้ AI ใช้ประกอบการตอบ</small>
|
|
</div>
|
|
<div class="condition-box">
|
|
<strong>ลำดับการทำงาน:</strong> (1) Intent เป็น system message แรก (2) ถ้าเปิด RAG จะต่อ RAG Context เข้า system (3) ข้อความผู้ใช้เป็น user message
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:1rem;padding-top:1rem;border-top:1px solid #414868;">
|
|
<button type="submit">บันทึกการตั้งค่า</button>
|
|
<span id="settings-message" class="success" style="display:none;margin-left:0.75rem;"></span>
|
|
<div id="settings-error" class="error"></div>
|
|
</div>
|
|
</form>
|
|
<p style="margin-top:1.5rem;"><a href="/Game/room-lobby.html">← กลับไปล็อบบี้</a></p>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
const BASE = '/Game';
|
|
|
|
function showLogin() {
|
|
document.getElementById('login-panel').style.display = 'block';
|
|
document.getElementById('settings-panel').style.display = 'none';
|
|
}
|
|
function showSettings() {
|
|
document.getElementById('login-panel').style.display = 'none';
|
|
document.getElementById('settings-panel').style.display = 'block';
|
|
}
|
|
|
|
document.getElementById('login-form').addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var errEl = document.getElementById('login-error');
|
|
errEl.textContent = '';
|
|
var password = document.getElementById('admin-password').value.trim();
|
|
fetch(BASE + '/api/ai-admin-login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password: password }),
|
|
})
|
|
.then(function (r) {
|
|
if (!r.ok) return r.text().then(function (t) { throw new Error(r.status === 404 ? 'Server ยังไม่รองรับ API นี้ (404)' : (t || 'Error ' + r.status)); });
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
if (data.ok) {
|
|
showSettings();
|
|
loadSettings();
|
|
} else {
|
|
errEl.textContent = data.error || 'เข้าสู่ระบบไม่สำเร็จ';
|
|
}
|
|
})
|
|
.catch(function (e) { errEl.textContent = e.message || 'เชื่อมต่อไม่สำเร็จ'; });
|
|
});
|
|
|
|
function loadSettings() {
|
|
fetch(BASE + '/api/ai-settings', { credentials: 'include' })
|
|
.then(function (r) {
|
|
if (r.status === 401) { showLogin(); return null; }
|
|
if (!r.ok) return r.text().then(function () { return null; });
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
if (!data) return;
|
|
if (data.models && data.models.length) {
|
|
var sel = document.getElementById('openai-model');
|
|
sel.innerHTML = '';
|
|
data.models.forEach(function (m) {
|
|
var opt = document.createElement('option');
|
|
opt.value = m;
|
|
opt.textContent = m;
|
|
sel.appendChild(opt);
|
|
});
|
|
}
|
|
document.getElementById('openai-model').value = data.model || 'gpt-4o-mini';
|
|
document.getElementById('ai-intent').value = data.intent != null ? data.intent : '';
|
|
document.getElementById('rag-enabled').checked = !!data.rag_enabled;
|
|
document.getElementById('rag-context').value = data.rag_context != null ? data.rag_context : '';
|
|
document.getElementById('rag-context-wrap').style.opacity = data.rag_enabled ? '1' : '0.7';
|
|
});
|
|
}
|
|
|
|
document.getElementById('rag-enabled').addEventListener('change', function () {
|
|
document.getElementById('rag-context-wrap').style.opacity = this.checked ? '1' : '0.7';
|
|
});
|
|
|
|
function switchTab(tabId) {
|
|
document.querySelectorAll('.tab').forEach(function (t) { t.classList.remove('active'); });
|
|
document.querySelectorAll('.tab-content').forEach(function (c) { c.classList.remove('active'); });
|
|
var tabBtn = document.querySelector('.tab[data-tab="' + tabId + '"]');
|
|
var tabPanel = document.getElementById(tabId + '-tab');
|
|
if (tabBtn) tabBtn.classList.add('active');
|
|
if (tabPanel) tabPanel.classList.add('active');
|
|
}
|
|
document.querySelectorAll('.tab').forEach(function (btn) {
|
|
btn.addEventListener('click', function () { switchTab(btn.getAttribute('data-tab')); });
|
|
});
|
|
|
|
document.getElementById('settings-form').addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var msgEl = document.getElementById('settings-message');
|
|
var errEl = document.getElementById('settings-error');
|
|
msgEl.style.display = 'none';
|
|
errEl.textContent = '';
|
|
var payload = {
|
|
model: document.getElementById('openai-model').value,
|
|
intent: document.getElementById('ai-intent').value,
|
|
rag_enabled: document.getElementById('rag-enabled').checked,
|
|
rag_context: document.getElementById('rag-context').value,
|
|
};
|
|
var key = document.getElementById('openai-key').value.trim();
|
|
if (key) payload.openai_api_key = key;
|
|
fetch(BASE + '/api/ai-settings', {
|
|
method: 'PUT',
|
|
credentials: 'include',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
})
|
|
.then(function (r) {
|
|
if (!r.ok) return r.text().then(function (t) { throw new Error(r.status === 404 ? 'Server ยังไม่รองรับ API นี้ (404)' : (t || 'Error ' + r.status)); });
|
|
return r.json();
|
|
})
|
|
.then(function (data) {
|
|
if (data && data.ok) {
|
|
msgEl.textContent = 'บันทึกแล้ว';
|
|
msgEl.style.display = 'block';
|
|
document.getElementById('openai-key').value = '';
|
|
} else {
|
|
errEl.textContent = (data && data.error) || 'บันทึกไม่สำเร็จ';
|
|
}
|
|
})
|
|
.catch(function (e) { errEl.textContent = e.message || 'เชื่อมต่อไม่สำเร็จ'; });
|
|
});
|
|
|
|
loadSettings();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|