🏁 ផ្នែកទី ១
សៀវភៅសិក្សា AI Web App
វគ្គនេះសិស្សនឹងសាងសង់ WebApp ប្រើ Gemini AI ភ្ជាប់ Supabase (Project ចាស់ ប្រើជាមួយ @reankh_bot) Deploy ឡើង Vercel · reankh.org
🤖
Gemini AI
🔐
Supabase Auth
💾
Database
🚀
Vercel
📌 Goal: WebApp ពិត · Login · AI Chat · Save · Video · Deploy Online
🔑 ផ្នែកទី ២
គណនី & API Key
💻 Software
- ✅ Visual Studio Code
- ✅ Google Chrome
- ✅ Git
- ✅ Node.js v20+
🌐 Accounts
- ✅ Gmail (Google AI Studio)
- ✅ GitHub
- ✅ Vercel (reankh.org)
- ✅ Supabase (Project ចាស់)
📄 ឯកសារ .env.local
NEXT_PUBLIC_GEMINI_API_KEY=AIzaSy_YOUR_KEY
NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIs...
TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN
TELEGRAM_CHAT_ID=YOUR_CHAT_ID
⚠️
.env.local មិនត្រូវ Push ទៅ GitHub ឡើយ! ត្រូវ Add ក្នុង Vercel Dashboard📁 ផ្នែកទី ៣
Project Structure
ai-webapp/
├── app/
│ ├── layout.js
│ ├── page.js
│ ├── login/page.js
│ ├── dashboard/page.js
│ ├── chat/page.js
│ ├── videos/page.js ← Video page
│ └── api/
│ ├── chat/route.js ← Gemini API
│ └── telegram/route.js
├── components/
│ ├── VideoPlayer.js ← Protected player
│ └── ProtectedRoute.js
├── lib/
│ ├── supabase.js
│ └── gemini.js
├── .env.local
└── package.json
npx create-next-app@latest ai-webapp --js --tailwind --app --no-src-dir
cd ai-webapp
npm install @google/generative-ai @supabase/supabase-js
npm run dev
🔐 ផ្នែកទី ៤
Supabase Auth (ភ្ជាប់ Project ចាស់)
ប្រើ Supabase Project ដែលភ្ជាប់ @reankh_bot — Copy URL & Anon Key ពី Dashboard → Settings → API
// lib/supabase.js
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)
// app/login/page.js
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { supabase } from '@/lib/supabase'
export default function LoginPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [mode, setMode] = useState('login') // 'login' | 'register'
const [error, setError] = useState('')
async function handleSubmit(e) {
e.preventDefault()
setError('')
const fn = mode === 'register'
? supabase.auth.signUp({ email, password })
: supabase.auth.signInWithPassword({ email, password })
const { error } = await fn
if (error) setError(error.message)
else router.push('/dashboard')
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={e=>setEmail(e.target.value)} required />
<input type="password" value={password} onChange={e=>setPassword(e.target.value)} required />
{error && <p className="text-red-400">{error}</p>}
<button type="submit">{mode === 'login' ? 'Login' : 'Register'}</button>
<button type="button" onClick={()=>setMode(m=>m==='login'?'register':'login')}>
{mode === 'login' ? 'Register' : 'Login'}
</button>
</form>
)
}
🤖 ផ្នែកទី ៥
Gemini AI Chat
// app/api/chat/route.js
import { GoogleGenerativeAI } from '@google/generative-ai'
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY)
export async function POST(request) {
const { message, history } = await request.json()
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' })
const chat = model.startChat({ history: history || [] })
const result = await chat.sendMessage(message)
return Response.json({ reply: result.response.text() })
}
👁️ ផ្នែកទី ៦
AI Image Reader (Vision)
// app/api/vision/route.js
import { GoogleGenerativeAI } from '@google/generative-ai'
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY)
export async function POST(request) {
const formData = await request.formData()
const file = formData.get('image')
const prompt = formData.get('prompt') || 'អ្វីដែលឃើញ?'
const bytes = await file.arrayBuffer()
const base64 = Buffer.from(bytes).toString('base64')
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' })
const result = await model.generateContent([
{ inlineData: { mimeType: file.type, data: base64 } },
prompt
])
return Response.json({ reply: result.response.text() })
}
💾 ផ្នែកទី ៧
Save ទៅ Supabase
-- Supabase SQL Editor — Run នេះ
create table if not exists ai_chats (
id uuid default gen_random_uuid() primary key,
user_id uuid references auth.users(id) on delete cascade,
role text not null check (role in ('user','model')),
message text not null,
created_at timestamptz default now()
);
alter table ai_chats enable row level security;
create policy "User owns chats" on ai_chats for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);
-- Video table
create table if not exists videos (
id uuid default gen_random_uuid() primary key,
title text not null,
description text,
video_url text not null,
thumbnail_url text,
sort_order int default 0,
is_active boolean default true,
created_at timestamptz default now()
);
alter table videos enable row level security;
create policy "Anyone logged in can view videos" on videos
for select using (auth.uid() is not null and is_active = true);
📱 ផ្នែកទី ៨
Telegram + AI
// app/api/telegram/route.js
export async function POST(request) {
const { message } = await request.json()
const res = await fetch(
`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`,
{ method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ chat_id: process.env.TELEGRAM_CHAT_ID,
text: message, parse_mode: 'HTML' }) }
)
return Response.json(await res.json())
}
🚀 ផ្នែកទី ៩
Deploy · reankh.org
git init && git add . && git commit -m "feat: AI webapp"
git branch -M main
git remote add origin https://github.com/honmomis/ai-webapp.git
git push -u origin main
# បន្ទាប់ Import ក្នុង Vercel → Add Env Vars → Deploy
✅ ផ្នែកទី ១០
Checklist
🎉