Skip to content

Commit b8bfc26

Browse files
committed
Enforces constellation selection and refines portal chat validation
Updates the portal UI to require a constellation selection before submitting messages, providing visual feedback for invalid states. It also adds server-side validation for the constellation ID and removes an unused Markdown parser from the portal service.
1 parent af25e16 commit b8bfc26

3 files changed

Lines changed: 61 additions & 29 deletions

File tree

src/main/java/dev/ikm/server/cosmos/portal/PortalController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ public String postChatMessage(@PathVariable("sessionId") UUID sessionId,
6464
boolean hasMessage = message != null && !message.isBlank();
6565
boolean hasFile = file != null && !file.isEmpty();
6666

67+
6768
// The client-side script should prevent empty submissions, but we validate
6869
// again.
69-
if (!hasMessage && !hasFile) {
70+
if ((!hasMessage && !hasFile) || constellationId == null) {
7071
// If a blank message gets through, return the indicator to be swapped with
7172
// itself,
7273
// resulting in no visual change for the user.

src/main/java/dev/ikm/server/cosmos/portal/PortalService.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,19 @@
1010
import org.springframework.web.multipart.MultipartFile;
1111

1212
import dev.ikm.server.cosmos.constellation.ConstellationEntity;
13-
import dev.langchain4j.data.document.parser.markdown.MarkdownDocumentParser;
1413

1514
@Service
1615
public class PortalService {
1716

1817
private final CosmosAgent cosmosAgent;
1918
private final ConcurrentMap<UUID, ConstellationEntity> constellationDB;
2019

21-
private final MarkdownDocumentParser markdownDocumentParser;
2220
private final Parser markdownParser;
2321
private final HtmlRenderer htmlRenderer;
2422

2523
public PortalService(ConcurrentMap<UUID, ConstellationEntity> constellationDB, CosmosAgent cosmosAgent) {
2624
this.constellationDB = constellationDB;
2725
this.cosmosAgent = cosmosAgent;
28-
this.markdownDocumentParser = new MarkdownDocumentParser();
2926
this.markdownParser = Parser.builder().build();
3027
this.htmlRenderer = HtmlRenderer.builder().build();
3128
}

src/main/resources/templates/portal.html

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
const filePreviewArea = document.getElementById('file-preview-area');
143143
const filePreviewName = document.getElementById('file-preview-name');
144144
const clearFileButton = document.getElementById('clear-file-button');
145+
const constellationSelect = document.getElementById('constellationId');
145146

146147
function scrollChatToBottom() {
147148
// A small delay ensures the new content is rendered before scrolling
@@ -158,6 +159,41 @@
158159
});
159160
}
160161

162+
function validateChatSubmission() {
163+
const messageText = chatInput.value.trim();
164+
const file = fileInput.files[0];
165+
let isValid = true;
166+
167+
if (!constellationSelect.value) {
168+
constellationSelect.classList.add('is-invalid');
169+
isValid = false;
170+
} else {
171+
constellationSelect.classList.remove('is-invalid');
172+
}
173+
174+
if (!messageText && !file) {
175+
isValid = false;
176+
}
177+
178+
return isValid;
179+
}
180+
181+
// Prevent submission early if invalid (stops HTMX indicator flash)
182+
chatForm.addEventListener('submit', function(e) {
183+
if (!validateChatSubmission()) {
184+
e.preventDefault();
185+
e.stopPropagation();
186+
e.stopImmediatePropagation();
187+
}
188+
});
189+
190+
// Remove invalid styling when the user selects a constellation
191+
constellationSelect.addEventListener('change', function() {
192+
if (this.value) {
193+
this.classList.remove('is-invalid');
194+
}
195+
});
196+
161197
// Handle file selection to show the preview
162198
fileInput.addEventListener('change', function() {
163199
if (fileInput.files.length > 0) {
@@ -180,35 +216,31 @@
180216
const messageText = chatInput.value.trim();
181217
const file = fileInput.files[0];
182218

183-
if (messageText || file) {
184-
const indicator = document.getElementById('chat-indicator');
185-
let userMessageContent = '';
219+
const indicator = document.getElementById('chat-indicator');
220+
let userMessageContent = '';
186221

187-
if (file) {
188-
userMessageContent += `<p class="mb-1"><i class="bi bi-file-earmark-arrow-up me-1"></i><em>${escapeHTML(file.name)}</em></p>`;
189-
}
190-
if (messageText) {
191-
userMessageContent += `<p class="mb-0">${escapeHTML(messageText)}</p>`;
192-
}
222+
if (file) {
223+
userMessageContent += `<p class="mb-1"><i class="bi bi-file-earmark-arrow-up me-1"></i><em>${escapeHTML(file.name)}</em></p>`;
224+
}
225+
if (messageText) {
226+
userMessageContent += `<p class="mb-0">${escapeHTML(messageText)}</p>`;
227+
}
193228

194-
const userMessageHTML = `
195-
<div class="d-flex mb-3 message-user">
196-
<div class="p-3 message-content">
197-
${userMessageContent}
198-
</div>
229+
const userMessageHTML = `
230+
<div class="d-flex mb-3 message-user">
231+
<div class="p-3 message-content">
232+
${userMessageContent}
199233
</div>
200-
`;
234+
</div>
235+
`;
201236

202-
// Insert the user's message right before the indicator
203-
indicator?.insertAdjacentHTML('beforebegin', userMessageHTML);
237+
// Insert the user's message right before the indicator
238+
indicator?.insertAdjacentHTML('beforebegin', userMessageHTML);
204239

205-
scrollChatToBottom();
206-
chatInput.value = ''; // Clear the input field immediately
207-
fileInput.value = ''; // Clear the file input
208-
filePreviewArea.classList.add('d-none'); // Hide the preview
209-
} else {
210-
evt.preventDefault(); // Prevent sending an empty message
211-
}
240+
scrollChatToBottom();
241+
chatInput.value = ''; // Clear the input field immediately
242+
fileInput.value = ''; // Clear the file input
243+
filePreviewArea.classList.add('d-none'); // Hide the preview
212244
});
213245

214246
// After the bot response is swapped in, scroll and focus
@@ -224,7 +256,9 @@
224256
chatInput.addEventListener('keydown', function(e) {
225257
if (e.key === 'Enter' && !e.shiftKey) {
226258
e.preventDefault();
227-
htmx.trigger('#chat-form', 'submit');
259+
if (validateChatSubmission()) {
260+
htmx.trigger('#chat-form', 'submit');
261+
}
228262
}
229263
});
230264

0 commit comments

Comments
 (0)