Skip to content

Commit d2363bf

Browse files
feat: Implement Vanilla JS AI Chat Prototype
- Added VanillaAiChatController to handle chat requests via Laravel backend. - Integrated OpenAI PHP client for AI responses, with fallback to canned responses if API key is not configured. - Defined an API route /api/vanilla-ai-chat. - Created public/ai_chat.html for the chat interface. - Implemented public/ai_chat.js for frontend logic using Vanilla JavaScript to communicate with the backend.
1 parent ac4e504 commit d2363bf

File tree

4 files changed

+291
-0
lines changed

4 files changed

+291
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\Http; // Or use OpenAI PHP client if installed
7+
use OpenAI\Laravel\Facades\OpenAI; // Use this if openai-php/client is set up
8+
9+
class VanillaAiChatController extends Controller
10+
{
11+
public function chat(Request $request)
12+
{
13+
$request->validate([
14+
'message' => 'required|string|max:2000',
15+
]);
16+
17+
$userMessage = $request->input('message');
18+
19+
// --- Option 1: Using OpenAI PHP Client (Recommended if set up) ---
20+
// Ensure your OPENAI_API_KEY is in .env and you've run composer require openai-php/client
21+
// Also, make sure you have published the OpenAI config if needed: php artisan vendor:publish --provider="OpenAI\Laravel\OpenAIServiceProvider"
22+
try {
23+
if (!config('openai.api_key') && !env('OPENAI_API_KEY')) {
24+
// Fallback to a canned response if API key is not configured
25+
return response()->json(['reply' => "Hello from Laravel! Your message was: \"{$userMessage}\". (AI not configured)"]);
26+
}
27+
28+
$response = OpenAI::chat()->create([
29+
'model' => 'gpt-3.5-turbo', // Or gpt-4 if you have access
30+
'messages' => [
31+
['role' => 'system', 'content' => 'You are a helpful assistant.'],
32+
['role' => 'user', 'content' => $userMessage],
33+
],
34+
]);
35+
36+
$reply = $response->choices[0]->message->content;
37+
return response()->json(['reply' => trim($reply)]);
38+
39+
} catch (\Throwable $th) {
40+
// Log the error for debugging
41+
// Log::error('OpenAI API Error: ' . $th->getMessage());
42+
// Fallback to a canned response in case of an API error
43+
// In a real app, you might want more sophisticated error handling
44+
if (str_contains($th->getMessage(), 'cURL error 6')) { // Example: DNS resolution error
45+
return response()->json(['reply' => "Sorry, I'm having trouble connecting to the AI service right now. Please check network or API key."]);
46+
}
47+
// Generic error if API key is missing or invalid
48+
if (str_contains($th->getMessage(), 'Incorrect API key provided') || str_contains($th->getMessage(), 'You didn\'t provide an API key')) {
49+
return response()->json(['reply' => "AI service connection error: Please ensure your API key is correctly configured in the .env file."]);
50+
}
51+
return response()->json(['reply' => "Sorry, an error occurred with the AI service. Your message was: \"{$userMessage}\". Error: " . $th->getMessage()]);
52+
}
53+
54+
// --- Option 2: Canned response (if you don't want to use an API yet) ---
55+
// return response()->json(['reply' => "Hello from Laravel! You said: \"{$userMessage}\". This is a test response."]);
56+
57+
// --- Option 3: Using Laravel's HTTP Client to call a generic AI API (more manual) ---
58+
/*
59+
$apiKey = env('SOME_OTHER_AI_API_KEY');
60+
if (!$apiKey) {
61+
return response()->json(['reply' => "AI API key not configured."]);
62+
}
63+
try {
64+
$response = Http::withHeaders([
65+
'Authorization' => 'Bearer ' . $apiKey,
66+
'Content-Type' => 'application/json',
67+
])->post('https://api.example-ai.com/v1/chat', [ // Replace with actual API endpoint
68+
'prompt' => $userMessage,
69+
'max_tokens' => 150,
70+
]);
71+
72+
if ($response->successful()) {
73+
return response()->json(['reply' => $response->json()['choices'][0]['text'] ?? 'No reply generated.']);
74+
} else {
75+
return response()->json(['reply' => 'Failed to get response from AI.', 'details' => $response->body()]);
76+
}
77+
} catch (\Exception $e) {
78+
return response()->json(['reply' => 'Error connecting to AI service: ' . $e->getMessage()]);
79+
}
80+
*/
81+
}
82+
}

