v2: шағым-бот (ИИ-кеңесші) + сервер

This commit is contained in:
Aset 2026-08-10 17:16:33 +00:00
parent 9b2f408771
commit fbcf0ec913
5 changed files with 144 additions and 2 deletions

View File

@ -19,6 +19,7 @@
<a href="#timer">Жаттығу</a> <a href="#timer">Жаттығу</a>
<a href="#tips">Кеңестер</a> <a href="#tips">Кеңестер</a>
<a href="#meds">Дәрі</a> <a href="#meds">Дәрі</a>
<a href="#chat">Шағым</a>
</nav> </nav>
</div> </div>
</header> </header>
@ -127,6 +128,21 @@
</div> </div>
</section> </section>
<section class="section dark" id="chat">
<div class="container">
<h2>💬 Шағымыңды айт</h2>
<p class="lead">Ауырсыну, қаттылық немесе басқа шағымыңды жаз — жүйе не істеу керек екенін айтады. Мысалы: «таңертең арқам қатты, қозғалу қиын».</p>
<div class="chat" id="chatBox">
<div class="msg bot">Сәлем! Шағымыңды жаз: ауырсыну қайда, қашан пайда болады, не істегенде жеңілдейді?</div>
</div>
<div class="chat-in">
<textarea id="chatInput" rows="2" placeholder="Мысалы: түнде бел ауырады, таңертең қиын тұрамын..."></textarea>
<button class="btn" id="chatSend">Жіберу</button>
</div>
<p class="note-light">Жауапты жасанды интеллект береді — бұл дәрігер кеңесінің орнын баспайды. Өткір ауырсыну болса — дәрігерге қарал.</p>
</div>
</section>
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p>🦴 Буын денсаулығы — күнделікті көмекші</p> <p>🦴 Буын денсаулығы — күнделікті көмекші</p>

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"name": "buyn-densaulygy",
"private": true,
"type": "commonjs",
"scripts": { "start": "node server.js" }
}

View File

@ -179,3 +179,46 @@ var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) e.target.classList.add("in"); }); entries.forEach(function (e) { if (e.isIntersecting) e.target.classList.add("in"); });
}, { threshold: .15 }); }, { threshold: .15 });
document.querySelectorAll(".reveal").forEach(function (el) { io.observe(el); }); document.querySelectorAll(".reveal").forEach(function (el) { io.observe(el); });
var chatBox = document.getElementById("chatBox");
var chatInput = document.getElementById("chatInput");
var chatSend = document.getElementById("chatSend");
function addMsg(who, text) {
var d = document.createElement("div");
d.className = "msg " + who;
d.textContent = text;
chatBox.appendChild(d);
chatBox.scrollTop = chatBox.scrollHeight;
}
async function sendChat() {
var text = chatInput.value.trim();
if (!text) return;
addMsg("user", text);
chatInput.value = "";
var waiting = document.createElement("div");
waiting.className = "msg bot typing";
waiting.textContent = "Жазып жатыр…";
chatBox.appendChild(waiting);
chatBox.scrollTop = chatBox.scrollHeight;
try {
var r = await fetch("/api/advice", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: text })
});
var j = await r.json();
waiting.remove();
if (j.ok) addMsg("bot", j.answer);
else addMsg("bot", "Кешіріңіз, жауап қазір келмеді. Кейінірек қайталап көріңіз немесе шағымыңды басқаша жазып жіберіңіз.");
} catch (e) {
waiting.remove();
addMsg("bot", "Қате болды. Байланысты тексеріп, қайталап көріңіз.");
}
}
chatSend.addEventListener("click", sendChat);
chatInput.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendChat(); }
});

66
server.js Normal file
View File

