Skip to content
34 changes: 16 additions & 18 deletions app/Http/Controllers/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,32 +116,30 @@ public function stream(Request $request, ?Chat $chat = null)
}

return response()->stream(function () use ($request, $chat) {
$messages = $request->input('messages', []);
$messages = collect($request->input('messages', []));
$prompt = $request->string('prompt')->trim()->value();
$autoStream = $request->input('autoStream', false);

if (empty($messages)) {
if ($messages->isEmpty() && empty($prompt) && ! $autoStream) {
return;
}

// Only save messages if we have an existing chat (authenticated user with saved chat)
if ($chat) {
foreach ($messages as $message) {
// Only save if message doesn't have an ID (not from database)
if (! isset($message['id'])) {
$chat->messages()->create([
'type' => $message['type'],
'content' => $message['content'],
]);
}
// Only save/load messages if we have an existing chat (authenticated user with saved chat)
if ($chat && Auth::check()) {
if ($prompt && ! $autoStream) {
$chat->messages()->create([
'type' => 'prompt',
'content' => $prompt,
]);
}
$messages = $chat->messages()->orderBy('created_at')->get();
}

// Prepare messages for OpenAI
$openAIMessages = collect($messages)
->map(fn ($message) => [
'role' => $message['type'] === 'prompt' ? 'user' : 'assistant',
'content' => $message['content'],
])
->toArray();
$openAIMessages = $messages->map(fn ($message) => [
'role' => $message['type'] === 'prompt' ? 'user' : 'assistant',
'content' => $message['content'],
])->toArray();

// Stream response from OpenAI
$fullResponse = '';
Expand Down
13 changes: 10 additions & 3 deletions resources/js/pages/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ function ChatWithStream({ chat, auth, flash }: { chat: ChatType | undefined; aut

if (shouldAutoStream) {
setTimeout(() => {
send({ messages: chat.messages });
const first = chat.messages[0];
if (first.type === 'prompt') {
send({ autoStream: 1 });
}
}, 100);
}
}, [chat?.messages, flash?.stream, send]); // Only run on mount
Expand Down Expand Up @@ -126,8 +129,12 @@ function ChatWithStream({ chat, auth, flash }: { chat: ChatType | undefined; aut
// Update local state
setMessages((prev) => [...prev, ...toAdd]);

// Send all messages including the new ones
send({ messages: [...messages, ...toAdd] });
// Send message data based on chat/auth status
if (chat && auth.user) {
send({ prompt: query });
} else {
send({ messages: [...messages, ...toAdd] });
}

input.value = '';
inputRef.current?.focus();
Expand Down
4 changes: 1 addition & 3 deletions tests/Feature/ChatFlowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ public function test_message_from_home_page_creates_chat_and_sends_message(): vo

// Now send the message to the new chat
$response = $this->post("/chat/{$chat->id}/stream", [
'messages' => [
['type' => 'prompt', 'content' => 'Hello from home page'],
],
'prompt' => 'Hello from home page',
]);

$response->assertStatus(200);
Expand Down
41 changes: 1 addition & 40 deletions tests/Feature/ChatStreamingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ public function test_authenticated_user_streams_to_existing_chat(): void
$chat = Chat::factory()->create(['user_id' => $user->id]);

$response = $this->actingAs($user)->post("/chat/{$chat->id}/stream", [
'messages' => [
['type' => 'prompt', 'content' => 'Hello AI'],
],
'prompt' => 'Hello AI',
]);

$response->assertStatus(200);
Expand Down Expand Up @@ -69,43 +67,6 @@ public function test_empty_messages_returns_early(): void
$this->assertDatabaseCount('messages', 0);
}

public function test_messages_marked_as_saved_are_not_duplicated(): void
{
$user = User::factory()->create();
$chat = Chat::factory()->create(['user_id' => $user->id]);

// Create existing message
$existingMessage = Message::factory()->create([
'chat_id' => $chat->id,
'type' => 'prompt',
'content' => 'Already saved',
]);

$response = $this->actingAs($user)->post("/chat/{$chat->id}/stream", [
'messages' => [
['id' => $existingMessage->id, 'type' => 'prompt', 'content' => 'Already saved'],
['type' => 'prompt', 'content' => 'New message'],
],
]);

$response->assertStatus(200);

// Get the streaming content
$response->streamedContent();

// Verify only the new message was saved (1 existing + 1 new prompt + 1 AI response)
$this->assertDatabaseCount('messages', 3);
$this->assertDatabaseHas('messages', [
'chat_id' => $chat->id,
'content' => 'New message',
]);
$this->assertDatabaseHas('messages', [
'chat_id' => $chat->id,
'type' => 'response',
'content' => 'This is a test response.',
]);
}

public function test_chat_not_created_for_anonymous_users(): void
{
$response = $this->post('/chat/stream', [
Expand Down
Loading