Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
bdf2ad5
feat: render stream in js
kauffinger Jun 9, 2025
59d8117
fix: code rendering
kauffinger Jun 9, 2025
e45b617
chore: formatting
kauffinger Jun 10, 2025
0c7c071
chore: setup formatting & format
kauffinger Jun 10, 2025
73c4e56
chore: formatting
kauffinger Jun 10, 2025
58d1f43
feat: persist chats
kauffinger Jun 10, 2025
bbffdfd
feat: basic model selection
kauffinger Jun 10, 2025
c3b994d
chore: add o3-mini
kauffinger Jun 10, 2025
c192db6
chore: update to prism 0.72
kauffinger Jun 17, 2025
6277a97
chore: switch to prettier for formatting
kauffinger Jun 17, 2025
66b190e
chore: formatting
kauffinger Jun 17, 2025
d518a4b
chore: move settings and limit past chats
kauffinger Jun 17, 2025
4bb2530
fix: duplicate livewire import
kauffinger Jun 17, 2025
8a3ae80
wip
kauffinger Jun 19, 2025
9d0da3a
chore: align streamed & rendered view & fix flashing
kauffinger Jun 21, 2025
00d8226
feat: add list chats component
kauffinger Jun 21, 2025
1fd8324
chore: update claude.md
kauffinger Jun 21, 2025
722c144
feat: align pagination with fluxui
kauffinger Jun 21, 2025
aea809d
chore: turbo boost links 🚀
kauffinger Jun 21, 2025
da0201f
chore: padding
kauffinger Jun 21, 2025
15f4191
chore: extract card component
kauffinger Jun 21, 2025
2db1283
ref: don't use dtos
kauffinger Jun 23, 2025
75691fb
feat: show thinking all the time
kauffinger Jun 23, 2025
e8b20ad
chore: fix tests & redirect home to dashboard
kauffinger Jun 24, 2025
aa3a22b
chore: add back home name to /
kauffinger Jun 24, 2025
de70bde
chore: update deps & bump prism
kauffinger Jun 24, 2025
8f3c6a1
fix: chat rendering & cleanup
kauffinger Jun 24, 2025
4b513c9
chore: streamline code
kauffinger Jun 24, 2025
29d2617
chore: drop unused dtos
kauffinger Jun 24, 2025
b2b2a36
chore: extract actions
kauffinger Jun 24, 2025
ddca7c6
fix: multiple tool calls
kauffinger Jun 24, 2025
010656d
chore: move chat partials to nested directory
kauffinger Jun 26, 2025
0a072f0
fix: ensure thinking configuration doesn't break normal models
kauffinger Jun 26, 2025
e268aac
chore: move chat sidebar & remove navigate hover
kauffinger Jun 26, 2025
6176e14
chore: deduplicate & rename markdown processor
kauffinger Jun 26, 2025
8304081
chore: formatting
kauffinger Jun 26, 2025
9c80243
chore: formatting
kauffinger Jun 26, 2025
c2ea070
chore: imports
kauffinger Jun 26, 2025
123cc5e
chore: add tests
kauffinger Jun 26, 2025
7dc5056
chore: update readme
kauffinger Jun 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .blade.format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"useLaravelPint": true,
"attributeJsOptions": {
"semi": true,
"printWidth": 120
}
}
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ jobs:
run: npm run build

- name: Run Tests
run: ./vendor/bin/pest
run: ./vendor/bin/pest
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
composer.lock
tests/**/*.html
storage/framework/**/*.blade.php
resources/views/emails/**/*.blade.php
24 changes: 24 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"printWidth": 120,
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "all",
"plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-blade"],
"overrides": [
{
"files": ["*.yml", "*.yaml"],
"options": { "tabWidth": 2 }
},
{
"files": "*.md",
"options": { "tabWidth": 2 }
},
{
"files": ["*.blade.php"],
"options": {
"parser": "blade"
}
}
]
}
120 changes: 120 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is **Livewire Chat Kit** - a Laravel Starter Kit for building LLM-powered chat interfaces. It combines Laravel 12, Livewire 3, FluxUI components, and Prism PHP for LLM integration to create chat applications with minimal JavaScript.

