Trợ Lý AI Toàn Năng Tiếng Việt
Một trợ lý AI đa năng chạy trong một tệp HTML duy nhất trên trình duyệt. Agent có thể hỗ trợ viết code HTML/CSS/JS/Python, phân tích logic phức tạp, giải toán từng bước, trả lời kiến thức tổng quát, tư vấn đời sống và giải thích rõ ràng bằng tiếng Việt.
Preview Mode
This is a preview with sample data. The template uses placeholders like
which will be replaced with actual agent data.
About This Template
Trợ Lý AI Toàn Năng Tiếng Việt is a browser-executable AI agent template built on AgentOp. It runs entirely in the browser using Python (via Pyodide) and can be deployed without a server — just download the generated HTML file and open it locally or host it anywhere.
Template Metadata
- Slug
- tro-ly-ai-toan-nang-tieng-viet
- Created By
- lenamphuc
- Created
- Sep 06, 2026
- Usage Count
- 1
Tags
Code Statistics
- HTML Lines
- 94
- CSS Lines
- 350
- JS Lines
- 156
- Python Lines
- 37
Source Code
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ agent_name }}</title>
<style>
{{ css_code }}
</style>
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div class="brand-card">
<div class="brand-emoji">🧠</div>
<div>
<h1>{{ agent_name }}</h1>
<p>{{ description }}</p>
</div>
</div>
<section class="panel">
<h2>⚡ Gợi ý nhanh</h2>
<div class="chip-list" id="example-prompts">
<button class="chip" type="button" data-prompt="Viết một ứng dụng to-do HTML/CSS/JS có lưu localStorage">💻 Viết app to-do</button>
<button class="chip" type="button" data-prompt="Giải chi tiết bất phương trình x^2 - 5x + 6 < 0">🧮 Giải toán từng bước</button>
<button class="chip" type="button" data-prompt="Giải thích closure trong JavaScript bằng ví dụ dễ hiểu">📘 Giải thích kiến thức</button>
<button class="chip" type="button" data-prompt="Lập kế hoạch cân bằng giữa học tập, công việc và nghỉ ngơi trong 1 tuần">🌱 Tư vấn đời sống</button>
</div>
</section>
<section class="panel">
<h2>🎯 Năng lực</h2>
<ul class="feature-list">
<li>Viết và sửa code HTML/CSS/JS/Python</li>
<li>Phân tích logic và thuật toán phức tạp</li>
<li>Giải toán rõ từng bước bằng tiếng Việt</li>
<li>Trả lời kiến thức và tư vấn dễ hiểu</li>
</ul>
</section>
</aside>
<main class="main-panel">
<header class="chat-header">
<div>
<h2>💬 Không gian hỏi đáp</h2>
<p id="status-text">Đang khởi tạo môi trường AI trong trình duyệt...</p>
</div>
</header>
<section id="results-container" class="chat-stream" aria-live="polite">
<div class="empty-state" id="empty-state">
<div class="empty-emoji">🚀</div>
<h3>Bắt đầu với một câu hỏi cụ thể</h3>
<p>Tôi phù hợp cho cả công việc kỹ thuật lẫn đời sống hằng ngày.</p>
<ul>
<li>💻 Tạo, sửa, giải thích code dài</li>
<li>🧠 Phân tích bài toán logic nhiều bước</li>
<li>🧮 Giải toán chi tiết, dễ theo dõi</li>
<li>📚 Trả lời kiến thức bằng tiếng Việt rõ ràng</li>
</ul>
</div>
</section>
<section class="composer-panel">
<form id="chat-form" class="composer-form">
<label class="sr-only" for="user-input">Câu hỏi cho trợ lý AI</label>
<textarea id="user-input" rows="6" placeholder="Ví dụ: Viết cho tôi một website portfolio bằng HTML/CSS/JS có hiệu ứng mượt, hoặc giải chi tiết bài toán này..." required></textarea>
<div class="composer-actions">
<div class="left-actions">
<button id="clear-btn" type="button" class="secondary-btn">🧹 Xóa hội thoại</button>
</div>
<div class="right-actions">
<button id="send-btn" type="submit" class="primary-btn">✨ Hỏi AI</button>
</div>
</div>
<div id="inline-error" class="inline-error hidden"></div>
</form>
</section>
</main>
</div>
<div id="loading-overlay" class="loading-overlay hidden" aria-hidden="true">
<div class="loading-card">
<div class="spinner"></div>
<p>AI đang suy nghĩ và soạn câu trả lời...</p>
</div>
</div>
<script>
{{ js_code }}
</script>
</body>
</html>
:root {
--bg: #0b1020;
--bg-soft: #121932;
--panel: rgba(18, 25, 50, 0.88);
--panel-2: rgba(255, 255, 255, 0.05);
--text: #ecf2ff;
--muted: #aebbd6;
--primary: #6ea8fe;
--primary-2: #8b5cf6;
--accent: #22c55e;
--danger: #f87171;
--border: rgba(255, 255, 255, 0.1);
--shadow: 0 20px 60px rgba(0, 0, 0, 0.35);
--radius-xl: 24px;
--radius-lg: 18px;
--radius-md: 14px;
--radius-sm: 10px;
--font: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
font-family: var(--font);
color: var(--text);
background:
radial-gradient(circle at top left, rgba(110,168,254,0.22), transparent 28%),
radial-gradient(circle at top right, rgba(139,92,246,0.18), transparent 26%),
linear-gradient(180deg, #0a0f1d 0%, #0f172a 100%);
}
.hidden { display: none !important; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.app-shell {
width: min(1400px, calc(100% - 32px));
margin: 24px auto;
display: grid;
grid-template-columns: 340px 1fr;
gap: 20px;
}
.sidebar, .main-panel {
min-height: 80vh;
}
.sidebar {
display: flex;
flex-direction: column;
gap: 18px;
}
.brand-card, .panel, .chat-header, .chat-stream, .composer-panel {
background: var(--panel);
border: 1px solid var(--border);
box-shadow: var(--shadow);
backdrop-filter: blur(14px);
}
.brand-card {
border-radius: var(--radius-xl);
padding: 22px;
display: grid;
grid-template-columns: 60px 1fr;
gap: 16px;
align-items: start;
}
.brand-emoji {
width: 60px;
height: 60px;
display: grid;
place-items: center;
border-radius: 18px;
background: linear-gradient(135deg, var(--primary), var(--primary-2));
font-size: 30px;
}
.brand-card h1 {
margin: 0 0 8px;
font-size: 1.3rem;
}
.brand-card p, .panel p, .chat-header p {
margin: 0;
color: var(--muted);
line-height: 1.55;
}
.panel {
border-radius: var(--radius-lg);
padding: 18px;
}
.panel h2, .chat-header h2 {
margin: 0 0 14px;
font-size: 1rem;
}
.chip-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.chip {
border: 1px solid var(--border);
background: var(--panel-2);
color: var(--text);
padding: 10px 12px;
border-radius: 999px;
cursor: pointer;
transition: 0.2s ease;
font: inherit;
text-align: left;
}
.chip:hover { transform: translateY(-1px); border-color: rgba(110,168,254,0.45); }
.feature-list {
margin: 0;
padding-left: 18px;
color: var(--muted);
line-height: 1.7;
}
.main-panel {
display: grid;
grid-template-rows: auto 1fr auto;
gap: 16px;
}
.chat-header {
border-radius: var(--radius-xl);
padding: 18px 22px;
}
.chat-stream {
border-radius: var(--radius-xl);
padding: 20px;
min-height: 50vh;
max-height: 58vh;
overflow: auto;
}
.empty-state {
border: 1px dashed rgba(255,255,255,0.14);
border-radius: var(--radius-lg);
padding: 28px;
text-align: left;
background: rgba(255,255,255,0.02);
}
.empty-state h3 { margin: 0 0 10px; }
.empty-state p, .empty-state li { color: var(--muted); line-height: 1.65; }
.empty-emoji { font-size: 2rem; margin-bottom: 8px; }
.message {
margin-bottom: 16px;
display: flex;
}
.message.user { justify-content: flex-end; }
.message.assistant, .message.system { justify-content: flex-start; }
.bubble {
max-width: min(900px, 92%);
border-radius: 18px;
padding: 14px 16px;
line-height: 1.65;
white-space: pre-wrap;
word-wrap: break-word;
border: 1px solid var(--border);
}
.message.user .bubble {
background: linear-gradient(135deg, rgba(110,168,254,0.24), rgba(139,92,246,0.22));
}
.message.assistant .bubble {
background: rgba(255,255,255,0.04);
}
.message.system .bubble {
background: rgba(34,197,94,0.12);
color: #d8ffe5;
}
.bubble code, .bubble pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.composer-panel {
border-radius: var(--radius-xl);
padding: 16px;
}
.composer-form {
display: flex;
flex-direction: column;
gap: 12px;
}
textarea {
width: 100%;
resize: vertical;
min-height: 140px;
border-radius: 16px;
border: 1px solid var(--border);
background: rgba(255,255,255,0.04);
color: var(--text);
padding: 16px;
font: inherit;
line-height: 1.6;
}
textarea::placeholder { color: #90a0c0; }
.composer-actions {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
}
.left-actions, .right-actions {
display: flex;
gap: 10px;
}
.primary-btn, .secondary-btn {
border: 1px solid var(--border);
border-radius: 14px;
padding: 12px 16px;
font: inherit;
cursor: pointer;
transition: 0.2s ease;
}
.primary-btn {
background: linear-gradient(135deg, var(--primary), var(--primary-2));
color: white;
border: none;
font-weight: 700;
}
.secondary-btn {
background: rgba(255,255,255,0.05);
color: var(--text);
}
.primary-btn:hover, .secondary-btn:hover, .chip:hover {
filter: brightness(1.06);
}
.primary-btn:disabled, .secondary-btn:disabled {
opacity: 0.55;
cursor: not-allowed;
filter: grayscale(0.1);
}
textarea:focus-visible, .primary-btn:focus-visible, .secondary-btn:focus-visible, .chip:focus-visible {
outline: 3px solid rgba(110,168,254,0.42);
outline-offset: 2px;
}
.inline-error {
color: #ffd7d7;
background: rgba(248,113,113,0.12);
border: 1px solid rgba(248,113,113,0.3);
border-radius: 12px;
padding: 10px 12px;
}
.loading-overlay {
position: fixed;
inset: 0;
background: rgba(7, 11, 22, 0.6);
display: grid;
place-items: center;
z-index: 999;
}
.loading-card {
min-width: 280px;
background: #111831;
border: 1px solid var(--border);
border-radius: 20px;
padding: 22px;
text-align: center;
box-shadow: var(--shadow);
}
.spinner {
width: 42px;
height: 42px;
margin: 0 auto 12px;
border: 4px solid rgba(255,255,255,0.15);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 1200px) {
.app-shell {
grid-template-columns: 1fr;
}
.sidebar, .main-panel {
min-height: auto;
}
.chat-stream {
max-height: none;
}
}
@media (max-width: 768px) {
.app-shell {
width: min(100% - 20px, 100%);
margin: 14px auto 20px;
gap: 14px;
}
.brand-card {
grid-template-columns: 1fr;
}
.composer-actions {
flex-direction: column;
align-items: stretch;
}
.left-actions, .right-actions {
width: 100%;
}
.primary-btn, .secondary-btn {
width: 100%;
}
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
(function () {
const resultsContainer = document.getElementById('results-container');
const emptyState = document.getElementById('empty-state');
const form = document.getElementById('chat-form');
const textarea = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const clearBtn = document.getElementById('clear-btn');
const inlineError = document.getElementById('inline-error');
const loadingOverlay = document.getElementById('loading-overlay');
const statusText = document.getElementById('status-text');
const chipButtons = Array.from(document.querySelectorAll('.chip'));
let ready = false;
function setStatus(msg) {
statusText.textContent = msg;
}
function showError(msg) {
inlineError.textContent = msg;
inlineError.classList.remove('hidden');
}
function clearError() {
inlineError.textContent = '';
inlineError.classList.add('hidden');
}
function setBusy(isBusy) {
sendBtn.disabled = isBusy;
clearBtn.disabled = isBusy;
textarea.disabled = isBusy;
chipButtons.forEach(btn => btn.disabled = isBusy);
loadingOverlay.classList.toggle('hidden', !isBusy);
}
function scrollToBottom() {
resultsContainer.scrollTop = resultsContainer.scrollHeight;
}
window.addMessage = function (type, content) {
if (emptyState) emptyState.classList.add('hidden');
const wrapper = document.createElement('div');
wrapper.className = 'message ' + escapeHtml(type);
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble.innerHTML = escapeHtml(content || '');
wrapper.appendChild(bubble);
resultsContainer.appendChild(wrapper);
scrollToBottom();
return bubble;
};
async function checkReadyState() {
const pyodideOk = !!window.pyodideReady;
const localNeedsModel = window.PROVIDER === 'local';
const localLoaded = !!(window.agentManager && window.agentManager.isLoaded);
if (!pyodideOk) {
ready = false;
setStatus('Đang khởi tạo môi trường AI trong trình duyệt...');
return false;
}
if (localNeedsModel && !localLoaded) {
ready = false;
setStatus('Vui lòng tải một mô hình ở thanh trên cùng để bắt đầu dùng trợ lý AI.');
return false;
}
ready = true;
setStatus('Sẵn sàng. Hãy nhập yêu cầu bằng tiếng Việt.');
return true;
}
async function submitQuery(prefill) {
clearError();
const query = typeof prefill === 'string' ? prefill : textarea.value;
if (!query || !query.trim()) {
showError('Vui lòng nhập câu hỏi hoặc yêu cầu cụ thể cho trợ lý AI.');
return;
}
const ok = await checkReadyState();
if (!ok) {
showError(window.PROVIDER === 'local'
? 'Môi trường chưa sẵn sàng. Nếu bạn đang dùng mô hình cục bộ, hãy tải mô hình từ thanh trên cùng trước.'
: 'Môi trường AI chưa sẵn sàng. Vui lòng đợi khởi tạo hoàn tất.');
return;
}
window.addMessage('user', query.trim());
textarea.value = '';
setBusy(true);
try {
window.pyodide.globals.set('user_query', query.trim());
const result = await window.pyodide.runPythonAsync('await process_user_query(user_query)');
const text = (result === undefined || result === null) ? '' : String(result);
window.addMessage('assistant', text || 'Tôi chưa tạo được nội dung trả lời. Hãy thử diễn đạt lại yêu cầu cụ thể hơn.');
} catch (err) {
console.error(err);
window.addMessage('system', 'Đã xảy ra lỗi khi xử lý yêu cầu.');
showError('Không thể xử lý yêu cầu lúc này. Vui lòng thử lại với nội dung ngắn hơn hoặc rõ hơn.');
} finally {
setBusy(false);
scrollToBottom();
textarea.focus();
}
}
form.addEventListener('submit', async function (e) {
e.preventDefault();
await submitQuery();
});
clearBtn.addEventListener('click', function () {
clearError();
resultsContainer.innerHTML = '';
if (emptyState) {
resultsContainer.appendChild(emptyState);
emptyState.classList.remove('hidden');
}
textarea.focus();
});
chipButtons.forEach(function (btn) {
btn.addEventListener('click', async function () {
const prompt = btn.getAttribute('data-prompt') || '';
textarea.value = prompt;
await submitQuery(prompt);
});
});
document.addEventListener('pyodide-ready', async function () {
window.pyodideReady = true;
await checkReadyState();
});
const readinessInterval = setInterval(async function () {
const ok = await checkReadyState();
if (ok) clearInterval(readinessInterval);
}, 1200);
checkReadyState();
})();
async def process_user_query(query: str) -> str:
"""Xử lý yêu cầu người dùng và tạo câu trả lời tiếng Việt rõ ràng.
Args:
query: Câu hỏi hoặc yêu cầu của người dùng.
Returns:
Câu trả lời hoàn chỉnh bằng tiếng Việt.
"""
cleaned = (query or "").strip()
if not cleaned:
return "Vui lòng nhập một câu hỏi hoặc yêu cầu cụ thể."
truncated = cleaned[:[[[MAX_CHARS|6000]]]]
prompt = f"""
Người dùng viết bằng tiếng Việt và muốn một trợ lý AI toàn năng.
Yêu cầu của người dùng:
{truncated}
Hãy trả lời hoàn toàn bằng tiếng Việt, trừ khi người dùng yêu cầu ngôn ngữ khác.
Quy tắc trả lời:
- Nếu người dùng hỏi về code: đưa ra code đúng, đầy đủ, dễ chạy; giải thích ngắn gọn trước hoặc sau code khi hữu ích.
- Nếu người dùng yêu cầu sửa lỗi code: nêu nguyên nhân, cách sửa, rồi đưa phiên bản đã sửa.
- Nếu người dùng hỏi toán: giải từng bước rõ ràng, trình bày dễ đọc.
- Nếu người dùng hỏi logic: phân tích tuần tự, không bỏ qua giả định quan trọng.
- Nếu người dùng hỏi kiến thức: giải thích dễ hiểu, có ví dụ ngắn nếu cần.
- Nếu người dùng xin tư vấn đời sống: trả lời thực tế, cân bằng, lịch sự, dễ áp dụng.
- Nếu đề bài mơ hồ: nêu giả định hợp lý và trả lời theo giả định đó.
- Với câu trả lời dài: dùng tiêu đề và gạch đầu dòng để dễ theo dõi.
- Không nói rằng bạn không thể làm nhiều lĩnh vực; hãy cố gắng hỗ trợ tối đa trong phạm vi yêu cầu.
"""
answer = await agentop_llm.generate(prompt, globals().get("TEMPLATE_SYSTEM_PROMPT", ""))
return answer or "Tôi chưa tạo được câu trả lời phù hợp. Bạn hãy thử mô tả yêu cầu chi tiết hơn một chút."
Name your agent
Based on . You'll get your own private copy to try and customize.