Larachat is a technical demonstration of real-time chat capabilities. It provides developers with a blueprint for building interfaces that feature streaming AI responses, persistent message history, and user authentication. Essentially a minimalist ChatGPT clone, Larachat focuses on being clean, functional, and entirely open-source.
What Larachat Does
useEventStream hook.System Requirements
Optional but Recommended
Framework Versions Used
This stack utilizes cutting-edge releases. If you encounter issues during setup, verify that your local environment matches these specific versions.
Quick Start
Begin by cloning the repository and installing the necessary dependencies.
composer install
npm install
Configure your environment settings.
cp .env.example .env
php artisan key:generate
Add your OpenAI API key to the .env file.
OPENAI_API_KEY=your-api-key-here
Run the database migrations and launch the development environment.
php artisan migrate
composer dev
The composer dev command runs several processes concurrently. If you prefer to manage them individually, you can open separate terminal windows:
php artisan servephp artisan queue:listennpm run devCommon Fixes
nvm: nvm install 22 && nvm use 22.composer install again and ensure your .env key is correctly formatted.database/database.sqlite. You can create it manually with touch database/database.sqlite before running migrations.npm cache clean --force), deleting the node_modules folder, and reinstalling (rm -rf node_modules && npm install). Ensure you are using Node 22+.Using the useStream Hook
Streaming responses are managed on the frontend by the @laravel/stream-react package via the useStream hook. This allows for straightforward integration of real-time data into React components.
import { useStream } from '@laravel/stream-react';
import { useState } from 'react';
function Chat() {
const [messages, setMessages] = useState([]);
const { data, send, isStreaming } = useStream('/chat/stream');
const handleSubmit = (e) => {
e.preventDefault();
const query = e.target.query.value;
const newMessage = { type: 'prompt', content: query };
setMessages([...messages, newMessage]);
send({ messages: [...messages, newMessage] });
e.target.reset();
};
return (
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.content}</div>
))}
{data && <div>{data}</div>}
<form onSubmit={handleSubmit}>
<input name="query" disabled={isStreaming} />
<button type="submit">Send</button>
</form>
</div>
);
}
The hook establishes a connection to a Laravel endpoint. The send function transmits JSON data, isStreaming provides a boolean for loading states, and data accumulates the incoming stream.
The Backend Stream Endpoint
On the server side, the controller manages the stream by flushing the output buffer as chunks are received from the OpenAI API.
public function stream(Request $request)
{
return response()->stream(function () use ($request) {
$messages = $request->input('messages', []);
$stream = OpenAI::chat()->createStreamed([
'model' => 'gpt-4',
'messages' => $messages,
]);
foreach ($stream as $response) {
$chunk = $response->choices[0]->delta->content;
if ($chunk !== null) {
echo $chunk;
ob_flush();
flush();
}
}
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no',
]);
}
Using the useEventStream Hook
In this demo, useEventStream handles real-time title generation. When a new chat begins, it is initially marked as "Untitled." The system then prompts OpenAI for a concise title and streams it back to update the UI.
import { useEventStream } from '@laravel/stream-react';
function TitleGenerator({ chatId, onTitleUpdate, onComplete }) {
const { message } = useEventStream(`/chat/${chatId}/title-stream`, {
eventName: "title-update",
endSignal: "</stream>",
onMessage: (event) => {
try {
const parsed = JSON.parse(event.data);
if (parsed.title) {
onTitleUpdate(parsed.title);
}
} catch (error) {
console.error('Error parsing title:', error);
}
},
onComplete: () => {
onComplete();
},
onError: (error) => {
console.error('EventStream error:', error);
onComplete();
},
});
return null;
}
Note the use of eventName to filter specific events. Multiple components can subscribe to this same stream; for instance, one component can update the main header while another refreshes the navigation sidebar.
Backend EventStream for Titles
use Illuminate\Http\StreamedEvent;
public function titleStream(Chat $chat)
{
$this->authorize('view', $chat);
return response()->eventStream(function () use ($chat) {
if ($chat->title && $chat->title !== 'Untitled') {
yield new StreamedEvent(
event: 'title-update',
data: json_encode(['title' => $chat->title])
);
return;
}
$firstMessage = $chat->messages()->where('type', 'prompt')->first();
$response = OpenAI::chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => 'Generate a short, descriptive title (max 50 chars) for a chat that starts with this message. Return only the title, no quotes or extra formatting.'
],
['role' => 'user', 'content' => $firstMessage->content]
],
'max_tokens' => 20,
'temperature' => 0.7,
]);
$title = trim($response->choices[0]->message->content);
$chat->update(['title' => $title]);
yield new StreamedEvent(
event: 'title-update',
data: json_encode(['title' => $title])
);
}, endStreamWith: new StreamedEvent(event: 'title-update', data: '</stream>'));
}
How It Fits Together
When a user sends their first message, the AI's reply streams back through useStream. Once that process finishes, the title generator triggers. The server requests a title from OpenAI, and the EventStream pushes the new title to the UI. The sidebar and header update instantly without requiring a page reload.
Advanced Features in the Demo
Handling CSRF Protection in Streaming Endpoints
While Inertia forms manage CSRF tokens automatically, streaming endpoints operate outside of Inertia's standard request lifecycle. Because useStream performs a direct POST to an API endpoint, you must handle the CSRF token manually.
Ensure the CSRF meta tag is in your root layout:
<meta name="csrf-token" content="{{ csrf_token() }}">
The useStream hook will attempt to read this automatically, or you can pass it explicitly during initialization:
const { send } = useStream('/chat/stream', {
csrfToken: document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
});
This configuration allows you to maintain standard Inertia-driven pages while integrating high-performance real-time streaming in the same application.
XunLong Review: AI Content Engine That Writes Reports, Fiction & Decks
Index-TTS-LoRA: Fine-Tuning Voice Models for Natural Speech Synthesis
Fuck-U-Code: A Brutally Honest Code Quality Analyzer
Shendeng VPN Review: High-Speed Gaming, Video Streaming, and Unlimited Data
Infinite Radio: The AI DJ That Adapts Music Genres to Your Screen
Coze Studio: Build and Deploy AI Agents with Golang and React
Gemini-CLI-UI: A Web Interface for the Google Gemini CLI Coding Assistant
PandaWiki Setup Guide: Building an AI-Powered Knowledge Base
Scira: The Minimalist AI Search Engine for Grok, Claude, and Beyond
MindForger Review: A Private Markdown IDE for Personal Knowledge Management
SerenityOS Build Guide: A C++ Unix-Like System for x86, Arm, and RISC-V
Chinese Kinship Calculator: Instantly Decode Family Relationship Terms