From d818c5f05d408d9c12c3f89fe010d3350e91fa66 Mon Sep 17 00:00:00 2001 From: kushal-saha Date: Wed, 29 Apr 2026 15:36:56 +0000 Subject: [PATCH] refactor(core): restructure chat models and services, integrate route-based chat IDs - Updated `Chat` and `ChatMessage` models for new table structures with UUIDs and attributes. - Integrated `ChatPolicy` for authorization checks. - Updated `ChatMessageController` with authorization and refined logic. - Adjusted routes and state in the frontend to handle chat IDs from the URL. - Enhanced `SocialMediaService` for handling chat-specific user prompts. - Removed unused migrations related to chats and chat messages. --- .idea/.gitignore | 2 + .idea/php.xml | 240 ++++++++++-------- backend/.gitignore | 1 + backend/app/Ai/Agents/ContentWriterAgent.php | 28 +- .../app/Ai/Agents/CreativeDirectorAgent.php | 28 +- .../app/Http/Controllers/ChatController.php | 4 +- .../Controllers/ChatMessageController.php | 3 + backend/app/Models/Chat.php | 9 +- backend/app/Models/ChatMessage.php | 9 +- backend/app/Policies/ChatPolicy.php | 47 ++++ backend/app/Services/SocialMediaService.php | 12 +- .../2026_04_28_055031_create_chats_table.php | 30 --- ...4_28_055044_create_chat_messages_table.php | 31 --- frontend/src/app/app.routes.ts | 10 +- frontend/src/app/chat/chat.store.ts | 13 +- frontend/src/app/chat/chat.ts | 16 +- 16 files changed, 238 insertions(+), 245 deletions(-) create mode 100644 backend/app/Policies/ChatPolicy.php delete mode 100644 backend/database/migrations/2026_04_28_055031_create_chats_table.php delete mode 100644 backend/database/migrations/2026_04_28_055044_create_chat_messages_table.php diff --git a/.idea/.gitignore b/.idea/.gitignore index 30cf57e..784d205 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -8,3 +8,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +/laravel-idea-personal.xml +/jsLibraryMappings.xml diff --git a/.idea/php.xml b/.idea/php.xml index 4bfd474..b52ccfc 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -3,6 +3,9 @@ + + @@ -15,149 +18,159 @@ + + + + + - + + + + + + + + + + + + - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + + - + + + + + + + + + + + + + + + + + + + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + + - - - - + + + + + + + - + + - - - - - - - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -166,6 +179,11 @@ + + + + + diff --git a/backend/.gitignore b/backend/.gitignore index 83d894f..f986e84 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -24,3 +24,4 @@ _ide_helper.php Homestead.json Homestead.yaml Thumbs.db +.idea/ diff --git a/backend/app/Ai/Agents/ContentWriterAgent.php b/backend/app/Ai/Agents/ContentWriterAgent.php index 9d2fa76..df166ed 100644 --- a/backend/app/Ai/Agents/ContentWriterAgent.php +++ b/backend/app/Ai/Agents/ContentWriterAgent.php @@ -3,19 +3,17 @@ namespace App\Ai\Agents; use Laravel\Ai\Attributes\Provider; +use Laravel\Ai\Concerns\RemembersConversations; use Laravel\Ai\Contracts\Agent; use Laravel\Ai\Contracts\Conversational; -use Laravel\Ai\Contracts\HasTools; -use Laravel\Ai\Contracts\Tool; use Laravel\Ai\Enums\Lab; -use Laravel\Ai\Messages\Message; use Laravel\Ai\Promptable; use Stringable; #[Provider(Lab::Groq)] -class ContentWriterAgent implements Agent, Conversational, HasTools +class ContentWriterAgent implements Agent, Conversational { - use Promptable; + use Promptable, RemembersConversations; /** * Get the instructions that the agent should follow. @@ -53,24 +51,4 @@ public function instructions(): Stringable|string - Do not include a platform label (e.g. "For Instagram:"). INSTRUCTIONS; } - - /** - * Get the list of messages comprising the conversation so far. - * - * @return Message[] - */ - public function messages(): iterable - { - return []; - } - - /** - * Get the tools available to the agent. - * - * @return Tool[] - */ - public function tools(): iterable - { - return []; - } } diff --git a/backend/app/Ai/Agents/CreativeDirectorAgent.php b/backend/app/Ai/Agents/CreativeDirectorAgent.php index b01241e..2c5425a 100644 --- a/backend/app/Ai/Agents/CreativeDirectorAgent.php +++ b/backend/app/Ai/Agents/CreativeDirectorAgent.php @@ -3,19 +3,17 @@ namespace App\Ai\Agents; use Laravel\Ai\Attributes\Provider; +use Laravel\Ai\Concerns\RemembersConversations; use Laravel\Ai\Contracts\Agent; use Laravel\Ai\Contracts\Conversational; -use Laravel\Ai\Contracts\HasTools; -use Laravel\Ai\Contracts\Tool; use Laravel\Ai\Enums\Lab; -use Laravel\Ai\Messages\Message; use Laravel\Ai\Promptable; use Stringable; #[Provider(Lab::Groq)] -class CreativeDirectorAgent implements Agent, Conversational, HasTools +class CreativeDirectorAgent implements Agent, Conversational { - use Promptable; + use Promptable, RemembersConversations; /** * Get the instructions that the agent should follow. @@ -27,24 +25,4 @@ public function instructions(): Stringable|string 'AI image generator. The scene must be visually compelling and directly '. "relevant to the post's topic. Return ONLY the image prompt — nothing else."; } - - /** - * Get the list of messages comprising the conversation so far. - * - * @return Message[] - */ - public function messages(): iterable - { - return []; - } - - /** - * Get the tools available to the agent. - * - * @return Tool[] - */ - public function tools(): iterable - { - return []; - } } diff --git a/backend/app/Http/Controllers/ChatController.php b/backend/app/Http/Controllers/ChatController.php index 54b0b92..2a031a6 100644 --- a/backend/app/Http/Controllers/ChatController.php +++ b/backend/app/Http/Controllers/ChatController.php @@ -5,7 +5,7 @@ use App\Actions\Chats\CreateChatAction; use App\Http\Requests\CreateChatRequest; use App\Http\Resources\ChatResponseResource; -use Illuminate\Http\Request; +use App\Models\Chat; class ChatController extends Controller { @@ -14,7 +14,7 @@ public function index(Request $request) {} public function store(CreateChatRequest $request, CreateChatAction $createChatAction) { return new ChatResponseResource( - $createChatAction->create($request->user(), $request->input('title', null)) + $createChatAction->create($request->user(), $request->input('title', 'New Chat')) ); } } diff --git a/backend/app/Http/Controllers/ChatMessageController.php b/backend/app/Http/Controllers/ChatMessageController.php index ca664b0..31ea45a 100644 --- a/backend/app/Http/Controllers/ChatMessageController.php +++ b/backend/app/Http/Controllers/ChatMessageController.php @@ -6,9 +6,12 @@ use App\Http\Resources\GeneratedPostResource; use App\Models\Chat; use App\Services\SocialMediaService; +use Illuminate\Http\Request; +use Illuminate\Routing\Attributes\Controllers\Authorize; class ChatMessageController extends Controller { + #[Authorize('update', 'chat')] public function store(GeneratePostRequest $request, Chat $chat, SocialMediaService $socialMediaService) { return new GeneratedPostResource( diff --git a/backend/app/Models/Chat.php b/backend/app/Models/Chat.php index cd344b1..679ab5b 100644 --- a/backend/app/Models/Chat.php +++ b/backend/app/Models/Chat.php @@ -3,20 +3,25 @@ namespace App\Models; use Illuminate\Database\Eloquent\Attributes\Fillable; +use Illuminate\Database\Eloquent\Attributes\Table; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; #[Fillable(['user_id', 'title'])] +#[Table('agent_conversations')] class Chat extends Model { + use HasUuids; + public function user(): BelongsTo { - return $this->belongsTo(User::class); + return $this->belongsTo(User::class, 'user_id'); } public function messages(): HasMany { - return $this->hasMany(ChatMessage::class); + return $this->hasMany(ChatMessage::class, 'conversation_id', 'id')->orderBy('created_at', 'asc'); } } diff --git a/backend/app/Models/ChatMessage.php b/backend/app/Models/ChatMessage.php index 626a239..83651c7 100644 --- a/backend/app/Models/ChatMessage.php +++ b/backend/app/Models/ChatMessage.php @@ -3,14 +3,19 @@ namespace App\Models; use Illuminate\Database\Eloquent\Attributes\Fillable; +use Illuminate\Database\Eloquent\Attributes\Table; +use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -#[Fillable(['chat_id', 'role', 'content', 'user_id'])] +#[Fillable(['conversation_id', 'agent_id', 'role', 'content', 'attachment', 'tool_calls', 'tool_results', 'usage', 'meta', 'content', 'user_id'])] +#[Table('agent_conversation_messages')] class ChatMessage extends Model { + use HasUuids; + public function chat(): BelongsTo { - return $this->belongsTo(Chat::class); + return $this->belongsTo(Chat::class, 'conversation_id', 'id'); } } diff --git a/backend/app/Policies/ChatPolicy.php b/backend/app/Policies/ChatPolicy.php new file mode 100644 index 0000000..6b92286 --- /dev/null +++ b/backend/app/Policies/ChatPolicy.php @@ -0,0 +1,47 @@ +user()->is($user); + } + + public function create(User $user): bool + { + return false; + } + + public function update(User $user, Chat $chat): bool + { + return $chat->user()->is($user); + } + + public function delete(User $user, Chat $chat): bool + { + return false; + } + + public function restore(User $user, Chat $chat): bool + { + return false; + } + + public function forceDelete(User $user, Chat $chat): bool + { + return false; + } +} diff --git a/backend/app/Services/SocialMediaService.php b/backend/app/Services/SocialMediaService.php index 8149fc3..f0c10ee 100644 --- a/backend/app/Services/SocialMediaService.php +++ b/backend/app/Services/SocialMediaService.php @@ -6,9 +6,7 @@ use App\Ai\Agents\ContentWriterAgent; use App\Ai\Agents\CreativeDirectorAgent; use App\Data\SocialMediaPostResponseDto; -use App\Enums\Chats\ChatRoles; use App\Models\Chat; -use App\Models\ChatMessage; readonly class SocialMediaService { @@ -25,17 +23,13 @@ public function __construct( */ public function generatePostWithImage(string $prompt, Chat $chat): SocialMediaPostResponseDto { - $this->chatMessage->store($chat, ChatRoles::USER, $prompt); - - $socialMediaResponse = $this->contentWriterAgent->prompt($prompt); + $socialMediaResponse = $this->contentWriterAgent->forUser($chat->user)->prompt($prompt); $postText = $socialMediaResponse->text; - /* @var ChatMessage $aiChat */ - $aiChat = $this->chatMessage->store($chat, ChatRoles::AI, $postText); - + // Generate image prompt via creative director agent $imagePromptResponse = $this->creativeDirectorAgent->prompt($postText); $imagePrompt = $imagePromptResponse->text; - return new SocialMediaPostResponseDto($aiChat->id, $postText, $imagePrompt, now()); + return new SocialMediaPostResponseDto($socialMediaResponse->conversationId, $postText, $imagePrompt, now()); } } diff --git a/backend/database/migrations/2026_04_28_055031_create_chats_table.php b/backend/database/migrations/2026_04_28_055031_create_chats_table.php deleted file mode 100644 index 134b217..0000000 --- a/backend/database/migrations/2026_04_28_055031_create_chats_table.php +++ /dev/null @@ -1,30 +0,0 @@ -id(); - $table->foreignIdFor(User::class, 'user_id')->constrained()->cascadeOnDelete(); - $table->string('title')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('chats'); - } -}; diff --git a/backend/database/migrations/2026_04_28_055044_create_chat_messages_table.php b/backend/database/migrations/2026_04_28_055044_create_chat_messages_table.php deleted file mode 100644 index 90ed1ea..0000000 --- a/backend/database/migrations/2026_04_28_055044_create_chat_messages_table.php +++ /dev/null @@ -1,31 +0,0 @@ -id(); - $table->foreignIdFor(Chat::class, 'chat_id')->constrained()->cascadeOnDelete(); - $table->string('role'); - $table->text('content'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('chat_messages'); - } -}; diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 4c598f2..62e28a3 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -3,10 +3,14 @@ import { Routes } from '@angular/router'; export const routes: Routes = [ { path: '', - loadComponent: () => import('./chat/chat').then(m => m.Chat) + loadComponent: () => import('./chat/chat').then((m) => m.Chat), + }, + { + path: ':id', + loadComponent: () => import('./chat/chat').then((m) => m.Chat), }, { path: 'user', - loadChildren: () => import('./auth/auth.routes').then(m => m.authRoutes) - } + loadChildren: () => import('./auth/auth.routes').then((m) => m.authRoutes), + }, ]; diff --git a/frontend/src/app/chat/chat.store.ts b/frontend/src/app/chat/chat.store.ts index d9129fb..d47020c 100644 --- a/frontend/src/app/chat/chat.store.ts +++ b/frontend/src/app/chat/chat.store.ts @@ -4,6 +4,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { ChatService } from './chat-service'; import { ChatState, Message } from './chat.types'; import { lastValueFrom } from 'rxjs'; +import { Router } from '@angular/router'; const initialState: ChatState = { messages: [ @@ -23,12 +24,16 @@ export const ChatStore = signalStore( withState(initialState), withMethods((store) => { const chatService = inject(ChatService); + const router = inject(Router); const newChat = async () => { try { const response = await lastValueFrom(chatService.newChat()); - patchState(store, { id: response.data.id }); - return response.data.id; + const chatId = response.data.id; + + patchState(store, { id: chatId }); + await router.navigate(['/', chatId], { replaceUrl: true }); + return chatId; } catch (error) { console.error('Failed to create a new chat:', error); throw error; @@ -37,7 +42,9 @@ export const ChatStore = signalStore( return { newChat, - + setChatId: (chatId: string) => { + patchState(store, { id: chatId }); + }, sendMessage: async (content: string) => { if (!store.id()) { try { diff --git a/frontend/src/app/chat/chat.ts b/frontend/src/app/chat/chat.ts index c917dfb..34fa180 100644 --- a/frontend/src/app/chat/chat.ts +++ b/frontend/src/app/chat/chat.ts @@ -2,16 +2,18 @@ import { Component, ElementRef, ViewChild, effect, inject } from '@angular/core' import { CommonModule } from '@angular/common'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { ChatStore } from './chat.store'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-chat', standalone: true, imports: [CommonModule, ReactiveFormsModule], templateUrl: './chat.html', - styleUrl: './chat.css' + styleUrl: './chat.css', }) export class Chat { readonly chatStore = inject(ChatStore); + private route = inject(ActivatedRoute); @ViewChild('scrollContainer') private scrollContainer!: ElementRef; @@ -30,6 +32,16 @@ export class Chat { }); } + ngOnInit() { + this.route.paramMap.subscribe((params) => { + const urlId = params.get('id'); + if (urlId) { + // If an ID exists in the URL, populate it in the store + this.chatStore.setChatId(urlId); + } + }); + } + errorMessage = ''; sendMessage() { @@ -50,6 +62,6 @@ export class Chat { try { const el = this.scrollContainer.nativeElement; el.scrollTop = el.scrollHeight; - } catch(err) { } + } catch (err) {} } }