Skip to content

Commit 4294d23

Browse files
author
Yuriy Bezsonov
committed
refactor UI components and add Spring AI agents app
1 parent 652a9b0 commit 4294d23

41 files changed

Lines changed: 4161 additions & 373 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.class
2+
*.jar
3+
*.war
4+
target/
5+
build/
6+
.idea/
7+
*.iml
8+
.settings/
9+
.classpath
10+
.project

apps/aiagent-agentcore/aiagent/src/main/resources/static/agent-icon.svg

Lines changed: 0 additions & 1 deletion
This file was deleted.

apps/aiagent-agentcore/aiagent/src/main/resources/static/app.js

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,56 @@ document.addEventListener('DOMContentLoaded', async function() {
1717

1818
const config = await loadConfig();
1919

20+
// Auto-resize textarea
21+
userInput.addEventListener('input', function() {
22+
this.style.height = 'auto';
23+
this.style.height = this.scrollHeight + 'px';
24+
});
25+
26+
// Submit on Enter, newline on Shift+Enter
27+
userInput.addEventListener('keydown', function(e) {
28+
if (e.key === 'Enter' && !e.shiftKey) {
29+
e.preventDefault();
30+
chatForm.requestSubmit();
31+
}
32+
});
33+
2034
if (uploadBtn) {
2135
if (isAttachmentsEnabled(config)) {
2236
uploadBtn.classList.remove('hidden');
2337
} else {
2438
uploadBtn.classList.add('hidden');
25-
userInput.classList.add('rounded-l-lg');
2639
}
2740
}
2841

42+
// Initialize model selector
43+
const modelSelect = document.getElementById('modelSelect');
44+
const models = getModels(config);
45+
if (modelSelect && models.length > 0) {
46+
modelSelect.classList.remove('hidden');
47+
models.forEach(m => {
48+
const opt = document.createElement('option');
49+
opt.value = m.id;
50+
opt.textContent = m.name;
51+
if (m.default) opt.selected = true;
52+
modelSelect.appendChild(opt);
53+
});
54+
const saved = getSelectedModel();
55+
if (saved && models.some(m => m.id === saved)) {
56+
modelSelect.value = saved;
57+
} else {
58+
const def = models.find(m => m.default) || models[0];
59+
setSelectedModel(def.id);
60+
}
61+
modelSelect.addEventListener('change', () => setSelectedModel(modelSelect.value));
62+
} else if (modelSelect) {
63+
modelSelect.classList.add('hidden');
64+
}
65+
2966
const loginMessage = document.getElementById('loginMessage');
3067
if (loginMessage) {
3168
if (config.authType === 'simple') {
32-
loginMessage.textContent = 'Local Mode - Enter any username to continue';
69+
loginMessage.textContent = 'Local Mode Enter any username to continue';
3370
} else {
3471
loginMessage.textContent = 'Sign in with your Cognito credentials';
3572
}
@@ -43,10 +80,8 @@ document.addEventListener('DOMContentLoaded', async function() {
4380

4481
loginForm.addEventListener('submit', async function(e) {
4582
e.preventDefault();
46-
4783
const username = document.getElementById('username').value;
4884
const password = document.getElementById('password').value;
49-
5085
loginError.classList.add('hidden');
5186

5287
try {
@@ -60,7 +95,6 @@ document.addEventListener('DOMContentLoaded', async function() {
6095

6196
chatForm.addEventListener('submit', async function(e) {
6297
e.preventDefault();
63-
6498
const message = userInput.value.trim();
6599
if (!message) return;
66100

@@ -69,16 +103,19 @@ document.addEventListener('DOMContentLoaded', async function() {
69103
return;
70104
}
71105

72-
addMessage(message, 'user');
106+
const userMsg = addMessage(message, 'user');
107+
requestAnimationFrame(() => {
108+
const headerH = document.querySelector('.chat-header').offsetHeight;
109+
const msgTop = userMsg.getBoundingClientRect().top + chatScreen.scrollTop - chatScreen.getBoundingClientRect().top;
110+
chatScreen.scrollTo({ top: msgTop - headerH - 24, behavior: 'smooth' });
111+
});
73112
userInput.value = '';
113+
userInput.style.height = 'auto';
74114

75115
const auth = loadAuth();
76-
if (!auth || Date.now() >= (auth.expiresAt - 300000)) {
116+
if (isSessionExpired(auth)) {
77117
addMessage('Your session has expired. Please log in again.', 'ai', { isError: true });
78-
setTimeout(() => {
79-
clearAuth();
80-
showLoginScreen();
81-
}, 2000);
118+
setTimeout(() => { clearAuth(); showLoginScreen(); }, 2000);
82119
return;
83120
}
84121

@@ -91,16 +128,14 @@ document.addEventListener('DOMContentLoaded', async function() {
91128
} catch (error) {
92129
removeLoading(loadingId);
93130
const retryFn = async () => {
94-
const errorMsg = messageContainer.lastElementChild;
131+
const container = document.getElementById('messageContainer');
132+
const errorMsg = container.lastElementChild;
95133
if (errorMsg) errorMsg.remove();
96134

97135
const freshAuth = loadAuth();
98-
if (!freshAuth || Date.now() >= (freshAuth.expiresAt - 300000)) {
136+
if (isSessionExpired(freshAuth)) {
99137
addMessage('Your session has expired. Please log in again.', 'ai', { isError: true });
100-
setTimeout(() => {
101-
clearAuth();
102-
showLoginScreen();
103-
}, 2000);
138+
setTimeout(() => { clearAuth(); showLoginScreen(); }, 2000);
104139
return;
105140
}
106141

@@ -122,15 +157,18 @@ document.addEventListener('DOMContentLoaded', async function() {
122157

123158
const themeIcon = document.getElementById('themeIcon');
124159
themeToggle.addEventListener('click', function() {
125-
const html = document.documentElement;
126-
html.classList.toggle('dark');
127-
themeIcon.textContent = html.classList.contains('dark') ? '☀️' : '🌙';
128-
localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');
160+
document.documentElement.classList.toggle('light');
161+
const isLight = document.documentElement.classList.contains('light');
162+
themeIcon.textContent = isLight ? '🌙' : '☀️';
163+
localStorage.setItem('theme', isLight ? 'light' : 'dark');
129164
});
130165

131166
if (localStorage.getItem('theme') === 'dark') {
132-
document.documentElement.classList.add('dark');
167+
document.documentElement.classList.remove('light');
133168
themeIcon.textContent = '☀️';
169+
} else {
170+
document.documentElement.classList.add('light');
171+
themeIcon.textContent = '🌙';
134172
}
135173

136174
logoutBtn.addEventListener('click', function() {
@@ -158,7 +196,7 @@ document.addEventListener('DOMContentLoaded', async function() {
158196
function showChatScreen() {
159197
loginScreen.classList.add('hidden');
160198
chatScreen.classList.remove('hidden');
161-
199+
chatScreen.scrollTop = 0;
162200
const auth = loadAuth();
163201
if (auth) {
164202
userDisplay.textContent = auth.username;

apps/aiagent-agentcore/aiagent/src/main/resources/static/auth.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ function clearAuth() {
2424

2525
function isAuthenticated() {
2626
const auth = loadAuth();
27-
if (!auth || !auth.accessToken || !auth.expiresAt) {
28-
return false;
29-
}
30-
// 5 minute buffer before expiry
31-
return Date.now() < (auth.expiresAt - 300000);
27+
if (!auth || !auth.expiresAt) return false;
28+
if (auth.authType !== 'simple' && !auth.accessToken) return false;
29+
return !isSessionExpired(auth);
30+
}
31+
32+
function isSessionExpired(auth) {
33+
return !auth || Date.now() >= (auth.expiresAt - 300000);
3234
}
3335

3436
async function authenticateUser(username, password, config) {
@@ -45,9 +47,7 @@ async function authenticateSimple(username, password) {
4547

4648
const auth = {
4749
username: username,
48-
accessToken: 'local-dev-token',
49-
idToken: 'local-dev-token',
50-
expiresAt: Date.now() + (24 * 60 * 60 * 1000), // 24 hours
50+
expiresAt: Date.now() + (24 * 60 * 60 * 1000),
5151
authType: 'simple'
5252
};
5353

@@ -89,10 +89,15 @@ async function authenticateCognito(username, password, config) {
8989
const accessToken = result.getAccessToken().getJwtToken();
9090
const idToken = result.getIdToken().getJwtToken();
9191
const expiresAt = Date.now() + (result.getAccessToken().getExpiration() * 1000);
92-
const payload = JSON.parse(atob(accessToken.split('.')[1]));
92+
93+
let parsedUsername = username;
94+
try {
95+
const payload = JSON.parse(atob(accessToken.split('.')[1]));
96+
parsedUsername = payload.username || username;
97+
} catch (e) {}
9398

9499
const auth = {
95-
username: payload.username || username,
100+
username: parsedUsername,
96101
accessToken: accessToken,
97102
idToken: idToken,
98103
expiresAt: expiresAt,

0 commit comments

Comments
 (0)