Skip to content

feat(chat): implement real-time chat system, AI chatbot, group management, and attachments#67

Merged
ilramdhan merged 13 commits into
mutugading:mainfrom
ilramdhan:fix/master-batch-costing
Jul 16, 2026
Merged

feat(chat): implement real-time chat system, AI chatbot, group management, and attachments#67
ilramdhan merged 13 commits into
mutugading:mainfrom
ilramdhan:fix/master-batch-costing

Conversation

@ilramdhan

Copy link
Copy Markdown
Member

Description

Pull Request ini menghadirkan fitur Real-Time Chat System dan integrasi AI Chatbot secara komprehensif. Inisiatif ini mencakup pembuatan sistem pengiriman pesan antar-pengguna (peer-to-peer & grup) dengan dukungan Server-Sent Events (SSE) untuk pembaruan instan, Presence Provider untuk status online dan typing, serta dukungan file attachments hingga 25MB.

Sebagai tambahan, PR ini menyertakan AI Chatbot pintar (ditenagai oleh model DeepSeek) yang dilengkapi dengan 7-layer guardrails dan fungsi pemanggilan alat (tool executor) secara streaming. Berbagai UI/UX polish tingkat lanjut—seperti browser push notifications beserta efek suara, laci obrolan responsif (chat drawer), status read receipts (centang biru), hingga manajemen grup—turut diimplementasikan untuk memberikan pengalaman komunikasi yang mulus di dalam platform.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 🎨 UI/UX improvement
  • ♻️ Refactor
  • 📚 Documentation
  • 🔧 Chore (deps, config)

Module/Component Affected

  • Dashboard
  • Finance
  • HR / IT / CI / EXSIM (Chat / Communications)
  • Components (common/)
  • Components (ui/)
  • Navigation
  • API Routes (BFF)

Changes Made

1. Real-Time Chat & Presence System:

  • Membangun arsitektur komunikasi real-time menggunakan SSE (Server-Sent Events) dan gRPC clients, didukung oleh TanStack Query hooks dan manajemen state Zustand.
  • Mengimplementasikan PresenceProvider yang melakukan mekanisme heartbeat untuk melacak dan menampilkan pengguna yang sedang online beserta indikator status pengetikan (typing indicator) menggunakan nama asli via UserName.
  • Menambahkan Browser Push Notifications (mendukung deteksi fokus tab browser) dengan peringatan suara via AudioContext serta badge notifikasi unread di judul tab.

2. AI Chatbot Integration:

  • Menambahkan Chatbot Panel (fixed floating) dan integrasi FAB dengan engine streaming model DeepSeek.
  • Menyiapkan 7-layer guardrails (deteksi prompt injection) dan 10 read-only tools yang dilindungi dengan validasi Zod.

3. Advanced Chat Features (Group, Attachments, Modifiers):

  • Group Management: UI dan BFF routes untuk pembuatan grup, mengubah nama grup, serta menambah/menghapus/meninggalkan anggota obrolan.
  • Attachments: Dukungan unggahan multipart (maks. 25MB) dengan pratinjau chip, image lightbox, dan pelacakan progres unggahan di MessageInput.
  • Message Controls: Dukungan inline edit message, soft-delete (menampilkan label [deleted]), fitur clear history, serta panel riwayat perubahan pesan (edit history).
  • Read Receipts: Logika read receipts (centang biru ganda) yang presisi, dihitung secara seketika (real-time via event read_receipt SSE), dengan mengecualikan laporan baca milik pengirim sendiri.

4. UI Polish & Critical Fixes:

  • Zustand Optimization: Memperbaiki bug infinite loop pada MessageThread akibat referensi array baru di dalam selector dengan mengimplementasikan pola useShallow dan useMemo.
  • Responsive Drawer: Memastikan laci obrolan (ChatDrawer) responsif dengan tata letak panel tunggal (single-panel layout) di perangkat seluler yang menyembunyikan daftar conversation saat thread sedang aktif.
  • User Picker: Mengganti kolom input UUID mentah menjadi UserPicker combobox yang dilengkapi fungsi pencarian otomatis dan titik indikator status online.
  • React-Compiler Fix: Mengganti metode setState-in-effect dengan penanganan di onOpenChange agar sesuai dengan best practice dan menghilangkan peringatan lint.

Related Issues

Fixes #
Related to #

Screenshots

Before

After

Testing Performed

Manual Testing

  • Desktop (1440px+)
  • Tablet (768px)
  • Mobile (375px)
  • Light mode
  • Dark mode

