| | console.log("--> SERVER STARTING..."); |
| | import express from 'express'; |
| | |
| | let SystemMessage, HumanMessage, AIMessage, LlamaCppLLM; |
| | try { |
| | const module = await import('./src/index.js'); |
| | SystemMessage = module.SystemMessage; |
| | HumanMessage = module.HumanMessage; |
| | AIMessage = module.AIMessage; |
| | LlamaCppLLM = module.LlamaCppLLM; |
| | console.log("--> Modules loaded successfully"); |
| | } catch (e) { |
| | console.error("--> FATAL IMPORT ERROR:", e); |
| | } |
| |
|
| | import bodyParser from 'body-parser'; |
| | import fs from 'fs'; |
| | import path from 'path'; |
| |
|
| | |
| | const PORT = 7860; |
| | const MODEL_PATH = './models/Qwen3-1.7B-Q8_0.gguf'; |
| | const MODEL_NAME = "dahanhstd-1.0"; |
| |
|
| | |
| | const PROMPTS = { |
| | "default": "You are a helpful AI assistant. Answer concisely.", |
| | "talk2people": fs.existsSync('MssterPrompt_Talk2People.txt') |
| | ? fs.readFileSync('MssterPrompt_Talk2People.txt', 'utf-8') |
| | : "You are a creative Video Director specializing in realistic Vietnamese scenes.", |
| | "coder": "You are an expert programmer." |
| | }; |
| |
|
| | |
| | const HTML_PAGE = ` |
| | <!DOCTYPE html> |
| | <html lang="vi"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Dạ Hành AI Studio</title> |
| | <style> |
| | :root { --primary:#00ff88; --bg:#121212; --chat-bg:#1e1e1e; --user-bg:#2a2a2a; --ai-bg:#0a3a2a; } |
| | body { font-family:'Segoe UI',sans-serif; background:var(--bg); color:white; margin:0; display:flex; height:100vh; overflow:hidden; } |
| | .sidebar { width:260px; background:#000; padding:20px; border-right:1px solid #333; display:flex; flex-direction:column; } |
| | .main { flex:1; display:flex; flex-direction:column; padding:20px; position:relative; } |
| | h1 { font-size:1.2rem; color:var(--primary); margin-bottom:20px; text-transform:uppercase; letter-spacing:1px; } |
| | select { width:100%; padding:12px; background:#333; color:white; border:1px solid #555; border-radius:8px; margin-bottom:20px; outline:none; } |
| | #chat-box { flex:1; overflow-y:auto; background:var(--chat-bg); border-radius:12px; padding:20px; margin-bottom:20px; display:flex; flex-direction:column; gap:15px; scroll-behavior:smooth; } |
| | .message { max-width:80%; padding:12px 18px; border-radius:18px; line-height:1.5; font-size:0.95rem; animation:fadeIn 0.3s ease; } |
| | .message.ai { align-self:flex-start; background:var(--ai-bg); border-bottom-left-radius:4px; } |
| | .message.user { align-self:flex-end; background:var(--user-bg); border-bottom-right-radius:4px; color:#ddd; } |
| | .input-area { display:flex; gap:10px; } |
| | input { flex:1; padding:15px 20px; border-radius:30px; border:1px solid #444; background:#222; color:white; outline:none; font-size:1rem; } |
| | input:focus { border-color:var(--primary); } |
| | button { padding:10px 30px; border-radius:30px; border:none; background:var(--primary); color:black; font-weight:bold; cursor:pointer; transition:0.2s; } |
| | button:hover { transform:scale(1.05); box-shadow:0 0 10px var(--primary); } |
| | .status { margin-top:auto; font-size:0.8rem; color:#666; border-top:1px solid #333; padding-top:15px; } |
| | @keyframes fadeIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } } |
| | /* Scrollbar */ |
| | ::-webkit-scrollbar { width:8px; } |
| | ::-webkit-scrollbar-track { background:#1e1e1e; } |
| | ::-webkit-scrollbar-thumb { background:#444; border-radius:4px; } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="sidebar"> |
| | <h1>DẠ HÀNH STUDIO</h1> |
| | <label style="font-size:0.9rem; color:#aaa; margin-bottom:5px">Chọn Role:</label> |
| | <select id="role-select"> |
| | <option value="talk2people">🎬 Đạo diễn (Talk2People)</option> |
| | <option value="coder">💻 Coder Expert</option> |
| | <option value="default">🤖 Trợ lý ảo</option> |
| | </select> |
| | <div class="status"> |
| | Model: <b>${MODEL_NAME}</b><br> |
| | Privacy: <b>Local Safe</b><br> |
| | Status: <span id="status-text" style="color:yellow">Connecting...</span> |
| | </div> |
| | </div> |
| | <div class="main"> |
| | <div id="chat-box"></div> |
| | <div class="input-area"> |
| | <input type="text" id="user-input" placeholder="Nhập tin nhắn..." autocomplete="off"> |
| | <button onclick="sendMessage()">GỬI</button> |
| | </div> |
| | </div> |
| | <script> |
| | const chatBox = document.getElementById('chat-box'); |
| | const userInput = document.getElementById('user-input'); |
| | const roleSelect = document.getElementById('role-select'); |
| | const statusText = document.getElementById('status-text'); |
| | let chatHistory = []; |
| | |
| | function addMessage(role, text) { |
| | const div = document.createElement('div'); |
| | div.className = 'message ' + role; |
| | div.innerText = text; |
| | chatBox.appendChild(div); |
| | chatBox.scrollTop = chatBox.scrollHeight; |
| | } |
| | |
| | async function checkHealth() { |
| | try { |
| | const res = await fetch('/info'); |
| | const data = await res.json(); |
| | statusText.innerText = "Online"; |
| | statusText.style.color = "#00ff88"; |
| | addMessage('ai', "Xin chào đây là hệ thống bot AI hoạt động riêng tư được đào tạo bởi Dạ Hành Studio, Không phụ thuộc vào Google"); |
| | } catch (e) { |
| | statusText.innerText = "Offline"; |
| | statusText.style.color = "red"; |
| | } |
| | } |
| | |
| | async function sendMessage() { |
| | const text = userInput.value.trim(); |
| | if (!text) return; |
| | |
| | addMessage('user', text); |
| | userInput.value = ''; |
| | chatHistory.push({ role: 'user', content: text }); |
| | |
| | // Loading effect |
| | const loadingId = 'loading-' + Date.now(); |
| | const loadingDiv = document.createElement('div'); |
| | loadingDiv.className = 'message ai'; |
| | loadingDiv.id = loadingId; |
| | loadingDiv.innerText = '...'; |
| | chatBox.appendChild(loadingDiv); |
| | chatBox.scrollTop = chatBox.scrollHeight; |
| | |
| | try { |
| | const response = await fetch('/chat', { |
| | method: 'POST', |
| | headers: { 'Content-Type': 'application/json' }, |
| | body: JSON.stringify({ |
| | message: text, |
| | history: chatHistory.slice(-10), |
| | role: roleSelect.value |
| | }) |
| | }); |
| | const data = await response.json(); |
| | |
| | document.getElementById(loadingId).remove(); |
| | |
| | if(data.error) { |
| | addMessage('ai', 'Lỗi: ' + data.error); |
| | } else { |
| | addMessage('ai', data.reply); |
| | chatHistory.push({ role: 'ai', content: data.reply }); |
| | } |
| | } catch (error) { |
| | document.getElementById(loadingId).remove(); |
| | addMessage('ai', 'Lỗi kết nối: ' + error.message); |
| | } |
| | } |
| | |
| | userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); }); |
| | roleSelect.addEventListener('change', () => { |
| | chatHistory = []; |
| | chatBox.innerHTML = ''; |
| | addMessage('ai', "Đã chuyển sang chế độ: " + roleSelect.options[roleSelect.selectedIndex].text); |
| | }); |
| | |
| | checkHealth(); |
| | </script> |
| | </body> |
| | </html> |
| | `; |
| |
|
| | |
| | let llm = null; |
| |
|
| | async function initModel() { |
| | if (!LlamaCppLLM) return; |
| | try { |
| | console.log('--> Loading model...'); |
| | if (!fs.existsSync(MODEL_PATH)) { |
| | console.error(`--> Model not found at ${MODEL_PATH}`); |
| | |
| | return; |
| | } |
| | llm = new LlamaCppLLM({ |
| | modelPath: MODEL_PATH, |
| | temperature: 0.7, |
| | maxTokens: 1024 |
| | }); |
| | await llm.invoke("Hello"); |
| | console.log('--> Model loaded successfully.'); |
| | } catch (err) { |
| | console.error('--> FATAL MODEL ERROR:', err.message); |
| | } |
| | } |
| |
|
| | |
| | const app = express(); |
| | app.use(bodyParser.json()); |
| |
|
| | |
| | app.post('/chat', async (req, res) => { |
| | |
| | const apiResponse = { |
| | model: MODEL_NAME, |
| | created: Date.now(), |
| | reply: "", |
| | error: null |
| | }; |
| |
|
| | if (!llm) { |
| | apiResponse.error = "System initializing or Model missing"; |
| | return res.status(503).json(apiResponse); |
| | } |
| |
|
| | try { |
| | const { message, history = [], role = 'default' } = req.body; |
| | const systemInstruction = PROMPTS[role] || PROMPTS['default']; |
| | |
| | const messages = [new SystemMessage(systemInstruction)]; |
| | history.forEach(msg => { |
| | if (msg.role === 'user') messages.push(new HumanMessage(msg.content)); |
| | if (msg.role === 'ai') messages.push(new AIMessage(msg.content)); |
| | }); |
| | messages.push(new HumanMessage(message)); |
| |
|
| | const response = await llm.invoke(messages); |
| | |
| | apiResponse.reply = response.content; |
| | res.json(apiResponse); |
| |
|
| | } catch (error) { |
| | apiResponse.error = error.message; |
| | res.status(500).json(apiResponse); |
| | } |
| | }); |
| |
|
| | |
| | app.get('/info', (req, res) => { |
| | res.json({ |
| | model: MODEL_NAME, |
| | status: llm ? "ready" : "loading", |
| | roles: Object.keys(PROMPTS) |
| | }); |
| | }); |
| |
|
| | |
| | app.get('/', (req, res) => { |
| | res.send(HTML_PAGE); |
| | }); |
| |
|
| | app.listen(PORT, '0.0.0.0', () => { |
| | console.log(`--> Server listening on ${PORT}`); |
| | initModel(); |
| | }); |
| |
|