public/ai_chat.html

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Vanilla JS AI Chat</title>
7+
<style>
8+
body {
9+
font-family: Arial, sans-serif;
10+
margin: 0;
11+
padding: 0;
12+
background-color: #f4f4f4;
13+
display: flex;
14+
justify-content: center;
15+
align-items: center;
16+
min-height: 100vh;
17+
}
18+
.chat-container {
19+
background-color: #fff;
20+
border-radius: 8px;
21+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
22+
width: 100%;
23+
max-width: 600px;
24+
display: flex;
25+
flex-direction: column;
26+
overflow: hidden;
27+
height: 80vh; /* Max height for the chat container */
28+
}
29+
.chat-header {
30+
background-color: #5a67d8; /* Indigo-like color */
31+
color: white;
32+
padding: 15px;
33+
text-align: center;
34+
font-size: 1.2em;
35+
}
36+
.chat-messages {
37+
flex-grow: 1;
38+
padding: 20px;
39+
overflow-y: auto;
40+
display: flex;
41+
flex-direction: column;
42+
gap: 10px;
43+
}
44+
.message {
45+
padding: 10px 15px;
46+
border-radius: 18px;
47+
max-width: 75%;
48+
line-height: 1.4;
49+
}
50+
.user-message {
51+
background-color: #e2e8f0; /* Light gray */
52+
color: #2d3748; /* Dark gray text */
53+
align-self: flex-end;
54+
border-bottom-right-radius: 5px;
55+
}
56+
.ai-message {
57+
background-color: #c3dafe; /* Lighter blue */
58+
color: #2c5282; /* Darker blue text */
59+
align-self: flex-start;
60+
border-bottom-left-radius: 5px;
61+
}
62+
.chat-input {
63+
display: flex;
64+
padding: 15px;
65+
border-top: 1px solid #e2e8f0;
66+
background-color: #f7fafc;
67+
}
68+
#messageInput {
69+
flex-grow: 1;
70+
padding: 10px;
71+
border: 1px solid #cbd5e0;
72+
border-radius: 20px;
73+
margin-right: 10px;
74+
font-size: 1em;
75+
}
76+
#sendButton {
77+
padding: 10px 20px;
78+
background-color: #5a67d8;
79+
color: white;
80+
border: none;
81+
border-radius: 20px;
82+
cursor: pointer;
83+
font-size: 1em;
84+
}
85+
#sendButton:hover {
86+
background-color: #434190;
87+
}
88+
.typing-indicator {
89+
font-style: italic;
90+
color: #718096; /* Medium gray */
91+
padding: 5px 0;
92+
align-self: flex-start;
93+
display: none; /* Hidden by default */
94+
}
95+
</style>
96+
</head>
97+
<body>
98+
<div class="chat-container">
99+
<div class="chat-header">AI Chat (Vanilla JS)</div>
100+
<div class="chat-messages" id="chatMessages">
101+
<!-- Messages will be appended here -->
102+
<div class="message ai-message">Hello! How can I help you today?</div>
103+
</div>
104+
<div class="typing-indicator" id="typingIndicator">AI is typing...</div>
105+
<div class="chat-input">
106+
<input type="text" id="messageInput" placeholder="Type your message...">
107+
<button id="sendButton">Send</button>
108+
</div>
109+
</div>
110+
111+
<script src="ai_chat.js"></script>
112+
</body>
113+
</html>

public/ai_chat.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
const messageInput = document.getElementById('messageInput');
3+
const sendButton = document.getElementById('sendButton');
4+
const chatMessages = document.getElementById('chatMessages');
5+
const typingIndicator = document.getElementById('typingIndicator');
6+
7+
// Function to add a message to the chat interface
8+
function addMessage(text, sender) {
9+
const messageDiv = document.createElement('div');
10+
messageDiv.classList.add('message', sender === 'user' ? 'user-message' : 'ai-message');
11+
messageDiv.textContent = text;
12+
chatMessages.appendChild(messageDiv);
13+
chatMessages.scrollTop = chatMessages.scrollHeight; // Scroll to the bottom
14+
}
15+
16+
// Function to handle sending a message
17+
async function sendMessage() {
18+
const messageText = messageInput.value.trim();
19+
if (messageText === '') {
20+
return;
21+
}
22+
23+
addMessage(messageText, 'user');
24+
messageInput.value = ''; // Clear input field
25+
typingIndicator.style.display = 'block'; // Show typing indicator
26+
27+
try {
28+
// Assuming Laravel is served at the root and api.php routes are prefixed with /api
29+
// If your Laravel setup is different (e.g., in a subfolder or different port), adjust the URL.
30+
const response = await fetch('/api/vanilla-ai-chat', {
31+
method: 'POST',
32+
headers: {
33+
'Content-Type': 'application/json',
34+
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') // Optional: for CSRF if web routes are used
35+
},
36+
body: JSON.stringify({ message: messageText })
37+
});
38+
39+
typingIndicator.style.display = 'none'; // Hide typing indicator
40+
41+
if (!response.ok) {
42+
const errorData = await response.json();
43+
addMessage(`Error: ${errorData.message || response.statusText}`, 'ai');
44+
if (errorData.reply) { // Show detailed reply from backend if available
45+
addMessage(errorData.reply, 'ai');
46+
}
47+
return;
48+
}
49+
50+
const data = await response.json();
51+
if (data.reply) {
52+
addMessage(data.reply, 'ai');
53+
} else {
54+
addMessage('Sorry, I could not get a reply.', 'ai');
55+
}
56+
57+
} catch (error) {
58+
typingIndicator.style.display = 'none'; // Hide typing indicator
59+
console.error('Send message error:', error);
60+
addMessage(`Network error or server is unreachable. (${error.message})`, 'ai');
61+
}
62+
}
63+
64+
// Event listeners
65+
sendButton.addEventListener('click', sendMessage);
66+
messageInput.addEventListener('keypress', (event) => {
67+
if (event.key === 'Enter') {
68+
sendMessage();
69+
}
70+
});
71+
72+
// Initial focus on input
73+
messageInput.focus();
74+
});

routes/api.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
use Illuminate\Http\Request;
4+
use Illuminate\Support\Facades\Route;
5+
use App\Http\Controllers\VanillaAiChatController;
6+
7+
/*
8+
|--------------------------------------------------------------------------
9+
| API Routes
10+
|--------------------------------------------------------------------------
11+
|
12+
| Here is where you can register API routes for your application. These
13+
| routes are loaded by the RouteServiceProvider and all of them will
14+
| be assigned to the "api" middleware group. Make something great!
15+
|
16+
*/
17+
18+
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
19+
return $request->user();
20+
});
21+
22+
Route::post('/vanilla-ai-chat', [VanillaAiChatController::class, 'chat']);

0 commit comments

Comments
 (0)