## Development Commands

```bash
# Laravel Herd handles server/queue/logs automatically
# Only run this if you need console output visibility
composer dev

# Run tests
composer test

# Format code (PHP + JS/CSS)
composer format

# Check formatting
composer format:check

# Build assets for development
npm run dev

# Build assets for production
npm run build
```

## Architecture Overview

### Core Chat System

- **`app/Livewire/Chat.php`** - Main chat component handling LLM streaming and message management
- **`app/Livewire/Chat/Sidebar.php`** - Chat navigation and history
- **Message flow**: User input → `sendMessage()` → `runChatToolLoop()` → Prism LLM streaming → real-time UI updates via Livewire streams

### Data Layer

- **Models**: `User`, `Chat`, `Message` with proper relationships
- **DTOs**: `UserMessage`, `AssistantMessage` for type-safe message handling
- **Enums**: `OpenAiModel`, `Visibility` for consistent value objects
- **Policies**: `ChatPolicy` for authorization

### Frontend Architecture

- **Livewire-first**: Dynamic UI with minimal JavaScript
- **FluxUI components**: Professional UI component library (find available components by googling "site:fluxui.dev <component name>"). Only use free components.
- **Streaming responses**: Real-time message updates using Livewire streams
- **Markdown rendering**: Built-in markdown-it with syntax highlighting

### LLM Integration (Prism)

- **Default provider**: OpenAI GPT-4o-mini
- **Multi-provider support**: Easy switching between OpenAI, Anthropic, etc.
- **Streaming**: Real-time token streaming for smooth UX
- **Configuration**: LLM provider/model configurable per chat

## Key Technical Patterns

### Message Streaming Flow

1. User submits message via `x-chat.message-input`
2. `sendMessage()` adds user message to array, clears input
3. `$this->js('$wire.runChatToolLoop()')` triggers LLM call
4. `runChatToolLoop()` streams tokens via `$this->stream()`
5. Frontend `x-chat.assistant-message` updates in real-time
6. Complete message added to permanent `$messages` array

### Environment Configuration

- **Required**: `OPENAI_API_KEY` (or provider-specific keys)
- **Database**: SQLite by default, configurable
- **Development**: Laravel Herd handles server/queue/logs automatically

### File Structure Patterns

- **`/app/Livewire`** - All Livewire components
- **`/resources/views/livewire`** - Component Blade templates
- **`/resources/js`** - Minimal JavaScript (streaming markdown component)
- **`/tests`** - Pest tests with Livewire testing utilities

## Development Notes

### Code Style

- **PHP**: Laravel Pint for formatting, Rector for refactoring
- **JavaScript/CSS**: Prettier with Tailwind plugin
- **Blade**: Prettier blade plugin for template formatting

### Testing

- **Framework**: Pest PHP with Livewire plugin
- **Coverage**: Feature and Unit tests for core functionality

### Provider Switching

To switch LLM providers, modify the `runChatToolLoop()` method in `Chat.php`:

```php
$generator = Prism::text()
->using(Provider::Anthropic, 'claude-3-sonnet-20240229')
// ->using(Provider::OpenAI, 'gpt-4o-mini') // Default
```

### Asset Pipeline

- **Vite**: Modern build tool with hot reload
- **TailwindCSS 4**: Utility-first CSS with typography plugin
- **Laravel Herd**: Development environment handles all services automatically

## Development Workflow Notes

- You don't need to run npm run build, I have npm run dev running
- Use `php artisan` to do laravel related things like creating Models/Components etc.
- Stay CRUDDY
- Available UI components are under vendor/livewire/flux/stubs/resources/views/flux
- Find available component docs by googling "site:fluxui.dev <component name>"
103 changes: 74 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,27 @@ This starter kit provides a clean, simple foundation for creating chat applicati

