-
Notifications
You must be signed in to change notification settings - Fork 285
Integrate S3 Chatting to frontend #651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Nikhil Maturi <code259@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR updates the student group UI to support live group chat plus “Shared Files” uploads/downloads (intended to back onto the new S3-backed endpoints from the linked Spring PR).
Changes:
- Replaces the static chat modal with a tabbed Chat / Shared Files UI and adds image attachment support.
- Adds frontend fetch helpers to load/send chat messages and to upload/list/download shared files.
- Extends the group dashboard to include an “Add File” action and wires it to the new chat/files UI.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
_posts/teacher_toolkit/StudentManagement/2025-04-07-Groups.html |
Adds tabbed chat/files UI, chat polling + send, and shared file upload/list/download behavior. |
_includes/group_dashboard.html |
Adds the same chat/files UI to the dashboard modal and introduces dashboard shared-file upload entrypoint. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function startChatPolling() { | ||
| if (chatPollTimer) clearInterval(chatPollTimer); | ||
| chatPollTimer = setInterval(fetchChatMessages, 4000); | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chat polling fetches and re-renders the full message list every 4 seconds. This can become expensive for both client and server as history grows. Consider incremental fetch (cursor/since), caching/ETags, and appending only new messages instead of clearing and rebuilding #chatMessages each poll.
| <script type="module"> | ||
| import { javaURI, pythonURI, fetchOptions } from '{{site.baseurl}}/assets/js/api/config.js'; | ||
| // Calendar state |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
group_dashboard.html was converted to <script type="module">, but the page still uses many inline event handlers (e.g., onclick="previousMonth()", onclick="refreshDashboard()", onchange="handleSharedFileUpload(event)"). In module scripts, top-level functions are not added to window, so these handlers will throw ReferenceError at runtime. Either export/assign the referenced functions onto window (e.g., window.previousMonth = previousMonth, etc.) or remove inline handlers and bind listeners in JS after DOM load.
_includes/group_dashboard.html
Outdated
| // Fetch group ID using group name | ||
| try { | ||
| const searchResponse = await fetch(`/api/group/search?name=${encodeURIComponent(groupName)}`); | ||
|
|
||
| if (!searchResponse.ok) { | ||
| throw new Error('Group not found'); | ||
| } | ||
|
|
||
| // Handle paste events for images | ||
| document.addEventListener('paste', function(event) { | ||
| // Only handle paste when chat modal is open | ||
| if (document.getElementById('chatModal').classList.contains('hidden')) return; | ||
| const groupData = await searchResponse.json(); | ||
| const groupId = groupData.id; | ||
|
|
||
| const items = (event.clipboardData || window.clipboardData).items; | ||
| let imageFile = null; | ||
| if (!groupId) { | ||
| alert('Error: Could not retrieve group ID'); | ||
| event.target.value = ''; | ||
| return; | ||
| } | ||
|
|
||
| for (let i = 0; i < items.length; i++) { | ||
| if (items[i].type.startsWith('image/')) { | ||
| imageFile = items[i].getAsFile(); | ||
| break; | ||
| // Convert file to base64 | ||
| const reader = new FileReader(); | ||
| reader.onload = async (e) => { | ||
| const base64Data = e.target.result.split(',')[1]; | ||
|
|
||
| // Prepare request body | ||
| const requestBody = { | ||
| filename: file.name, | ||
| base64Data: base64Data | ||
| }; | ||
|
|
||
| try { | ||
| // Make POST request to upload file | ||
| const response = await fetch(`/api/groups/chat/${groupId}/files`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify(requestBody) | ||
| }); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleSharedFileUpload is calling /api/group/search and /api/groups/chat/... as relative URLs and without fetchOptions. Elsewhere in this file, API calls consistently use ${javaURI} and fetchOptions (and /api/groups/search returns an array). As written, this will likely hit the GitHub Pages origin, fail auth, and mis-handle the search response shape. Reuse resolveActiveGroupId(...) + ${javaURI}/api/groups/chat/${id}/files with fetchOptions (and handle the search response as an array) to make dashboard uploads work reliably.
|
|
||
| if (msg.image) { | ||
| const img = document.createElement('img'); | ||
| img.src = `data:image/png;base64,${msg.image}`; |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chat message rendering hard-codes data:image/png;base64,... for msg.image. Since image selection allows image/* (and pasted images can be JPEG/GIF), non-PNG images will fail to render. Include the image MIME type in the payload/response (or store a full data URL) and use it when constructing img.src.
| img.src = `data:image/png;base64,${msg.image}`; | |
| let imageSrc; | |
| if (typeof msg.image === 'string' && msg.image.startsWith('data:')) { | |
| // msg.image is already a full data URL | |
| imageSrc = msg.image; | |
| } else if (msg.imageMimeType) { | |
| // Use provided MIME type for the base64 image data | |
| imageSrc = `data:${msg.imageMimeType};base64,${msg.image}`; | |
| } else { | |
| // Backward-compatible fallback: assume PNG | |
| imageSrc = `data:image/png;base64,${msg.image}`; | |
| } | |
| img.src = imageSrc; |
| imageMessageBox.innerHTML = ''; | ||
| } | ||
|
|
||
| document.addEventListener('paste', function(event) { |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The paste handler runs on every paste anywhere on the page and will attach an image even when the chat modal is closed. This can lead to unexpected hidden attachments. Add the same guard used in group_dashboard.html (skip when #chatModal has hidden) or scope the listener to the chat input/modal.
| document.addEventListener('paste', function(event) { | |
| document.addEventListener('paste', function(event) { | |
| const chatModal = document.getElementById('chatModal'); | |
| if (chatModal && chatModal.hasAttribute('hidden')) { | |
| return; | |
| } |
|
|
||
| if (msg.image) { | ||
| const img = document.createElement('img'); | ||
| img.src = `data:image/png;base64,${msg.image}`; |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chat message rendering hard-codes data:image/png;base64,... for msg.image. Since the UI accepts image/*, non-PNG images (e.g., JPEG) will not render correctly. Include image MIME type in the payload/response (or store a full data URL) and use it when setting img.src.
| img.src = `data:image/png;base64,${msg.image}`; | |
| // If msg.image is already a full data URL (e.g., "data:image/jpeg;base64,..."), | |
| // use it directly. Otherwise, include the MIME type if provided, falling back to PNG. | |
| if (typeof msg.image === 'string' && msg.image.startsWith('data:')) { | |
| img.src = msg.image; | |
| } else { | |
| const mimeType = msg.imageMimeType || 'image/png'; | |
| img.src = `data:${mimeType};base64,${msg.image}`; | |
| } |
| function startChatPolling() { | ||
| if (chatPollTimer) clearInterval(chatPollTimer); | ||
| chatPollTimer = setInterval(fetchChatMessages, 4000); | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chat polling fetches and re-renders the full message list every 4 seconds (setInterval(fetchChatMessages, 4000) + renderChatMessages clears and rebuilds DOM). This will not scale with message volume and will generate unnecessary backend load. Consider incremental fetching (e.g., since/cursor), ETags, or server push (SSE/WebSocket), and update DOM append-only instead of full re-render.
| try { | ||
| const base64Data = await fileToBase64(file); | ||
| const payload = { filename: file.name, base64Data }; | ||
|
|
||
| const res = await fetch(`${javaURI}/api/groups/chat/${activeChatGroupId}/files`, { | ||
| ...fetchOptions, | ||
| method: 'POST', | ||
| headers: { | ||
| ...(fetchOptions.headers || {}), | ||
| 'Content-Type': 'application/json' | ||
| }, | ||
| body: JSON.stringify(payload) | ||
| }); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shared file uploads are base64-encoded and sent in JSON (fileToBase64 + body: JSON.stringify(payload)). For larger media/docs (PDF/DOCX), this increases payload size ~33%, risks hitting request limits, and can freeze the browser due to memory copies. Prefer direct-to-S3 uploads via pre-signed URLs (multipart where needed) and enforce client-side size limits with a user-visible error.
|
There are some recommend copilot fixes on this that sound legit |
|
Rohan:
|
…t and simplify code
Co-authored-by: Nikhil Maturi <code259@users.noreply.github.com>
stored in s3 bucket called pages-groups