feat(cms): implement complete SysSiteCMS content management system
Initial implementation of the HomeSysPanel CMS built with Laravel 13.0. Includes complete admin panel, content management, user authentication, modular architecture for products/services/chatbot, and marketing tools. Key features: - User authentication with email verification and password reset - Role-based access control (Admin/User with granular permissions) - Content management (pages, news, FAQ, downloads, galleries, banners) - Contact management system with subjects and responses - Marketing tools (email campaigns, visitor analytics, social sharing) - Modular architecture (Products, Services, Chatbot modules) - AI Chatbot integration with OpenRouter.ai and LlamaCpp support - Visitor tracking with geolocation - SEO optimization with meta tags and Open Graph - Responsive design with Bootstrap 5 - Dark mode support - Maintenance mode - Feature request system
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Chatbot\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\Chatbot\Models\ChatbotKnowledge;
|
||||
use App\Modules\Chatbot\Models\ChatbotRule;
|
||||
use App\Modules\Chatbot\Models\ChatbotConversation;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use App\Notifications\FeatureRequestNotification;
|
||||
|
||||
class ChatbotController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the knowledge base listing.
|
||||
*/
|
||||
public function index(): \Illuminate\View\View
|
||||
{
|
||||
$knowledgeEntries = ChatbotKnowledge::orderBy('category')
|
||||
->orderBy('title')
|
||||
->paginate(20);
|
||||
|
||||
$categories = ChatbotKnowledge::getCategories();
|
||||
|
||||
return view('chatbot::admin.index', compact('knowledgeEntries', 'categories'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new knowledge entry.
|
||||
*/
|
||||
public function create(): \Illuminate\View\View
|
||||
{
|
||||
$categories = ChatbotKnowledge::getCategories();
|
||||
return view('chatbot::admin.create', compact('categories'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created knowledge entry.
|
||||
*/
|
||||
public function store(Request $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'restrictions' => 'nullable|string',
|
||||
'category' => 'required|string|in:' . implode(',', array_keys(ChatbotKnowledge::getCategories())),
|
||||
]);
|
||||
|
||||
$validated['active'] = $request->has('active');
|
||||
|
||||
ChatbotKnowledge::create($validated);
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.index')
|
||||
->with('success', 'Informação adicionada à base de conhecimento com sucesso!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing a knowledge entry.
|
||||
*/
|
||||
public function edit(ChatbotKnowledge $chatbot_knowledge): \Illuminate\View\View
|
||||
{
|
||||
$entry = $chatbot_knowledge;
|
||||
$categories = ChatbotKnowledge::getCategories();
|
||||
return view('chatbot::admin.edit', compact('entry', 'categories'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a knowledge entry.
|
||||
*/
|
||||
public function update(Request $request, ChatbotKnowledge $chatbot_knowledge): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'restrictions' => 'nullable|string',
|
||||
'category' => 'required|string|in:' . implode(',', array_keys(ChatbotKnowledge::getCategories())),
|
||||
]);
|
||||
|
||||
$validated['active'] = $request->has('active');
|
||||
|
||||
$chatbot_knowledge->update($validated);
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.index')
|
||||
->with('success', 'Informação atualizada com sucesso!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a knowledge entry.
|
||||
*/
|
||||
public function destroy(ChatbotKnowledge $chatbot_knowledge): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$chatbot_knowledge->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.index')
|
||||
->with('success', 'Informação removida da base de conhecimento.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display conversations listing with visitor contacts.
|
||||
*/
|
||||
public function conversations(): \Illuminate\View\View
|
||||
{
|
||||
$conversations = ChatbotConversation::with('messages')
|
||||
->whereNotNull('visitor_name')
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(20);
|
||||
|
||||
return view('chatbot::admin.conversations', compact('conversations'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a specific conversation with all messages.
|
||||
*/
|
||||
public function showConversation(ChatbotConversation $conversation): \Illuminate\View\View
|
||||
{
|
||||
$conversation->load('messages');
|
||||
|
||||
return view('chatbot::admin.conversation-show', compact('conversation'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a conversation.
|
||||
*/
|
||||
public function destroyConversation(ChatbotConversation $conversation): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$conversation->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.conversations')
|
||||
->with('success', 'Conversa removida com sucesso.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display chatbot settings.
|
||||
*/
|
||||
public function settings(): \Illuminate\View\View
|
||||
{
|
||||
$settings = [
|
||||
'chatbot_enabled' => Setting::get('chatbot_enabled', 'off'),
|
||||
'chatbot_provider' => Setting::get('chatbot_provider', 'openrouter'),
|
||||
'chatbot_openrouter_api_key' => Setting::get('chatbot_openrouter_api_key'),
|
||||
'chatbot_openrouter_model' => Setting::get('chatbot_openrouter_model', 'openai/gpt-3.5-turbo'),
|
||||
'chatbot_llamacpp_base_url' => Setting::get('chatbot_llamacpp_base_url', 'http://172.17.0.1:8080'),
|
||||
'chatbot_llamacpp_model' => Setting::get('chatbot_llamacpp_model', ''),
|
||||
'chatbot_welcome_message' => Setting::get('chatbot_welcome_message', 'Olá! Como posso ajudar você hoje?'),
|
||||
'chatbot_custom_instructions' => Setting::get('chatbot_custom_instructions'),
|
||||
'chatbot_max_tokens' => Setting::get('chatbot_max_tokens', '500'),
|
||||
'chatbot_temperature' => Setting::get('chatbot_temperature', '0.7'),
|
||||
];
|
||||
|
||||
return view('chatbot::admin.settings', compact('settings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update chatbot settings.
|
||||
*/
|
||||
public function updateSettings(Request $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'chatbot_enabled' => 'nullable|in:on,off',
|
||||
'chatbot_provider' => 'required|in:openrouter,llamacpp',
|
||||
'chatbot_openrouter_api_key' => 'nullable|string|max:255',
|
||||
'chatbot_openrouter_model' => 'nullable|string|max:255',
|
||||
'chatbot_llamacpp_base_url' => 'nullable|string|max:255',
|
||||
'chatbot_llamacpp_model' => 'nullable|string|max:255',
|
||||
'chatbot_welcome_message' => 'required|string|max:500',
|
||||
'chatbot_custom_instructions' => 'nullable|string',
|
||||
'chatbot_max_tokens' => 'required|integer|min:50|max:2000',
|
||||
'chatbot_temperature' => 'required|numeric|min:0|max:2',
|
||||
]);
|
||||
|
||||
foreach ($validated as $key => $value) {
|
||||
if ($key === 'chatbot_enabled') {
|
||||
Setting::set($key, $value ?? 'off', 'chatbot');
|
||||
} else {
|
||||
Setting::set($key, $value, 'chatbot');
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure enabled is set properly when checkbox is not checked
|
||||
if (!$request->has('chatbot_enabled')) {
|
||||
Setting::set('chatbot_enabled', 'off', 'chatbot');
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.settings')
|
||||
->with('success', 'Configurações do chatbot atualizadas com sucesso!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the chatbot rules listing.
|
||||
*/
|
||||
public function rulesIndex(): \Illuminate\View\View
|
||||
{
|
||||
$rules = ChatbotRule::orderBy('order')
|
||||
->orderBy('id')
|
||||
->paginate(30);
|
||||
|
||||
return view('chatbot::admin.rules.index', compact('rules'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new chatbot rule.
|
||||
*/
|
||||
public function rulesCreate(): \Illuminate\View\View
|
||||
{
|
||||
return view('chatbot::admin.rules.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created chatbot rule.
|
||||
*/
|
||||
public function rulesStore(Request $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string|max:2000',
|
||||
'order' => 'nullable|integer|min:0|max:9999',
|
||||
]);
|
||||
|
||||
$validated['active'] = $request->has('active');
|
||||
$validated['order'] = $validated['order'] ?? 0;
|
||||
|
||||
ChatbotRule::create($validated);
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.rules.index')
|
||||
->with('success', 'Regra criada com sucesso! A IA passará a segui-la nas próximas conversas.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing a chatbot rule.
|
||||
*/
|
||||
public function rulesEdit(ChatbotRule $rule): \Illuminate\View\View
|
||||
{
|
||||
return view('chatbot::admin.rules.edit', compact('rule'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a chatbot rule.
|
||||
*/
|
||||
public function rulesUpdate(Request $request, ChatbotRule $rule): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string|max:2000',
|
||||
'order' => 'nullable|integer|min:0|max:9999',
|
||||
]);
|
||||
|
||||
$validated['active'] = $request->has('active');
|
||||
$validated['order'] = $validated['order'] ?? 0;
|
||||
|
||||
$rule->update($validated);
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.rules.index')
|
||||
->with('success', 'Regra atualizada com sucesso!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a chatbot rule.
|
||||
*/
|
||||
public function rulesDestroy(ChatbotRule $rule): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$rule->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.chatbot.rules.index')
|
||||
->with('success', 'Regra removida com sucesso.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Chatbot\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Modules\Chatbot\Models\ChatbotConversation;
|
||||
use App\Modules\Chatbot\Models\ChatbotMessage;
|
||||
use App\Modules\Chatbot\Notifications\ChatbotConversationNotification;
|
||||
use App\Modules\Chatbot\Services\OpenRouterService;
|
||||
use App\Modules\Chatbot\Services\LlamaCppService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ChatbotApiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Register visitor information before starting the chat.
|
||||
*/
|
||||
public function registerVisitor(Request $request): JsonResponse
|
||||
{
|
||||
// Check if chatbot is enabled
|
||||
if (Setting::get('chatbot_enabled', 'off') !== 'on') {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'O assistente virtual está indisponível no momento.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/',
|
||||
'name' => 'required|string|max:255',
|
||||
'phone' => 'required|string|max:30',
|
||||
'city' => 'required|string|max:255',
|
||||
], [
|
||||
'session_id.regex' => 'ID de sessão inválido.',
|
||||
'name.required' => 'Por favor, informe seu nome.',
|
||||
'phone.required' => 'Por favor, informe seu telefone.',
|
||||
'city.required' => 'Por favor, informe sua cidade.',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Preencha todos os campos obrigatórios.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$sessionId = $request->input('session_id');
|
||||
|
||||
// Get or create conversation and save visitor info
|
||||
$conversation = ChatbotConversation::getOrCreateBySession($sessionId);
|
||||
$conversation->update([
|
||||
'visitor_name' => trim($request->input('name')),
|
||||
'visitor_phone' => trim($request->input('phone')),
|
||||
'visitor_city' => trim($request->input('city')),
|
||||
]);
|
||||
|
||||
// Send notification to admins
|
||||
$adminTypes = \App\Models\UserType::where('is_admin', true)->pluck('name')->toArray();
|
||||
$admins = User::whereIn('user_type', $adminTypes)->get();
|
||||
|
||||
if ($admins->isNotEmpty()) {
|
||||
Notification::send($admins, new ChatbotConversationNotification($conversation));
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Dados registrados com sucesso.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming chat message from the public widget.
|
||||
*/
|
||||
public function sendMessage(Request $request): JsonResponse
|
||||
{
|
||||
// Check if chatbot is enabled
|
||||
if (Setting::get('chatbot_enabled', 'off') !== 'on') {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'O assistente virtual está indisponível no momento.',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'message' => 'required|string|max:1000',
|
||||
'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/',
|
||||
], [
|
||||
'session_id.regex' => 'ID de sessão inválido.',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Mensagem inválida.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$sessionId = $request->input('session_id');
|
||||
$userMessage = trim($request->input('message'));
|
||||
|
||||
// Get or create conversation
|
||||
$conversation = ChatbotConversation::getOrCreateBySession($sessionId);
|
||||
|
||||
// Check if conversation is ended
|
||||
if ($conversation->isEnded()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Esta conversa foi finalizada. Inicie uma nova conversa.',
|
||||
'conversation_ended' => true,
|
||||
], 403);
|
||||
}
|
||||
|
||||
// Save user message
|
||||
ChatbotMessage::create([
|
||||
'conversation_id' => $conversation->id,
|
||||
'role' => 'user',
|
||||
'content' => $userMessage,
|
||||
]);
|
||||
|
||||
// Get conversation context
|
||||
$context = $conversation->getContextForApi(20);
|
||||
|
||||
// Call the configured AI provider (OpenRouter or llama.cpp)
|
||||
$provider = Setting::get('chatbot_provider', 'openrouter');
|
||||
|
||||
if ($provider === 'llamacpp') {
|
||||
$aiService = new LlamaCppService();
|
||||
} else {
|
||||
$aiService = new OpenRouterService();
|
||||
}
|
||||
|
||||
$result = $aiService->chat($context, $conversation);
|
||||
|
||||
// Save assistant message with token usage
|
||||
ChatbotMessage::create([
|
||||
'conversation_id' => $conversation->id,
|
||||
'role' => 'assistant',
|
||||
'content' => $result['content'],
|
||||
'tokens_used' => $result['tokens_used'],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $result['content'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* End a conversation.
|
||||
*/
|
||||
public function endConversation(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/',
|
||||
], [
|
||||
'session_id.regex' => 'ID de sessão inválido.',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Sessão inválida.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$sessionId = $request->input('session_id');
|
||||
$conversation = ChatbotConversation::where('session_id', $sessionId)->first();
|
||||
|
||||
if (!$conversation) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Conversa não encontrada.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if ($conversation->isEnded()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Conversa já foi finalizada.',
|
||||
]);
|
||||
}
|
||||
|
||||
$conversation->update([
|
||||
'status' => 'ended',
|
||||
'ended_at' => now(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Conversa finalizada com sucesso.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a rating for a conversation.
|
||||
*/
|
||||
public function rateConversation(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'session_id' => 'required|string|max:255|regex:/^[a-zA-Z0-9\-_]+$/',
|
||||
'rating' => 'required|integer|min:1|max:5',
|
||||
'comment' => 'nullable|string|max:500',
|
||||
], [
|
||||
'session_id.regex' => 'ID de sessão inválido.',
|
||||
'rating.required' => 'Por favor, selecione uma avaliação.',
|
||||
'rating.min' => 'A avaliação mínima é 1 estrela.',
|
||||
'rating.max' => 'A avaliação máxima é 5 estrelas.',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Avaliação inválida.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$sessionId = $request->input('session_id');
|
||||
$conversation = ChatbotConversation::where('session_id', $sessionId)->first();
|
||||
|
||||
if (!$conversation) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Conversa não encontrada.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$conversation->update([
|
||||
'rating' => $request->input('rating'),
|
||||
'rating_comment' => $request->input('comment'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Avaliação registrada. Obrigado pelo feedback!',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial welcome message and chatbot status.
|
||||
*/
|
||||
public function getStatus(): JsonResponse
|
||||
{
|
||||
$enabled = Setting::get('chatbot_enabled', 'off') === 'on';
|
||||
$welcomeMessage = Setting::get('chatbot_welcome_message', 'Olá! Como posso ajudar você hoje?');
|
||||
|
||||
return response()->json([
|
||||
'enabled' => $enabled,
|
||||
'welcome_message' => $welcomeMessage,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user