## Features

* **Livewire-Powered:** Build dynamic interfaces with PHP.
* **Streamed Responses:** Real-time message streaming from LLMs for a smooth UX.
* **FluxUI Components:** Beautiful, pre-built UI components for a polished look and feel.
* **Prism Integration:** The Laravel-way of speaking to LLMs. Easy to use, test, and switch between providers (e.g., OpenAI, Anthropic).
* **Minimal JavaScript:** Focus on your PHP backend.
* **TailwindCSS Styled:** Includes a TailwindCSS setup with a typography plugin for rendering markdown.
### Chat System

- **Multi-Chat Management:** Create, manage, and navigate between multiple chat conversations
- **Chat Sidebar:** Quick access to recent chats with intelligent navigation
- **Chat Sharing:** Share conversations publicly or keep them private with visibility controls
- **Model Selection:** Choose from different LLM models (GPT-4o, GPT-4o-mini, etc.) per chat
- **Persistent History:** All conversations are saved and accessible across sessions

### LLM Integration

- **Livewire-Powered:** Build dynamic interfaces with PHP.
- **Streamed Responses:** Real-time message streaming from LLMs for a smooth UX.
- **Prism Integration:** The Laravel-way of speaking to LLMs. Easy to use, test, and switch between providers (e.g., OpenAI, Anthropic).
- **Tool Support:** Built-in LLM tool calling with visual feedback and result display

### UI & Design

- **FluxUI Components:** Beautiful, pre-built UI components for a polished look and feel.
- **Minimal JavaScript:** Focus on your PHP backend.
- **TailwindCSS Styled:** Includes a TailwindCSS setup with a typography plugin for rendering markdown.
- **Real-time Updates:** Seamless UI updates using Livewire streams

## Installation

