สร้าง LINE Bot บันทึกรายรับ–รายจ่ายอัตโนมัติด้วย Gemini AI + Supabase แบบทำตามได้จริง
ทุกวันนี้หลายคนอยากจดรายรับ–รายจ่าย แต่ปัญหาคือ “ขี้เกียจกรอกฟอร์ม”
ต้องเปิดแอป เลือกหมวด พิมพ์จำนวนเงิน เลือกวันที่ แล้วกดบันทึก สุดท้ายก็เลิกจดไปในไม่กี่วัน
แต่ถ้าเราทำให้การบันทึกเงินง่ายเหมือนแชทกับเพื่อนล่ะ ?
เช่น
หรือ
ระบบก็รู้ทันทีว่าเป็น รายรับ หมวดเงินเดือน จำนวน 20,000 บาท
หรือ
บทความนี้จะพาไปสร้างระบบ ManageMoney LINE Bot ซึ่งเป็น LINE Bot สำหรับบันทึกรายรับ–รายจ่าย โดยใช้
- LINE Messaging API รับข้อความจากผู้ใช้
- Google Gemini AI วิเคราะห์ข้อความภาษาไทย
- Supabase PostgreSQL เก็บข้อมูลรายรับ–รายจ่าย
- Node.js + Express ทำ backend และ webhook
โปรเจกต์ตัวอย่าง: https://github.com/James-Chirawat/managemoney
โปรเจกต์นี้ทำอะไรได้บ้าง ?
ManageMoney LINE Bot คือบอทที่ให้ผู้ใช้พิมพ์ข้อความภาษาไทยธรรมดา แล้วระบบจะวิเคราะห์และบันทึกธุรกรรมทางการเงินให้อัตโนมัติ
จุดเด่นของระบบนี้คือ ผู้ใช้ไม่ต้องจำ command ไม่ต้องพิมพ์รูปแบบตายตัว เช่น /expense food 80 แต่สามารถพิมพ์แบบภาษาคนจริง ๆ ได้
Architecture ภาพรวมของระบบ
Flow การทำงานหลักของระบบเป็นแบบนี้
พูดง่าย ๆ คือ LINE เป็นหน้าบ้าน, Node.js เป็น backend, Gemini เป็นสมองวิเคราะห์ข้อความ และ Supabase เป็นฐานข้อมูล
Component Diagram
เพื่อให้เห็นหน้าที่ของแต่ละส่วนชัดขึ้น ลองดูภาพรวม component ของระบบ
Tech Stack ที่ใช้
จาก package.json โปรเจกต์นี้ใช้ dependency หลักดังนี้
{
"dependencies": {
"@google/generative-ai": "^0.24.0",
"@line/bot-sdk": "^9.6.0",
"@supabase/supabase-js": "^2.104.0",
"dotenv": "^16.4.7",
"express": "^4.21.2"
}
}หน้าที่ของแต่ละตัวคือ
express สร้าง web server และ webhook endpoint
@line/bot-sdk เชื่อมกับ LINE Messaging API
@google/generative-ai เรียกใช้งาน Gemini AI
@supabase/supabase-js เชื่อมต่อ Supabase
Databasedotenvโหลดค่า environment variables จาก .env
โครงสร้างโปรเจกต์
โครงสร้างหลักของโปรเจกต์แบ่งค่อนข้างดี อ่านง่าย และแยกหน้าที่ชัดเจน
managemoney/
├── src/
│ ├── index.js
│ ├── app.js
│ ├── config.js
│ ├── handlers/
│ │ └── messageHandler.js
│ ├── services/
│ │ ├── lineService.js
│ │ ├── geminiService.js
│ │ └── transactionService.js
│ ├── messages/
│ │ ├── textReplies.js
│ │ ├── flexTransaction.js
│ │ ├── flexSummary.js
│ │ └── index.js
│ ├── state/
│ │ └── pendingConfirmations.js
│ ├── utils/
│ │ ├── dateParser.js
│ │ ├── moneyParser.js
│ │ └── transactionRules.js
│ └── constants/
│ ├── categories.js
│ └── prompts.js
├── .env.example
├── .gitignore
├── package.json
└── README.mdแนวคิดสำคัญคือแยกเป็น 4 ส่วนหลัก
- App / Webhook Layer — รับ request จาก LINE
- Handler Layer — ตัดสินใจว่าข้อความนี้ต้องทำอะไร
- Service Layer — เชื่อมต่อ Gemini, LINE, Supabase
- Utils / Rules Layer — แยก logic ย่อย เช่น parse วันที่, parse เงิน, rule การ auto-save
เตรียมเครื่องมือก่อนเริ่ม
ก่อนเริ่มติดตั้ง ควรมีสิ่งเหล่านี้
- Node.js เวอร์ชัน 18 ขึ้นไป
- npm
- Git
- LINE Developers Account
- Google AI Studio Account
- Supabase Account
- Render หรือ Railway สำหรับ deploy
ขั้นตอนที่ 1: Clone โปรเจกต์
git clone https://github.com/James-Chirawat/managemoney.git
cd managemoney
npm installหลังจากติดตั้ง dependencies แล้ว ให้ลองดู script ใน package.json
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js"
}ถ้ารันระหว่างพัฒนาใช้
npm run devถ้ารัน production ใช้
npm startขั้นตอนที่ 2: สร้าง LINE Bot
เข้าไปที่ LINE Developers Console
https://developers.line.biz/จากนั้นทำตามขั้นตอนนี้
- สร้าง Provider
- สร้าง Channel แบบ Messaging API
- เข้าไปที่ Channel settings
- คัดลอกค่าต่อไปนี้
- Channel Secret
- Channel Access Token
ค่าทั้งสองตัวนี้จะเอาไปใส่ในไฟล์ .env
ขั้นตอนที่ 3: สร้าง Gemini API Key
เข้าไปที่ Google AI Studio
https://aistudio.google.com/apikeyจากนั้นสร้าง API Key แล้วเก็บไว้ก่อน
ในโปรเจกต์นี้ใช้ Gemini เพื่อแปลงข้อความภาษาไทยให้กลายเป็นข้อมูลแบบ structured JSON เช่น
จากข้อความนี้
กินข้าวมันไก่ 80ให้กลายเป็นข้อมูลประมาณนี้
{
"item": "ข้าวมันไก่",
"amount": 80,
"category": "อาหาร",
"type": "รายจ่าย",
"date": "2026-04-24",
"confidence": 0.95,
"missing_fields": []
}ขั้นตอนที่ 4: สร้าง Supabase Database
เข้าไปที่
https://supabase.com/แล้วสร้าง Project ใหม่
จากนั้นไปที่
Project Settings → APIคัดลอก
- Project URL
- anon public key
จากนั้นไปที่ SQL Editor แล้วรัน SQL นี้
CREATE TABLE transactions (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
item TEXT NOT NULL,
amount NUMERIC NOT NULL,
category TEXT NOT NULL,
type TEXT NOT NULL CHECK (type IN ('รายรับ', 'รายจ่าย')),
date DATE NOT NULL,
line_user_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow all inserts" ON transactions
FOR INSERT WITH CHECK (true);
CREATE POLICY "Allow all selects" ON transactions
FOR SELECT USING (true);
CREATE INDEX idx_transactions_user ON transactions (line_user_id);
CREATE INDEX idx_transactions_date ON transactions (date);ตารางนี้จะเก็บข้อมูลธุรกรรมทั้งหมด โดยมี column สำคัญคือ
item ชื่อรายการ เช่น ข้าวมันไก่
amount จำนวนเงิน
category หมวดหมู่ เช่น อาหาร
type รายรับ หรือ รายจ่าย
dateวันที่เกิดรายการ
line_user_id LINE user ID ของผู้ใช้
created_at เวลาที่บันทึกข้อมูล
ขั้นตอนที่ 5: ตั้งค่า Environment Variables
สร้างไฟล์ .env
cp .env.example .envแล้วใส่ค่าประมาณนี้
LINE_CHANNEL_ACCESS_TOKEN=your_line_channel_access_token
LINE_CHANNEL_SECRET=your_line_channel_secret
GEMINI_API_KEY=your_gemini_api_key
GEMINI_MODEL=gemini-2.0-flash
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_TABLE=transactions
PORT=3000ข้อควรระวังมาก ๆ คือห้าม commit ไฟล์ .env ขึ้น GitHub เพราะมี secret key อยู่ข้างใน
อธิบายโค้ดสำคัญ
ต่อไปมาดูส่วนสำคัญของระบบกัน
1. app.js — จุดรับ Webhook จาก LINE
ไฟล์นี้เป็นหัวใจของฝั่ง server เพราะเป็นตัวสร้าง Express app และเปิด endpoint ให้ LINE ส่ง event เข้ามา
โค้ดส่วนสำคัญคือ
const express = require('express');
const line = require('@line/bot-sdk');
const { config } = require('./config');
const { handleEvent } = require('./services/lineService');
function createApp() {
const app = express();
const lineMiddleware = line.middleware({
channelSecret: config.line.channelSecret,
});
app.get('/', (req, res) => {
res.json({
status: 'ok',
name: 'ManageMoney LINE Bot',
version: '1.0.0',
timestamp: new Date().toISOString(),
});
});
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.post('/webhook', lineMiddleware, async (req, res) => {
try {
const events = req.body.events || [];
if (events.length === 0) {
return res.status(200).json({ status: 'no events' });
}
const results = await Promise.allSettled(
events.map((event) => handleEvent(event))
);
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(`❌ Event ${index} failed:`, result.reason);
}
});
return res.status(200).json({ status: 'ok' });
} catch (error) {
console.error('❌ Webhook error:', error);
return res.status(500).json({
status: 'error',
message: error.message,
});
}
});
return app;
}
module.exports = { createApp };สิ่งที่น่าสนใจคือ
const lineMiddleware = line.middleware({
channelSecret: config.line.channelSecret,
});ตัวนี้ใช้ตรวจสอบว่า request ที่เข้ามาเป็นของ LINE จริงหรือไม่ โดย LINE จะส่ง signature มาที่ header แล้ว SDK จะช่วย validate ให้
อีกจุดที่ดีคือ
Promise.allSettled(events.map((event) => handleEvent(event)))LINE อาจส่ง event มาหลายตัวพร้อมกัน โค้ดนี้จึงประมวลผลทุก event และถ้าบาง event error ก็ไม่ทำให้ทั้งหมดพัง
2. messageHandler.js — สมองหลักของการจัดการข้อความ
ไฟล์นี้เป็นตัวตัดสินใจว่า เมื่อผู้ใช้พิมพ์ข้อความมา ระบบควรทำอะไร
ตัวอย่างโค้ดหลัก
async function handleTextMessage(userId, userMessage) {
const pendingReply = await handlePendingConfirmation(userId, userMessage);
if (pendingReply) return pendingReply;
if (isGreeting(userMessage)) {
return GENERAL_RESPONSES.greeting;
}
if (isHelpRequest(userMessage)) {
return GENERAL_RESPONSES.help;
}
if (isAnalysisRequest(userMessage)) {
return buildSummaryReply(userId, userMessage);
}
if (userMessage.length < 2) {
return GENERAL_RESPONSES.help;
}
try {
console.log(`📩 [${userId || 'unknown'}] "${userMessage}"`);
const parsed = await parseExpenseMessage(userMessage);
console.log('🤖 Gemini:', JSON.stringify(parsed));
if (parsed.missing_fields && parsed.missing_fields.length > 0) {
return generateMissingFieldReply(parsed.missing_fields, parsed);
}
const transactionData = toTransactionData(parsed);
if (shouldAutoSaveTransaction(parsed, userMessage)) {
return saveAndBuildReply(transactionData, userId);
}
if (userId) {
setPending(userId, transactionData);
}
return generateConfirmQuickReply(transactionData);
} catch (error) {
console.error('❌ Error handling message:', error);
return GENERAL_RESPONSES.error;
}
}Flow ของ function นี้คือ
- ตรวจว่ามีรายการที่รอการยืนยันอยู่ไหม
- ถ้าเป็นคำทักทาย ให้ตอบ greeting
- ถ้าขอ help ให้ตอบคู่มือ
- ถ้าขอสรุป เช่น “สรุปเดือนนี้” ให้ดึงข้อมูลมาสรุป
- ถ้าเป็นข้อความรายรับรายจ่าย ให้ส่งไปให้ Gemini วิเคราะห์
- ถ้าข้อมูลไม่ครบ ให้ถามกลับ
- ถ้าข้อมูลชัดเจน ให้บันทึกทันที
- ถ้ากำกวม ให้ถามยืนยันก่อน
จุดที่น่าสนใจมากคือแนวคิด auto-save เมื่อมั่นใจ
if (shouldAutoSaveTransaction(parsed, userMessage)) {
return saveAndBuildReply(transactionData, userId);
}เช่น ถ้าผู้ใช้พิมพ์ว่า
กินข้าวมันไก่ 80ระบบมั่นใจว่าเป็นรายจ่ายแน่นอน จึงบันทึกทันที
แต่ถ้าพิมพ์ว่า
โอน 500คำว่า “โอน” อาจเป็นได้ทั้งโอนออกหรือมีคนโอนเข้า ระบบจึงไม่ควรบันทึกทันที แต่ควรถามยืนยัน
3. ระบบ Pending Confirmation
ใน messageHandler.js มี logic สำหรับจัดการข้อความที่ต้องรอผู้ใช้ยืนยัน
const CONFIRM_YES = [
'ใช่',
'yes',
'ใช่ครับ',
'ใช่ค่ะ',
'ตกลง',
'ok',
'โอเค',
'ได้',
'บันทึก',
'ถูก',
'ถูกต้อง',
'✅',
'👍'
];
const CONFIRM_NO = [
'ไม่',
'no',
'ไม่ใช่',
'ไม่ถูก',
'ผิด',
'ยกเลิก',
'cancel',
'❌',
'👎'
];เมื่อระบบไม่มั่นใจ จะเก็บรายการไว้ใน memory ก่อน
setPending(userId, transactionData);
return generateConfirmQuickReply(transactionData);ถ้าผู้ใช้ตอบว่า “ใช่” ระบบจึงค่อยบันทึก
if (CONFIRM_YES.some((word) => normalizedMessage === word)) {
clearPending(userId);
return saveAndBuildReply(pendingData, userId);
}ถ้าผู้ใช้ตอบว่า “ไม่” ระบบจะยกเลิก
if (CONFIRM_NO.some((word) => normalizedMessage === word)) {
clearPending(userId);
return 'ยกเลิกแล้วครับ ถ้าจะบันทึกใหม่ พิมพ์รายการมาได้เลย';
}แนวคิดนี้สำคัญมากสำหรับระบบที่ใช้ AI เพราะ AI อาจเดาผิดได้ เราจึงควรให้ระบบ “ถามกลับ” ในกรณีที่ไม่มั่นใจ
4. geminiService.js — วิเคราะห์ข้อความด้วย Gemini AI
ไฟล์นี้ทำหน้าที่แปลงข้อความภาษาคนให้กลายเป็น JSON ที่ระบบใช้ต่อได้
เริ่มจากสร้าง Gemini model
const { GoogleGenerativeAI } = require('@google/generative-ai');
const { config } = require('../config');
const genAI = new GoogleGenerativeAI(config.gemini.apiKey);
const model = genAI.getGenerativeModel({
model: config.gemini.model,
generationConfig: {
responseMimeType: 'application/json',
temperature: 0.15,
},
});จุดที่น่าสนใจคือ
responseMimeType: 'application/json'เป็นการบอก Gemini ว่าให้ตอบกลับมาเป็น JSON เพื่อให้เรา JSON.parse() ต่อได้ง่าย
และ
temperature: 0.15เป็นการตั้งค่าให้คำตอบค่อนข้างนิ่ง ไม่สุ่มมากเกินไป เหมาะกับงาน parse ข้อมูล
Function หลัก: parseExpenseMessage
async function parseExpenseMessage(userMessage) {
try {
const today = getToday();
const yesterday = getYesterday();
const systemPrompt = buildSystemPrompt(today, yesterday);
const userPrompt = buildUserPrompt(userMessage);
const result = await model.generateContent({
contents: [
{
role: 'user',
parts: [{ text: systemPrompt + '\n\n' + userPrompt }],
},
],
});
const responseText = result.response.text();
const parsed = JSON.parse(responseText);
return sanitizeResult(parsed, userMessage);
} catch (error) {
console.error('❌ Gemini AI Error:', error.message);
return fallbackParse(userMessage);
}
}Flow คือ
- ดึงวันที่วันนี้และเมื่อวาน
- สร้าง prompt
- ส่งข้อความไปให้ Gemini
- รับผลลัพธ์เป็น JSON
- ตรวจสอบและ sanitize ข้อมูล
- ถ้า Gemini พัง ให้ใช้ fallback parser
ส่วนที่ดีมากคือมี fallback
return fallbackParse(userMessage);เพราะในการใช้งานจริง AI API อาจล่ม, timeout, ตอบ JSON ไม่ถูก หรือ quota เต็มได้ ระบบจึงไม่ควรหยุดทำงานทันที
5. sanitizeResult — กัน AI ตอบเพี้ยน
ถึงเราจะใช้ AI แต่เราไม่ควรเชื่อ AI แบบ 100%
Get Codequeror’s stories in your inbox
Join Medium for free to get updates from this writer.
ระบบนี้จึงมี function สำหรับ validate และ sanitize ผลลัพธ์จาก Gemini
ตัวอย่าง logic สำคัญ
if (parsed.amount !== null && parsed.amount !== undefined) {
parsed.amount = parseAmountValue(parsed.amount);
if (parsed.amount === null) {
parsed.amount = null;
if (!parsed.missing_fields) parsed.missing_fields = [];
if (!parsed.missing_fields.includes('amount')) {
parsed.missing_fields.push('amount');
}
}
} else {
parsed.amount = extractAmount(originalText);
}หมายความว่า ถ้า Gemini ให้จำนวนเงินมา ระบบจะเช็กอีกทีว่าเป็นตัวเลขจริงไหม
ถ้า Gemini ไม่ได้ให้จำนวนเงินมา ระบบจะลองดึงจากข้อความเองด้วย extractAmount
ต่อมาคือตรวจประเภท
if (!['รายรับ', 'รายจ่าย'].includes(parsed.type)) {
parsed.type = keywordType;
parsed.confidence = Math.min(parsed.confidence || 0.5, 0.6);
}ถ้า Gemini ตอบ type แปลก ๆ เช่น "income" หรือ "expense" ระบบจะใช้ keyword rule แทน และลด confidence ลง
ตรวจหมวดหมู่
if (!ALL_CATEGORIES.includes(parsed.category)) {
parsed.category = keywordCategory;
parsed.confidence = Math.min(parsed.confidence || 0.5, 0.7);
}ถ้า Gemini ให้หมวดที่ไม่มีในระบบ ก็เปลี่ยนกลับมาใช้หมวดที่ระบบรองรับ
ตรวจวันที่
if (!parsed.date || !isValidDate(parsed.date)) {
parsed.date = parsedDateFromText || today;
}ถ้าไม่มีวันที่ หรือวันที่ผิด format ให้ใช้วันที่จากข้อความ หรือ default เป็นวันนี้
นี่คือ pattern ที่ดีมากสำหรับการใช้ AI ใน production:
ให้ AI ช่วยคิด แต่ระบบต้อง validate เองเสมอ
6. fallbackParse — ระบบสำรองเมื่อ Gemini ใช้งานไม่ได้
ถ้า Gemini error ระบบจะมาใช้ fallback parser
function fallbackParse(text) {
const today = getToday();
const amount = extractAmount(text);
const item = cleanTransactionItem(text);
const type = classifyByKeyword(text);
const category = categorizeByKeyword(text, type);
const missing_fields = [];
if (amount === null) missing_fields.push('amount');
if (item === null) missing_fields.push('item');
return {
item,
amount,
category,
type,
date: parseDateFromText(text) || today,
confidence: isAmbiguousTransactionText(text) ? 0.35 : 0.88,
missing_fields,
reply_hint: 'ใช้ fallback parser',
};
}ตัวอย่างเช่น ถ้า Gemini ใช้ไม่ได้ แต่ผู้ใช้พิมพ์ว่า
ค่ารถ 40ระบบยังสามารถใช้ rule ธรรมดาในการดึง
- amount = 40
- item = ค่ารถ
- type = รายจ่าย
- category = เดินทาง
ได้อยู่ นี่ทำให้ระบบ robust ขึ้นมาก
7. transactionService.js — บันทึกและอ่านข้อมูลจาก Supabase
ไฟล์นี้รับผิดชอบการเชื่อมต่อฐานข้อมูล
เริ่มจากสร้าง Supabase client
const { createClient } = require('@supabase/supabase-js');
const { config } = require('../config');
const supabase = createClient(
config.supabase.url,
config.supabase.anonKey
);
const TABLE = config.supabase.table;แปลงข้อมูลก่อน insert
function toTransactionRow(data, lineUserId = null) {
const { item, amount, category, type, date } = data;
return {
item,
amount,
category,
type,
date,
line_user_id: lineUserId,
};
}บันทึกข้อมูล
async function appendTransaction(data, lineUserId = null) {
try {
const { data: inserted, error } = await supabase
.from(TABLE)
.insert([toTransactionRow(data, lineUserId)])
.select();
if (error) {
console.error('❌ Supabase Insert Error:', error.message);
return { success: false, error: error.message };
}
console.log(
`✅ บันทึก: ${data.item} | ${data.amount} | ${data.category} | ${data.type} | ${data.date}`
);
return { success: true, data: inserted };
} catch (error) {
console.error('❌ Supabase Error:', error.message);
return { success: false, error: error.message };
}
}อ่านข้อมูลเพื่อทำ summary
async function getTransactions(lineUserId, dateFrom, dateTo) {
try {
let query = supabase
.from(TABLE)
.select('item, amount, category, type, date')
.gte('date', dateFrom)
.lte('date', dateTo)
.order('date', { ascending: true });
if (lineUserId) {
query = query.eq('line_user_id', lineUserId);
}
const { data, error } = await query;
if (error) {
console.error('❌ Supabase Select Error:', error.message);
return null;
}
return data;
} catch (error) {
console.error('❌ Supabase Error:', error.message);
return null;
}
}จุดสำคัญคือ
query = query.eq('line_user_id', lineUserId);ทำให้ผู้ใช้แต่ละคนเห็นข้อมูลของตัวเอง ไม่ปนกับคนอื่น
Sequence Diagram: การบันทึกรายจ่าย 1 รายการ
ลองดู flow แบบ sequence เมื่อผู้ใช้พิมพ์ว่า กินข้าวมันไก่ 80
การรันบนเครื่องตัวเอง
หลังจากตั้งค่า .env แล้ว ให้รัน
npm run devถ้าระบบทำงาน จะเห็น server start ที่ port 3000
แต่ LINE ต้องการ webhook ที่เป็น HTTPS public URL ดังนั้นถ้าจะทดสอบบนเครื่อง local อาจใช้ ngrok
ngrok http 3000แล้วเอา URL ที่ได้ เช่น
https://xxxx.ngrok-free.app/webhookไปใส่ใน LINE Developers Console ที่ช่อง Webhook URL
ตั้งค่า Webhook ใน LINE
ไปที่ LINE Developers Console
Messaging API → Webhook settingsตั้งค่า Webhook URL เป็น
https://your-domain.com/webhookจากนั้นเปิด
Use webhook: Enabledแล้วกด Verify
ถ้า Verify ผ่าน แปลว่า LINE ส่ง request มาถึง server ได้แล้ว
ทดสอบการใช้งาน
หลัง deploy แล้ว ลองเพิ่มบอทเป็นเพื่อนใน LINE แล้วพิมพ์
กินข้าวมันไก่ 80ถ้าทุกอย่างถูกต้อง ระบบควรตอบกลับว่าบันทึกแล้ว
ลองพิมพ์
เงินเดือน 25000ควรถูกบันทึกเป็นรายรับ
ลองพิมพ์
โอน 500ระบบควรถามยืนยันก่อน เพราะยังไม่ชัดเจนว่าเงินเข้า หรือเงินออก
ลองพิมพ์
สรุปเดือนนี้ระบบควรดึงข้อมูลจาก Supabase มาสรุปให้
ประโยชน์ของโปรเจกต์นี้
โปรเจกต์นี้มีประโยชน์หลายด้าน ทั้งในมุมผู้ใช้จริงและมุมคนพัฒนา
1. ลด friction ในการบันทึกข้อมูล
ผู้ใช้ไม่ต้องเปิดแอป ไม่ต้องเลือกหมวด ไม่ต้องกรอกฟอร์ม แค่พิมพ์เหมือนคุยกับเพื่อน
2. ใช้ AI กับงานที่จับต้องได้จริง
ไม่ใช่แค่ chatbot ถามตอบทั่วไป แต่เป็น AI ที่ช่วยแปลงภาษาธรรมชาติเป็น structured data แล้วเอาไปใช้งานต่อได้จริง
3. เป็นพื้นฐานของ Personal Finance Assistant
ระบบนี้สามารถต่อยอดเป็นผู้ช่วยการเงินส่วนตัวได้ เช่น แจ้งเตือนใช้เงินเกินงบ วิเคราะห์พฤติกรรมการใช้จ่าย หรือแนะนำวิธีประหยัดเงิน
4. โครงสร้างเหมาะกับการต่อยอด
เพราะแยก service, handler, utils และ messages ชัดเจน จึงแก้ไขหรือต่อยอดได้ง่าย
แนวทางการต่อยอด
โปรเจกต์นี้ยังต่อยอดได้อีกเยอะมาก
1. ทำ Dashboard
เอาข้อมูลจาก Supabase ไปแสดงเป็น dashboard เช่น
- รายจ่ายรายวัน
- รายจ่ายรายเดือน
- หมวดที่ใช้เงินเยอะที่สุด
- กราฟรายรับรายจ่าย
- เงินคงเหลือ
ใช้ Next.js, React หรือ Vue ทำ frontend ได้
2. ตั้งงบประมาณรายเดือน
เพิ่มตาราง budgets
CREATE TABLE budgets (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
line_user_id TEXT NOT NULL,
category TEXT NOT NULL,
amount NUMERIC NOT NULL,
month TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);จากนั้นให้ผู้ใช้พิมพ์ เช่น
ตั้งงบอาหารเดือนนี้ 5000แล้วระบบบันทึกงบประมาณให้
ถ้าใช้เกินงบ บอทสามารถเตือนได้ว่า
เดือนนี้หมวดอาหารใช้ไป 4,800 จากงบ 5,000 แล้วนะครับ
เหลืออีกแค่ 200 บาท3. เพิ่มระบบลบหรือแก้ไขรายการ
ตอนนี้ระบบเน้นบันทึกและสรุป แต่สามารถเพิ่มคำสั่งเช่น
ลบรายการล่าสุดหรือ
แก้รายการล่าสุดเป็น 90 บาทโดยต้องเพิ่ม function ใน transactionService.js เช่น
async function deleteLatestTransaction(lineUserId) {
const { data, error } = await supabase
.from(TABLE)
.select('id')
.eq('line_user_id', lineUserId)
.order('created_at', { ascending: false })
.limit(1);
if (error || !data.length) {
return { success: false };
}
const latestId = data[0].id;
const { error: deleteError } = await supabase
.from(TABLE)
.delete()
.eq('id', latestId);
if (deleteError) {
return { success: false };
}
return { success: true };
}4. เพิ่มการแจ้งเตือนรายวัน
เช่น ทุก 21:00 น. บอทถามว่า
วันนี้มีรายจ่ายอะไรที่ยังไม่ได้บันทึกไหมครับ?สามารถใช้ cron job หรือ scheduled function ช่วยได้
5. เชื่อมกับ Google Sheets
ถึงโปรเจกต์นี้ใช้ Supabase แต่ถ้าอยากให้ผู้ใช้ดูข้อมูลง่าย ๆ ในรูปแบบ spreadsheet ก็สามารถ sync ข้อมูลไป Google Sheets เพิ่มได้
แนวทางคือ
Supabase → Scheduled Job → Google Sheets APIหรือทุกครั้งที่บันทึกข้อมูล ให้ append ลง Google Sheets ด้วย
6. เพิ่ม OCR จากสลิปหรือใบเสร็จ
ผู้ใช้ส่งรูปใบเสร็จเข้ามาใน LINE แล้วระบบอ่านข้อมูลจากภาพ เช่น
- ร้านค้า
- ยอดเงิน
- วันที่
- รายการสินค้า
จากนั้นส่งให้ Gemini ช่วยสรุปและบันทึก
7. เพิ่ม Personality ให้บอท
เช่น ให้บอทตอบแบบเป็นกันเองมากขึ้น
บันทึกแล้วครับ กินข้าวมันไก่ 80 บาท
ร้านนี้เริ่มแรงแล้วนะครับ 😆หรือถ้าได้เงินเดือน
เงินเดือนเข้า 25,000 บาทแล้วครับ
เดือนนี้ขอให้เหลือเก็บเยอะ ๆ น้าาา 🎉ส่วนนี้ทำได้ในไฟล์กลุ่ม messages/
ข้อควรระวังในการใช้ AI กับข้อมูลการเงิน
แม้ระบบนี้จะมีประโยชน์มาก แต่ข้อมูลการเงินเป็นข้อมูลส่วนตัว ควรระวังเรื่องเหล่านี้
1. อย่าเก็บ API Key ไว้ใน GitHub
ไฟล์ .env ต้องอยู่ใน .gitignore
2. ตรวจสอบสิทธิ์ Supabase RLS ให้ดี
ตัวอย่างในโปรเจกต์เปิด policy กว้างเพื่อให้ง่ายต่อการทดลอง แต่ถ้าจะใช้จริง ควรออกแบบ security ให้เข้มขึ้น
3. อย่าเชื่อ AI 100%
ควร validate ข้อมูลทุกครั้ง เช่น จำนวนเงิน ประเภท หมวดหมู่ และวันที่
4. ข้อมูลกำกวมต้องถามยืนยัน
ข้อความอย่าง “โอน 500” ไม่ควรถูกบันทึกทันที เพราะอาจเป็นเงินเข้า หรือเงินออกก็ได้
สิ่งที่ได้เรียนรู้จากโปรเจกต์นี้
โปรเจกต์นี้เป็นตัวอย่างที่ดีมากของการนำ AI มาใช้กับงานจริง เพราะไม่ได้ใช้ AI แค่ตอบคำถาม แต่ใช้ AI เป็นตัวช่วยแปลงภาษาธรรมชาติให้กลายเป็นข้อมูลที่ระบบนำไปประมวลผลต่อได้
สิ่งสำคัญที่ได้เรียนรู้คือ
- การทำ LINE Bot ด้วย Node.js
- การรับ Webhook จาก LINE
- การใช้ Gemini AI วิเคราะห์ภาษาไทย
- การออกแบบ JSON response จาก AI
- การ validate และ sanitize ผลลัพธ์จาก AI
- การบันทึกข้อมูลลง Supabase
- การออกแบบระบบ confirmation เมื่อข้อมูลกำกวม
- การทำระบบ multi-user ด้วย LINE user ID
สรุป
ManageMoney LINE Bot เป็นโปรเจกต์เล็กที่มีประโยชน์จริง และเหมาะมากสำหรับคนที่อยากฝึกทำระบบ AI Application แบบ end-to-end
จากไอเดียง่าย ๆ อย่าง “พิมพ์รายจ่ายใน LINE แล้วให้บอทบันทึกให้” สามารถต่อยอดเป็นระบบผู้ช่วยการเงินส่วนตัวที่ฉลาดขึ้นได้อีกมาก
เช่น วิเคราะห์นิสัยการใช้เงิน แจ้งเตือนงบประมาณ ทำ dashboard หรือแม้แต่ให้คำแนะนำการออมแบบ personalized
สำหรับใครที่อยากเริ่มทำ AI Product แบบจับต้องได้ โปรเจกต์นี้เป็นจุดเริ่มต้นที่ดีมาก เพราะได้เรียนรู้ทั้ง backend, AI, database และ user experience ไปพร้อมกัน
Repository ตัวอย่าง: https://github.com/James-Chirawat/managemoney