Browser Testing

  • Chrome
  • Firefox
  • Safari
  • Edge

Build Verification

  • npm run lint passes
  • npx tsc --noEmit passes
  • npm run build succeeds

Accessibility

  • Keyboard navigation works
  • Screen reader compatible
  • Proper ARIA labels
  • Color contrast adequate

Performance

  • No unnecessary re-renders (Fixed Zustand Selector Infinite Loop)
  • Images optimized
  • Heavy components lazy loaded

Pre-merge Checklist

  • I have read and followed RULES.md
  • I have read and followed CONTRIBUTING.md
  • Loading states implemented (if data fetching)
  • Error handling present
  • Component props typed properly
  • Uses semantic color classes
  • Responsive design tested
  • Dark mode compatible
  • Screenshots included (for UI changes)

Reviewer Notes

  • Zustand Selectors: Untuk pengembangan UI obrolan (chat) berikutnya, mohon perhatikan penggunaan useShallow dari Zustand. Jangan me-return nilai fallback array baru [] langsung di dalam fungsi selector karena akan memicu render tiada akhir (infinite loop) akibat kegagalan caching getSnapshot pada kapabilitas React rendering.
  • AudioContext & Notifications: Implementasi notifikasi suara beep menggunakan AudioContext dirancang agar aman terhadap kebijakan autoplay dari browser, dan hanya aktif saat permission notification browser telah diberikan oleh pengguna saat proses autentikasi.
  • SSE Data Flattening: Data bertingkat (nested oneof proto) telah diratakan (flattened) di dalam flattenChatEvent() untuk menyesuaikan dengan kebutuhan manajemen state frontend sehingga pembaruan UI instan tanpa reload halaman dapat tereksekusi dengan efisien.

ilramdhan and others added 13 commits July 16, 2026 05:04
Chat System:
- TypeScript types + normalizers for conversations/messages
- Zustand stores: chat-store, presence-store
- Providers: ChatProvider (SSE events), PresenceProvider (heartbeat)
- TanStack Query hooks: useConversations, useMessages, useSendMessage
- BFF API routes: 9 routes for chat + presence endpoints
- SSE stream extended with event: chat frames
- UI: ConversationList, MessageThread, MessageBubble (read receipts),
  ChatFab, ChatDrawer (Sheet right), /chat email-client page
- gRPC clients: getChatClient(), getPresenceClient()

AI Chatbot:
- DeepSeek streaming client with SYSTEM_PROMPT (Indonesian)
- 7-layer guardrails: prompt injection detection (10 regex patterns)
- 10 read-only tools with Zod validation + tool executor
- Streaming BFF route with tool call handling
- Zustand persist store (survives navigation, localStorage)
- ChatbotPanel (fixed floating), ChatbotFab, streaming UI
- Dashboard layout wired with both FABs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Browser Notification API: shows desktop notification when chat message
  arrives while tab is hidden or user is viewing a different conversation
- Notification sound: 300ms sine wave beep via AudioContext
- Tab title badge: shows unread count as (N) prefix
- Permission request: prompted once on auth via notification-provider

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New Conversation dialog: swap raw UUID Input for searchable UserPicker
  combobox with debounced search by name/email
- UserPicker: add showOnlineStatus prop — green/gray dot per user
- ConversationList: add "Online Now" avatar row at top — click to DM
- ChatFab: show online user count badge + tooltip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nline users fetch

1. Chat Drawer: w-full responsive width, single-panel mobile layout
   with back button (hides conversation list when thread is active)
2. New Conversation: add error toast on mutation failure, close dialog
   on success even if conversationId is empty
3. Presence Provider: fetch all online users on mount + refresh every
   60s so "Online Now" section works without chat history

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The selector `s.messages[conversationId] ?? []` creates a new array
reference on every getSnapshot call, violating React's requirement
that getSnapshot results be cached. Use useShallow + useMemo to
stabilize the reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace manual Avatar/AvatarFallback with UserAvatar (resolves profile
photo + initials via useUser hook) and UserName (resolves full name).
Fixes "Unknown User" / "UN" initials for online users whose data
wasn't in the lightweight lookup map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…from flat fields

SSE bridge: flattenChatEvent() converts proto StreamChatEventsResponse
(nested oneof) into flat ChatSSEEvent fields the store expects.
ChatProvider: builds ChatMessage object from flat SSE fields when
message_received/edited arrives without a nested message object.
Enables real-time chat delivery without page reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Browser notification: document.hidden → document.hasFocus()
   Shows notification when window unfocused (not just tab hidden)

