diff --git a/backend/routes/api.php b/backend/routes/api.php
index 1ebca80..9aacac7 100644
--- a/backend/routes/api.php
+++ b/backend/routes/api.php
@@ -5,9 +5,9 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
-Route::get("/me", function (Request $request) {
+Route::get('/me', function (Request $request) {
return $request->user();
-})->middleware("auth:sanctum");
+})->middleware('auth:sanctum');
-Route::apiResource("chats", ChatController::class);
-Route::apiResource("chats.messages", ChatMessageController::class);
+Route::apiResource('chats', ChatController::class);
+Route::apiResource('chats.messages', ChatMessageController::class);
diff --git a/frontend/src/app/chat/chat.html b/frontend/src/app/chat/chat.html
index 78f84b6..d4a3bb6 100644
--- a/frontend/src/app/chat/chat.html
+++ b/frontend/src/app/chat/chat.html
@@ -4,7 +4,11 @@
class="flex-1 overflow-y-auto p-8 flex flex-col gap-6 scroll-smooth custom-scrollbar"
#scrollContainer
>
- @for (msg of messageStore.messages(); track msg.id) {
+ @if(messageStore.isLoading()) {
+
@@ -33,7 +39,7 @@
class="w-2 h-2 bg-[#4facfe] rounded-full animate-[typing_1.4s_infinite_ease-in-out_both] delay-0"
>
- }
+ } }
diff --git a/frontend/src/app/chat/chat.store.ts b/frontend/src/app/chat/chat.store.ts
index f263325..9202b64 100644
--- a/frontend/src/app/chat/chat.store.ts
+++ b/frontend/src/app/chat/chat.store.ts
@@ -20,7 +20,8 @@ const initialMessageState: MessageState = {
},
},
],
- isLoading: false,
+ isLoading: true,
+ isAgentThinking: false,
id: null,
};
@@ -37,14 +38,16 @@ export const MessageStore = signalStore(
const router = inject(Router);
const newChat = async () => {
+ patchState(store, { isLoading: true });
try {
const response = await lastValueFrom(chatService.newChat());
const chatId = response.data.id;
- patchState(store, { id: chatId });
+ patchState(store, { id: chatId, isLoading: false });
await router.navigate(['/', chatId], { replaceUrl: true });
return chatId;
} catch (error) {
+ patchState(store, { isLoading: false });
console.error('Failed to create a new chat:', error);
throw error;
}
@@ -98,7 +101,7 @@ export const MessageStore = signalStore(
patchState(store, (state) => ({
messages: [...state.messages, userMessage],
- isLoading: true,
+ isAgentThinking: true,
}));
try {
const aiMessage: MessageResponse = await lastValueFrom(
@@ -107,7 +110,7 @@ export const MessageStore = signalStore(
patchState(store, (state) => ({
messages: [...state.messages, aiMessage.data],
- isLoading: false,
+ isAgentThinking: false,
}));
} catch (error: any) {
let errorText = 'Sorry, I encountered an error while communicating with the server.';
@@ -125,20 +128,33 @@ export const MessageStore = signalStore(
}
setErrorMessage(errorText, 'assistant');
patchState(store, () => ({
- isLoading: false,
+ isAgentThinking: false,
}));
}
},
- fetchChatHistory: async () => {
- if (!store.id()) {
+ fetchChatHistory: async (chatId: string | null = null) => {
+ if (!store.id() && !chatId) {
setErrorMessage('Please create a new chat session first.', 'assistant');
return;
}
+
+ if (chatId) {
+ patchState(store, { id: chatId });
+ }
+
patchState(store, { isLoading: true });
try {
const chatHistory = await lastValueFrom(chatService.getMessages(
store.id()));
- patchState(store, { messages: chatHistory.data, isLoading: false });
+ if (chatHistory.data.length !== 0) {
+ patchState(store, { messages: chatHistory.data });
+ } else {
+ patchState(store, { messages: initialMessageState.messages });
+ }
+ patchState(store, { isLoading: false });
+ if (chatId) {
+ await router.navigate(['/', chatId], { replaceUrl: true });
+ }
} catch (error: any) {
let errorText = 'Failed to load chat history. Please try again.';
diff --git a/frontend/src/app/chat/chat.ts b/frontend/src/app/chat/chat.ts
index e8b8d2e..4db7c5b 100644
--- a/frontend/src/app/chat/chat.ts
+++ b/frontend/src/app/chat/chat.ts
@@ -4,11 +4,12 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ChatStore, MessageStore } from './chat.store';
import { ActivatedRoute } from '@angular/router';
import { Sidebar } from "../core/layout/sidebar/sidebar";
+import { Loader } from "../shared/loader/loader";
@Component({
selector: 'app-chat',
standalone: true,
- imports: [CommonModule, ReactiveFormsModule, Sidebar],
+ imports: [CommonModule, ReactiveFormsModule, Sidebar, Loader],
templateUrl: './chat.html',
styleUrl: './chat.css',
})
diff --git a/frontend/src/app/chat/chat.types.ts b/frontend/src/app/chat/chat.types.ts
index 952e8f3..72492af 100644
--- a/frontend/src/app/chat/chat.types.ts
+++ b/frontend/src/app/chat/chat.types.ts
@@ -23,6 +23,7 @@ export type MessageCollection = JsonApiCollection;
export type MessageState = {
messages: JsonApiResource[];
isLoading: boolean;
+ isAgentThinking: boolean;
id: string | null;
};
export type ChatState = {
diff --git a/frontend/src/app/core/layout/sidebar/sidebar.ts b/frontend/src/app/core/layout/sidebar/sidebar.ts
index f3b7260..08a5d74 100644
--- a/frontend/src/app/core/layout/sidebar/sidebar.ts
+++ b/frontend/src/app/core/layout/sidebar/sidebar.ts
@@ -1,11 +1,12 @@
import { Component, signal, computed, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
-import { ChatStore } from '../../../chat/chat.store';
+import { ChatStore, MessageStore } from '../../../chat/chat.store';
import { AuthStore } from '../../../auth/auth.store';
import { InitialsPipe } from './initials-pipe';
import { JsonApiResource } from '../../types/api';
import { Chat } from '../../../chat/chat.types';
+import { Login } from '../../../auth/login/login';
@Component({
selector: 'app-sidebar',
@@ -16,6 +17,7 @@ import { Chat } from '../../../chat/chat.types';
})
export class Sidebar implements OnInit {
protected chatStore = inject(ChatStore);
+ protected messageStore = inject(MessageStore);
protected authStore = inject(AuthStore);
protected isOpen = signal(true);
@@ -59,11 +61,13 @@ export class Sidebar implements OnInit {
}
protected newChat(): void {
- console.log('New chat triggered');
+ this.messageStore.newChat();
}
protected selectChat(chatId: string): void {
this.activeChatId.set(chatId);
+ console.log(chatId);
+ this.messageStore.fetchChatHistory(chatId);
}
protected onSearch(event: Event): void {
diff --git a/frontend/src/app/shared/loader/loader.spec.ts b/frontend/src/app/shared/loader/loader.spec.ts
new file mode 100644
index 0000000..9152c9f
--- /dev/null
+++ b/frontend/src/app/shared/loader/loader.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Loader } from './loader';
+
+describe('Loader', () => {
+ let component: Loader;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [Loader],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(Loader);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/shared/loader/loader.ts b/frontend/src/app/shared/loader/loader.ts
new file mode 100644
index 0000000..2d8c074
--- /dev/null
+++ b/frontend/src/app/shared/loader/loader.ts
@@ -0,0 +1,29 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-loader',
+ imports: [],
+ template: ``,
+ styles: `
+ #loader {
+ max-width: 40px;
+
+ aspect-ratio: 1;
+
+ border-radius: 50%;
+
+ border: 4px solid lightblue;
+
+ border-right-color: var(--color-blue-500);
+
+ animation: spinner 1s infinite linear;
+ }
+
+ @keyframes spinner {
+ to {
+ transform: rotate(1turn);
+ }
+ }
+ `,
+})
+export class Loader {}