Expand Down Expand Up @@ -68,45 +83,75 @@ Remember to add the corresponding API key to your `.env` file (e.g., `ANTHROPIC_

### 2. Explore the Chat Interface

Navigate to your application's `/dashboard` route (or wherever you've set up the chat component) to start interacting with the chat interface.
Navigate to your application's `/dashboard` route to start interacting with the chat interface. You can:

- Create new chats from the sidebar
- Switch between existing conversations
- Share chats publicly or keep them private
- Change the LLM model for each chat
- Use built-in tools (like the example sum calculator)

## Architecture

## Core Components
### Core Components

### `app/Livewire/Chat.php`
- **`app/Livewire/Chats/Index.php`** - Paginate over all of your chats
- **`app/Livewire/Chats/Show.php`** - Main chat component handling LLM streaming, tool calls, and message management
- **`app/Livewire/ChatSidebar.php`** - Chat navigation, history, and new chat creation
- **`app/Models/Chat.php`** - Chat model with user relationships and visibility controls
- **`app/Models/Message.php`** - Message model with tool call/result support and Prism integration

This is the heart of the chat functionality.
### Business Logic Layer

### `resources/views/livewire/chat.blade.php`
- **`app/Actions/`** - Clean action classes for complex operations:
- `AddNewUserMessageToChat` - Handles user message persistence
- `UpdateStreamDataFromPrismChunk` - Processes streaming LLM responses
- `PersistStreamDataToMessages` - Saves complete responses to database
- **`app/Dtos/StreamData.php`** - Type-safe data transfer object for streaming
- **`app/Policies/ChatPolicy.php`** - Authorization rules for chat access and sharing

This Blade view renders the chat interface.
### UI Components

- **Chat Interface:** Real-time streaming with markdown rendering
- **Tool Results Display:** Visual feedback for LLM tool executions
- **Model Selector:** Per-chat model configuration
- **Sharing Controls:** Public/private visibility management

## How it Works

1. User types a message in `x-chat.message-input` and hits send.
2. `sendMessage()` in `Chat.php` is triggered.
* The user's message is added to the `$messages` array.
* The input field is cleared.
* `$this->js('$wire.runChatToolLoop()')` is called, which immediately invokes the `runChatToolLoop()` method. This allows the UI to update with the user's message before the LLM call.
3. `runChatToolLoop()`:
* Constructs a request to the LLM using Prism, including the system prompt and the current chat history.
* The request is sent as a stream.
* As tokens arrive from the LLM:
* They are appended to a local `$message` variable.
* The `stream()` method sends the accumulated `$message` (converted to markdown) to the frontend, updating the part of the view listening to `streamed-message`. This is typically handled by the `x-chat.assistant-message` component.
4. Once the LLM finishes generating its response:
* The complete assistant message is added to the `$messages` array. The temporary streamed display is effectively replaced by the final message in the loop.
### Message Flow

1. **User Input:** Message typed in chat interface and submitted
2. **Message Processing:** `AddNewUserMessageToChat` action persists user message
3. **LLM Streaming:** `runChatToolLoop()` initiates real-time streaming with Prism
4. **Stream Handling:** `UpdateStreamDataFromPrismChunk` processes each chunk (text, tool calls, results)
5. **UI Updates:** Livewire streams update the interface in real-time
6. **Persistence:** `PersistStreamDataToMessages` saves complete conversation

### Tool Integration

- **Tool Definitions:** Tools are defined in the `runChatToolLoop()` method
- **Automatic Execution:** LLM can call tools during conversation flow
- **Visual Feedback:** Tool calls and results are displayed with dedicated UI components

### Technical Implementation

- **UUID Models:** All models use UUIDs for security and scalability
- **Policy Authorization:** Fine-grained access control with `ChatPolicy`
- **Type Safety:** DTOs and strong typing throughout the application
- **Testing:** Comprehensive Pest test suite with Livewire integration
- **Code Quality:** Laravel Pint, Rector, and Prettier for consistent formatting

## Contributing

Contributions are welcome! If you'd like to improve the Livewire Chat Kit, please feel free to:

* Report a bug.
* Suggest a new feature.
* Submit a pull request.
- Report a bug.
- Suggest a new feature.
- Submit a pull request.

Please visit the [GitHub repository](https://github.com/kauffinger/livewire-chat-kit) to contribute.

## License

This project is open-sourced software licensed under the [MIT license](LICENSE.md).
```
34 changes: 34 additions & 0 deletions app/Actions/AddNewUserMessageToChat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Actions;

use App\Models\Chat;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class AddNewUserMessageToChat
{
public function handle(Chat $chat, string $userMessage): void
{
DB::transaction(function () use ($chat, $userMessage): void {
$chat->messages()->create([
'role' => 'user',
'parts' => [
'text' => $userMessage,
],
'attachments' => '[]',
]);

$this->updateChatTitleIfMessageIsFirstMessage($chat, $userMessage);
});
}

private function updateChatTitleIfMessageIsFirstMessage(Chat $chat, string $userMessage): void
{
if ($chat->messages()->count() === 1) {
$chat->update([
'title' => Str::limit($userMessage, 50),
]);
}
}
}
31 changes: 31 additions & 0 deletions app/Actions/PersistStreamDataToMessages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Actions;

use App\Dtos\StreamData;
use App\Models\Chat;
use Illuminate\Support\Facades\DB;

class PersistStreamDataToMessages
{
public function handle(Chat $chat, StreamData $streamData): void
{
DB::transaction(function () use ($chat, $streamData): void {
if ($streamData->toolResults !== []) {
$chat->messages()->create([
'role' => 'tool_result',
'parts' => ['toolResults' => $streamData->toolResults],
'attachments' => '[]',
]);
}

$chat->messages()->create([
'role' => 'assistant',
'parts' => $streamData->toArray(),
'attachments' => '[]',
]);

$chat->touch();
});
}
}
Loading