@ -0,0 +1,66 @@
const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = process.env.PORT || 3000;
function body(req) {
return new Promise((resolve) => {
let s = "";
req.on("data", (c) => (s += c));
req.on("end", () => {
try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); }
});
});
}
async function ai(prompt) {
const r = await fetch(process.env.AI_BASE_URL + "/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + process.env.AI_API_KEY },
body: JSON.stringify({
model: process.env.AI_MODEL,
temperature: 0.4,
messages: [
{ role: "system", content: "Сен — артрит, артроз және анкилозды спондилит (Бехтерев ауруы) бар адамдарға арналған көмекші-кеңесші. Қазақ тілінде, жай сөзбен, қысқа әрі нақты жауап бер. Жауабыңда мыналар болсын: 1) шағымды қалай бағалауға болады; 2) қазір не істеу керек (жеңіл қозғалыс, жылу/суық, тыныштық) — жаттығуды тек ауырсынбай ғана орындау керектігін ескерт; 3) дәрі-дәрмекті тек дәрігер тағайындайтынын еске сал, дәрі атын ұсынба; 4) қандай белгілер кезінде дәрігерге шұғыл қаралу керектігін анық жаз (қатты күшейген ауырсыну, қатты ісіну, қызба, аяқ-қолды қозғалта алмау, сезімталдық жоғалуы). Ақырында: «Бұл ақпарат дәрігер кеңесінің орнын баспайды» деп қос. Сен медициналық маман емессің." },
{ role: "user", content: prompt }
]
})
});
if (!r.ok) throw new Error("ИИ недоступен, код " + r.status);
const j = await r.json();
const out = j.choices?.[0]?.message?.content;
if (!out) throw new Error("ИИ вернул пустой ответ");
return out.trim();
}
http.createServer(async (req, res) => {
const url = req.url.split("?")[0];
if (req.method === "POST" && url === "/api/advice") {
const data = await body(req);
const text = (data.text || "").trim();
if (!text) {
res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
return res.end(JSON.stringify({ ok: false, error: "Хабарлама бос" }));
}
try {
const answer = await ai(text);
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
return res.end(JSON.stringify({ ok: true, answer }));
} catch (e) {
res.writeHead(502, { "Content-Type": "application/json; charset=utf-8" });
return res.end(JSON.stringify({ ok: false, error: "ИИ временно недоступен, попробуйте ещё раз" }));
}
}
let file = url === "/" ? "/index.html" : url;
const full = path.join(__dirname, file);
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
const ext = path.extname(full);
const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
return res.end(fs.readFileSync(full));
}
res.writeHead(404); res.end("Not found");
}).listen(PORT, () => console.log("Сервер на порту " + PORT));

View File

@ -70,8 +70,19 @@ h1,h2,h3{font-family:"Unbounded",sans-serif;line-height:1.15}
.warn{background:#fff6e6;border:1px solid #f0d9a8;border-radius:12px;padding:18px 22px;margin-top:8px;font-size:15px;color:#5a4a1e} .warn{background:#fff6e6;border:1px solid #f0d9a8;border-radius:12px;padding:18px 22px;margin-top:8px;font-size:15px;color:#5a4a1e}
.footer{background:var(--ink);color:#9aa3b2;text-align:center} /* чат */
.footer p{font-weight:700;color:var(--white)} .chat{background:#141a26;border:1px solid #222b3c;border-radius:16px;padding:18px;max-height:420px;overflow-y:auto;display:flex;flex-direction:column;gap:10px;margin-bottom:14px}
.msg{max-width:80%;padding:12px 16px;border-radius:14px;font-size:15px;line-height:1.55;white-space:pre-wrap;word-wrap:break-word}
.msg.bot{background:#1d2738;color:#e3eaf4;align-self:flex-start;border-bottom-left-radius:4px}
.msg.user{background:var(--cyan);color:var(--ink);align-self:flex-end;border-bottom-right-radius:4px;font-weight:600}
.msg.typing{color:#7d8796;font-style:italic}
.chat-in{display:flex;gap:10px}
.chat-in textarea{flex:1;background:#141a26;border:1px solid #222b3c;border-radius:10px;color:var(--white);padding:12px 14px;font:15px/1.5 "Manrope",sans-serif;resize:vertical;min-height:52px}
.chat-in textarea:focus{outline:none;border-color:var(--cyan)}
.chat-in textarea::placeholder{color:#5a6575}
.note-light{color:#8a93a3;font-size:14px;margin-top:6px}
.footer{background:var(--ink);color:#9aa3b2;text-align:center}.footer p{font-weight:700;color:var(--white)}
.footer .foot-small{color:#7d8796;font-weight:400;margin-top:4px;font-size:14px} .footer .foot-small{color:#7d8796;font-weight:400;margin-top:4px;font-size:14px}
.reveal{opacity:0;transform:translateY(20px);transition:opacity .6s ease,transform .6s ease} .reveal{opacity:0;transform:translateY(20px);transition:opacity .6s ease,transform .6s ease}