2. Chat store handleSSEEvent: on message_received, also update
   conversation's lastMessage preview and increment unreadCount
   (skip increment if conversation is currently active)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…close button

Fix 1-2: Conversation preview — compact time format (5m/14:30/Yesterday/Jul 15),
  proper truncation layout, lastMessage + unreadCount live update via SSE

Fix 3: Thread header with conversation name + close (X) button — works in
  both drawer and /chat page, deselects active conversation

Fix 4: Typing indicator shows actual user names ("Ilham is typing...")
  instead of "Someone is typing..." — store tracks {id, name} pairs

Wire 7: Edit message — inline textarea on hover menu, Enter to save
Wire 8: Delete message — ConfirmDialog, soft-delete shows [deleted]
Wire 9: Edit history dialog — view previous versions with timestamps

New files: edit-history-dialog.tsx
New hooks: useEditMessage, useDeleteMessage, useEditHistory
New types: RawEditHistoryEntry, normalizeEditHistoryEntry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…B theme

E+F: Drawer showCloseButton={false} — suppress default Sheet X that
  overlapped with conversation list + button and thread close button.
  Added dedicated close button in conversation list header instead.

G: Typing indicator fallback — uses <UserName> component when SSE
  userName is empty, resolves actual name from API.

H: Online users — cap to 8 visible avatars + "+N more" badge,
  hidden scrollbar, edge fade gradient indicator.

I: ChatFab variant toggle — matches ChatbotFab pattern:
  variant={isOpen ? "default" : "secondary"}

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sender auto-creates a read receipt on send. In 2-person DM this made
receipts.length=1 >= participantCount-1=1, showing blue double-check
immediately. Now filters out sender's own userId before computing
readByAll/delivered thresholds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Frontend for the three remaining chat features, consistent across both the
chat drawer and the /chat page.

Group management:
- New Group dialog, group settings panel (rename, add/remove members, leave),
  and hooks (useAddParticipants, useRemoveParticipant, useUpdateGroup,
  useLeaveConversation) with participant BFF routes.

Clear history:
- useClearHistory hook + DELETE history BFF route, wired into the thread menu.

Attachments:
- Upload BFF route (multipart, 25MB, type validation) + useUploadAttachment.
- attachment-preview (inline images with lightbox, downloadable file chips),
  paperclip picker with pending chips and upload progress in MessageInput,
  attachment rendering in MessageBubble, and "attachment" last-message preview.
- SSE bridge threads attachments through real-time message events.

Also resolves react-compiler lint errors in the chat dialogs and provider by
replacing setState-in-effect resets with onOpenChange handlers and the
adjust-state-during-render pattern.

Authored-By: Ilham R <ilhamram332@gmail.com>
Co-Authored-By: Ilham R <me@ilramdhan.dev>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: IT Mutugading <it@mutugading.com>
Co-Authored-By: Indra Putro <indraputro@mutugading.com>
Co-Authored-By: Ilham R <ilham.ramadhan@mutugading.com>
Co-Authored-By: IT Mutu Gading <mutugadingapps@gmail.com>
The chat store dropped read_receipt SSE events (default case), so a sender
never learned the peer had read their messages until a full refetch — the
checkmark stayed a single gray tick even after the peer read and replied.

Add a markReadBy store action and a read_receipt case in handleSSEEvent that
appends the reader's receipt to every message they can see, flipping the
sender's checkmark to blue in real time.

Authored-By: Ilham R <ilhamram332@gmail.com>
Co-Authored-By: Ilham R <me@ilramdhan.dev>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: IT Mutugading <it@mutugading.com>
Co-Authored-By: Indra Putro <indraputro@mutugading.com>
Co-Authored-By: Ilham R <ilham.ramadhan@mutugading.com>
Co-Authored-By: IT Mutu Gading <mutugadingapps@gmail.com>
@ilramdhan ilramdhan added this to the Costing Release Milestone milestone Jul 16, 2026
@ilramdhan ilramdhan self-assigned this Jul 16, 2026
Copilot AI review requested due to automatic review settings July 16, 2026 17:18
@ilramdhan ilramdhan added the bug Something isn't working label Jul 16, 2026
@ilramdhan ilramdhan added documentation Improvements or additions to documentation enhancement New feature or request feat fix labels Jul 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ilramdhan
ilramdhan merged commit 9516b2a into mutugading:main Jul 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request feat fix

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants