Skip to content

Commit 91bdb39

Browse files
Add guest trial chat, agent tier credits, and deployment instructions
Co-authored-by: bixmatech <bixmatech@gmail.com>
1 parent d1c4cfe commit 91bdb39

File tree

5 files changed

+74
-6
lines changed

5 files changed

+74
-6
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,17 @@ If you discover a security vulnerability within Laravel, please send an e-mail t
5959
## License
6060

6161
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
62+
63+
# AI Agents SaaS (Laravel)
64+
65+
## Plesk Deployment
66+
- Zip the project directory (excluding vendor if needed) and upload to your Plesk domain root.
67+
- Extract and ensure `storage` and `bootstrap/cache` are writable.
68+
- Create a MySQL database in Plesk and set `.env` accordingly (DB_*, APP_URL, MAIL_*, OPENAI_API_KEY, STRIPE_*).
69+
- Visit `/install` to generate app key, run migrations, and create the first admin user.
70+
71+
## Features
72+
- Agents (chatbots) with model settings, public pages, embed.
73+
- Chat UI (guest trial of 3 messages, then login), history, sharing.
74+
- Credits and Stripe checkout; manual bank payments with admin approval.
75+
- Admin panel to manage agents, packages, users, payments.

app/Http/Controllers/ChatController.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ protected function filterBadWords(string $text): string
2323
public function send(Request $request, Agent $agent)
2424
{
2525
$user = $request->user();
26+
$requiredMin = $agent->requiredMinCredits();
27+
if ($requiredMin !== null && $user->credits < $requiredMin) {
28+
return response()->json(['error' => 'This agent requires at least '.$requiredMin.' credits.'], 403);
29+
}
2630
if ($user->credits <= 0) {
2731
return response()->json(['error' => 'Insufficient credits'], 402);
2832
}
@@ -85,4 +89,40 @@ public function send(Request $request, Agent $agent)
8589
'remaining_credits' => $user->fresh()->credits,
8690
]);
8791
}
92+
93+
public function guest(Request $request, Agent $agent)
94+
{
95+
$data = $request->validate([
96+
'message' => 'required|string|max:4000',
97+
'thread_key' => 'nullable|string',
98+
]);
99+
100+
$trialUsed = (int) $request->session()->get('guest_trial_count', 0);
101+
if ($trialUsed >= 3) {
102+
return response()->json(['error' => 'Trial limit reached. Please log in.'], 429);
103+
}
104+
105+
$message = $this->filterBadWords($data['message']);
106+
107+
$messages = [];
108+
if ($agent->prompt) $messages[] = ['role' => 'system', 'content' => $agent->prompt];
109+
$messages[] = ['role' => 'user', 'content' => $message];
110+
111+
$client = OpenAI::client(config('services.openai.api_key'));
112+
$resp = $client->chat()->create([
113+
'model' => $agent->model,
114+
'messages' => $messages,
115+
'temperature' => $agent->temperature ?? 1.0,
116+
'max_tokens' => $agent->max_tokens ?? 256,
117+
]);
118+
119+
$answer = $resp->choices[0]->message->content ?? '';
120+
121+
$request->session()->put('guest_trial_count', $trialUsed + 1);
122+
123+
return response()->json([
124+
'message' => $answer,
125+
'remaining_trial' => max(0, 3 - ($trialUsed + 1)),
126+
]);
127+
}
88128
}

app/Models/Agent.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,15 @@ public function category()
3939
{
4040
return $this->belongsTo(Category::class);
4141
}
42+
43+
public function tiers()
44+
{
45+
return $this->belongsToMany(Tier::class);
46+
}
47+
48+
public function requiredMinCredits(): ?int
49+
{
50+
$min = $this->tiers()->min('min_credits');
51+
return $min ? (int) $min : null;
52+
}
4253
}

resources/views/agents/public.blade.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<input id="message" class="flex-1 border rounded p-2" placeholder="Type your message"/>
1717
<button id="send" class="px-4 py-2 bg-indigo-600 text-white rounded">Send</button>
1818
</div>
19+
<div class="mt-2 text-sm text-gray-500">@auth You're logged in, full credits apply. @else You have 3 trial messages before login. @endauth</div>
1920
</div>
2021
@vite(['resources/js/app.js'])
2122
<script>
@@ -36,13 +37,13 @@
3637
replyDiv.innerHTML = '<div class="inline-block bg-gray-50 dark:bg-gray-800 rounded p-2">...</div>';
3738
chat.appendChild(replyDiv);
3839
try {
39-
const res = await axios.post('/api/chat/{{ $agent->slug }}'+ (threadId ? ('?thread_id='+threadId) : ''), {
40-
message: text,
41-
thread_id: threadId,
42-
});
43-
threadId = res.data.thread_id;
40+
const url = @json(auth()->check() ? url('/api/chat/'.$agent->slug.'/send') : url('/api/chat/'.$agent->slug.'/guest'));
41+
const payload = @json(auth()->check()) ? {message: text, thread_id: threadId} : {message: text};
42+
const res = await axios.post(url + (threadId && @json(auth()->check()) ? ('?thread_id='+threadId) : ''), payload);
43+
if (res.data.thread_id) threadId = res.data.thread_id;
44+
const suffix = res.data.remaining_trial !== undefined ? ` (Trials left: ${res.data.remaining_trial})` : '';
4445
replyDiv.innerHTML = '<div class="inline-block bg-gray-50 dark:bg-gray-800 rounded p-2">'+
45-
(res.data.message || '(empty)') +'</div>';
46+
(res.data.message || '(empty)') + suffix +'</div>';
4647
} catch (e) {
4748
replyDiv.innerHTML = '<div class="inline-block bg-red-50 text-red-700 rounded p-2">'+ (e.response?.data?.error || 'Error') +'</div>';
4849
}

routes/api.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
use Illuminate\Support\Facades\Route;
44
use App\Http\Controllers\ChatController;
55

6+
Route::post('/chat/{agent:slug}/guest', [ChatController::class, 'guest']);
7+
68
Route::middleware('auth:sanctum')->group(function () {
79
Route::post('/chat/{agent:slug}/send', [ChatController::class, 'send']);
810
});

0 commit comments

Comments
 (0)