# larachat
**Repository Path**: mirrors_laravel/larachat
## Basic Information
- **Project Name**: larachat
- **Description**: An AI chat demo application built with Inertia and React with the useStream hook
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-06-08
- **Last Updated**: 2025-10-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Laravel Chat Demo with useStream
A real-time chat application demonstrating the power of Laravel's `useStream` hook for React applications. This demo showcases how to build a ChatGPT-like interface with streaming responses, message persistence, and authentication support.
## Video Tutorial
Watch the complete tutorial on YouTube:
[](https://youtu.be/BuUbTRHuvAw)
🎥 **[Watch on YouTube: Building an AI Chat App with Laravel and React useStream](https://youtu.be/BuUbTRHuvAw)**
## Features
- 🚀 Real-time streaming responses using Server-Sent Events (SSE)
- 💬 ChatGPT-like interface with message history
- 🔐 Optional authentication with message persistence
- 🎯 Automatic chat title generation using `useEventStream`
- 🎨 Beautiful UI with Tailwind CSS v4 and shadcn/ui
- 📱 Responsive design with mobile support
- 🌓 Dark/light mode with system preference detection
## System Requirements
Before getting started, ensure your system meets these requirements:
### Required
- **PHP 8.2 or higher** with the following extensions:
- curl, dom, fileinfo, filter, hash, mbstring, openssl, pcre, pdo, session, tokenizer, xml
- **Node.js 22 or higher** (for React 19 support)
- **Composer 2.x**
- **SQLite** (default database, or MySQL/PostgreSQL if preferred)
- **Git** (for cloning the repository)
### Optional but Recommended
- **OpenAI API Key** (for AI responses - the app works without it but uses mock responses)
- **PHP development server** or **Laravel Valet** for local development
### Framework Versions Used
- **Laravel 12.0** (latest)
- **React 19** (latest)
- **Tailwind CSS v4** (beta)
- **Inertia.js 2.0**
> **Note**: This demo uses cutting-edge versions to showcase the latest features. If you encounter compatibility issues, check the versions above against your local environment.
## Quick Start
1. Clone the repository and install dependencies:
```bash
composer install
npm install
```
2. Set up your environment:
```bash
cp .env.example .env
php artisan key:generate
```
3. Configure your OpenAI API key in `.env`:
```env
OPENAI_API_KEY=your-api-key-here
```
4. Run migrations and start the development server:
```bash
php artisan migrate
composer dev
```
> **Note**: The `composer dev` command runs multiple processes concurrently (server, queue, logs, and Vite). If you encounter issues, run each command separately in different terminals:
> ```bash
> # Terminal 1: Laravel server
> php artisan serve
>
> # Terminal 2: Queue worker (for background jobs)
> php artisan queue:listen
>
> # Terminal 3: Vite development server
> npm run dev
> ```
## Troubleshooting
### Common Setup Issues
**"Node.js version too old" error:**
- Ensure you have Node.js 22+ installed
- Use `nvm` to manage Node.js versions: `nvm install 22 && nvm use 22`
**"Class 'OpenAI' not found" error:**
- Run `composer install` to ensure all PHP dependencies are installed
- Check that your `OPENAI_API_KEY` is set in `.env` (or leave it empty for mock responses)
**Database connection errors:**
- The default setup uses SQLite - ensure the `database/database.sqlite` file exists
- If it's missing, create it with: `touch database/database.sqlite`
- Then run: `php artisan migrate`
**Vite build errors with Tailwind CSS v4:**
- Clear your npm cache: `npm cache clean --force`
- Delete `node_modules` and reinstall: `rm -rf node_modules && npm install`
- Ensure you're using Node.js 22+
**"CSRF token mismatch" for streaming:**
- Ensure the CSRF meta tag is present in your layout (already included in this demo)
- Clear browser cache and cookies for the local development domain
## Using the useStream Hook
The `useStream` hook from `@laravel/stream-react` makes it incredibly simple to consume streamed responses in your React application. Here's how this demo implements it:
### Basic Chat Implementation
```tsx
import { useStream } from '@laravel/stream-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;
// Add user message to local state
const newMessage = { type: 'prompt', content: query };
setMessages([...messages, newMessage]);
// Send all messages to the stream
send({ messages: [...messages, newMessage] });
e.target.reset();
};
return (
{/* Display messages */}
{messages.map((msg, i) => (
{msg.content}
))}
{/* Show streaming response */}
{data &&
{data}
}
{/* Input form */}
);
}
```
### Key Concepts
1. **Stream URL**: The hook connects to your Laravel endpoint that returns a streamed response
2. **Sending Data**: The `send` method posts JSON data to your stream endpoint
3. **Streaming State**: Use `isStreaming` to show loading indicators or disable inputs
4. **Response Accumulation**: The `data` value automatically accumulates the streamed response
### Backend Stream Endpoint
On the Laravel side, create a streaming endpoint:
```php
public function stream(Request $request)
{
return response()->stream(function () use ($request) {
$messages = $request->input('messages', []);
// Stream response from OpenAI
$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
This demo showcases `useEventStream` for real-time updates. When you create a new chat, it initially shows "Untitled" but automatically generates a proper title using OpenAI and streams it back in real-time.
#### Key Implementation Details
The critical configuration for `useEventStream` is using `eventName` (not `event`) and handling the `MessageEvent` properly:
```tsx
import { useEventStream } from '@laravel/stream-react';
function TitleGenerator({ chatId, onTitleUpdate, onComplete }) {
const { message } = useEventStream(`/chat/${chatId}/title-stream`, {
eventName: "title-update", // Use 'eventName', not 'event'
endSignal: "",
onMessage: (event) => { // Receives MessageEvent object
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; // This is a listener component
}
```
#### Multiple EventStream Consumers
You can have multiple components listening to the same EventStream for different purposes:
```tsx
// Component 1: Updates conversation title
setShouldGenerateTitle(false)}
/>
// Component 2: Updates sidebar
setShouldUpdateSidebar(false)}
/>
```
### Backend EventStream Implementation
The Laravel backend uses `response()->eventStream()` to generate and stream title updates:
```php
use Illuminate\Http\StreamedEvent;
public function titleStream(Chat $chat)
{
$this->authorize('view', $chat);
return response()->eventStream(function () use ($chat) {
// If title already exists, send it immediately
if ($chat->title && $chat->title !== 'Untitled') {
yield new StreamedEvent(
event: 'title-update',
data: json_encode(['title' => $chat->title])
);
return;
}
// Generate title using OpenAI
$firstMessage = $chat->messages()->where('type', 'prompt')->first();
$response = OpenAI::chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
[
'role' => 'system',
'content' => 'Generate a concise, descriptive title (max 50 characters) for a chat that starts with the following message. Respond with 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]);
// Stream the new title
yield new StreamedEvent(
event: 'title-update',
data: json_encode(['title' => $title])
);
}, endStreamWith: new StreamedEvent(event: 'title-update', data: ''));
}
```
#### EventStream Route Configuration
```php
Route::middleware('auth')->group(function () {
Route::get('/chat/{chat}/title-stream', [ChatController::class, 'titleStream'])
->name('chat.title.stream');
});
```
#### How It Works
1. **User sends first message** → AI response streams back via `useStream`
2. **Response completes** → Triggers EventStream for title generation
3. **Server generates title** → Uses OpenAI to create descriptive title
4. **EventStream sends update** → Both conversation header and sidebar update in real-time
5. **Components unmount** → Clean up after receiving title
This creates a seamless experience where users see titles generated and updated live without any page refreshes.
### Advanced Features in This Demo
- **Authentication Support**: Authenticated users get their chats persisted to the database
- **Dynamic Routing**: Different stream URLs for authenticated vs anonymous users
- **Message Persistence**: Completed responses are added to the message history
- **Real-time Title Generation**: Event streams automatically update chat titles
- **Error Handling**: Graceful fallbacks for API failures
## Project Structure
```
resources/js/
├── pages/
│ └── chat.tsx # Main chat component with useStream
├── components/
│ ├── conversation.tsx # Message display component
│ └── ui/ # shadcn/ui components
└── layouts/
└── app-layout.tsx # Main application layout
```
## Why useStream Needs CSRF Tokens (Even with Inertia)
If you're familiar with Inertia.js, you might wonder why we need to handle CSRF tokens manually when using `useStream`. Here's the key distinction:
### Inertia Forms vs Stream Endpoints
**Inertia Forms** use the `useForm` helper:
```tsx
// Standard Inertia approach - CSRF handled automatically
const form = useForm({ message: '' });
form.post('/chat'); // Returns an Inertia response
```
**Stream Endpoints** require manual CSRF handling:
```tsx
// Streaming approach - needs CSRF token
const { send } = useStream('/chat/stream'); // This is a POST to an API endpoint
```
### Why the Difference?
1. **Different Response Types**: Inertia expects a page component response, while streaming endpoints return Server-Sent Events (SSE)
2. **Direct API Calls**: The `useStream` hook makes direct POST requests to your endpoint, bypassing Inertia's request lifecycle
3. **No Automatic CSRF**: Since it's not an Inertia request, CSRF tokens aren't automatically included
### Setting Up CSRF for Streams
Add the CSRF meta tag to your layout:
```blade
```
The `useStream` hook automatically reads this token, or you can provide it explicitly:
```tsx
const { send } = useStream('/chat/stream', {
csrfToken: document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
});
```
This separation actually gives you more flexibility - you can have both traditional Inertia pages and real-time streaming features in the same application!
## Learn More
- [Prism by Echo Labs](https://prism.echolabs.dev/) - Alternative Laravel package for AI integration (supports multiple providers)
- [Laravel Stream Documentation](https://github.com/laravel/stream)
- [Server-Sent Events in Laravel](https://laravel.com/docs/responses#event-streams)
- [OpenAI PHP Client](https://github.com/openai-php/client) - Used in this demo for OpenAI integration
## License
This demo is open-sourced software licensed under the [MIT license](LICENSE.md).