First Commit
This commit is contained in:
+260
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Enums\MessageType;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Insight;
|
||||
use App\Models\Subject;
|
||||
use App\Models\TokenUsage;
|
||||
use App\Services\AIService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* Controller for managing AI-generated insights.
|
||||
*/
|
||||
class InsightController extends Controller
|
||||
{
|
||||
/**
|
||||
* The AI service instance.
|
||||
*/
|
||||
protected AIService $ai;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct(AIService $ai)
|
||||
{
|
||||
$this->ai = $ai;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of insights for the authenticated user.
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
$subjectId = $request->input('subject_id');
|
||||
|
||||
/** @var \Illuminate\Pagination\LengthAwarePaginator $insights */
|
||||
$insightsQuery = Insight::where('user_id', $user->id)
|
||||
->with('subject');
|
||||
|
||||
if ($subjectId) {
|
||||
$insightsQuery->where('subject_id', (int) $subjectId);
|
||||
}
|
||||
|
||||
$insights = $insightsQuery->orderByDesc('created_at')
|
||||
->paginate(20);
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<int, Subject> $subjects */
|
||||
$subjects = Subject::where('is_active', true)->get();
|
||||
|
||||
// Token usage statistics
|
||||
$totalInputTokens = TokenUsage::where('user_id', $user->id)->sum('input_tokens');
|
||||
$totalOutputTokens = TokenUsage::where('user_id', $user->id)->sum('output_tokens');
|
||||
$totalTokens = $totalInputTokens + $totalOutputTokens;
|
||||
|
||||
return view('admin.insights.index', compact(
|
||||
'insights',
|
||||
'subjects',
|
||||
'subjectId',
|
||||
'totalInputTokens',
|
||||
'totalOutputTokens',
|
||||
'totalTokens'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new AI insight for the specified subject.
|
||||
*/
|
||||
public function generate(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'subject_id' => ['required', 'exists:subjects,id'],
|
||||
'custom_prompt' => ['nullable', 'string', 'max:500'],
|
||||
'message_type' => ['nullable', 'string', 'max:50'],
|
||||
'language' => ['nullable', 'string', 'in:pt_BR,en,es'],
|
||||
]);
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
/** @var Subject|null $subject */
|
||||
$subject = Subject::findOrFail((int) $validated['subject_id']);
|
||||
|
||||
$messageType = MessageType::tryFrom($validated['message_type'] ?? '') ?? MessageType::AiGenerated;
|
||||
|
||||
$language = $validated['language']
|
||||
?? app()->getLocale()
|
||||
?? 'en';
|
||||
|
||||
// Build the prompt with subject context
|
||||
$prompt = $validated['custom_prompt']
|
||||
?: $this->buildDefaultPrompt($subject, $messageType);
|
||||
|
||||
$context = ['language' => $language];
|
||||
|
||||
if ($subject->introduction) {
|
||||
$context['introduction'] = $subject->introduction;
|
||||
$prompt = "Context about {$subject->name}: {$subject->introduction}\n\n{$prompt}";
|
||||
}
|
||||
|
||||
// Generate message via AI based on type
|
||||
$result = $this->ai->generateInsight($subject->name, $prompt, $context, $messageType);
|
||||
|
||||
if (!$result['success']) {
|
||||
return back()->with('error', __('Failed to generate message: :error', [
|
||||
'error' => $result['error'] ?? __('Unknown error'),
|
||||
]));
|
||||
}
|
||||
|
||||
// Store the generated message
|
||||
Insight::create([
|
||||
'user_id' => $user->id,
|
||||
'subject_id' => $subject->id,
|
||||
'content' => $result['content'],
|
||||
'type' => $messageType->value,
|
||||
'input_tokens' => $result['input_tokens'],
|
||||
'output_tokens' => $result['output_tokens'],
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
|
||||
// Track token usage
|
||||
TokenUsage::create([
|
||||
'user_id' => $user->id,
|
||||
'input_tokens' => $result['input_tokens'],
|
||||
'output_tokens' => $result['output_tokens'],
|
||||
'total_tokens' => $result['input_tokens'] + $result['output_tokens'],
|
||||
'model' => config('services.openrouter.model', 'google/gemini-2.0-flash-exp:free'),
|
||||
'provider' => 'openrouter',
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Message generated successfully!'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a default prompt based on the message type.
|
||||
*/
|
||||
private function buildDefaultPrompt(Subject $subject, MessageType $messageType): string
|
||||
{
|
||||
return match ($messageType) {
|
||||
MessageType::LanguageLesson => __('Teach me a practical language lesson about :subject with examples, practice sentences, pronunciation, grammar, and vocabulary.', ['subject' => $subject->name]),
|
||||
MessageType::GameSimulator => __('Give me a strategic tip for :subject with actionable strategies, commands, and practical steps.', ['subject' => $subject->name]),
|
||||
MessageType::TechnicalTeaching => __('Give me a technical teaching lesson about :subject covering components, techniques, repairs, schematics, and practical exercises.', ['subject' => $subject->name]),
|
||||
default => __('Generate an insightful learning tip about :subject. Include practical advice and key concepts.', ['subject' => $subject->name]),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a manually created insight.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'subject_id' => ['required', 'exists:subjects,id'],
|
||||
'content' => ['required', 'string', 'max:5000'],
|
||||
]);
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
Insight::create([
|
||||
'user_id' => $user->id,
|
||||
'subject_id' => (int) $validated['subject_id'],
|
||||
'content' => $validated['content'],
|
||||
'type' => 'manual',
|
||||
'input_tokens' => 0,
|
||||
'output_tokens' => 0,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Insight created successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified insight.
|
||||
*/
|
||||
public function destroy(Insight $insight): RedirectResponse
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
if ($insight->user_id !== $user->id && !$user->hasRole('superadmin')) {
|
||||
return back()->with('error', __('You do not have permission to delete this insight.'));
|
||||
}
|
||||
|
||||
$insight->delete();
|
||||
|
||||
return back()->with('success', __('Insight deleted successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate an insight to the user's preferred language.
|
||||
*/
|
||||
public function translate(Request $request, Insight $insight): RedirectResponse
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
if ($insight->user_id !== $user->id && !$user->hasRole('superadmin')) {
|
||||
return back()->with('error', __('You do not have permission to translate this insight.'));
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'target_language' => ['required', 'string', 'max:10'],
|
||||
]);
|
||||
|
||||
$targetLanguage = $validated['target_language'];
|
||||
|
||||
// Get user's language preference for this subject
|
||||
$userSubject = \App\Models\UserSubject::where('user_id', $insight->user_id)
|
||||
->where('subject_id', $insight->subject_id)
|
||||
->first();
|
||||
|
||||
$language = $userSubject->insight_language ?? $user->locale ?? 'en';
|
||||
|
||||
// Don't translate if already in the target language
|
||||
if ($language === $targetLanguage) {
|
||||
return back()->with('info', __('Insight is already in the selected language.'));
|
||||
}
|
||||
|
||||
$result = $this->ai->translate($insight->content, $targetLanguage, $insight->subject->name);
|
||||
|
||||
if (!$result['success']) {
|
||||
return back()->with('error', __('Failed to translate message: :error', [
|
||||
'error' => $result['error'] ?? __('Unknown error'),
|
||||
]));
|
||||
}
|
||||
|
||||
// Create a new insight with translated content
|
||||
Insight::create([
|
||||
'user_id' => $insight->user_id,
|
||||
'subject_id' => $insight->subject_id,
|
||||
'content' => $result['content'],
|
||||
'type' => $insight->type->value,
|
||||
'input_tokens' => $result['input_tokens'],
|
||||
'output_tokens' => $result['output_tokens'],
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
|
||||
// Track token usage
|
||||
TokenUsage::create([
|
||||
'user_id' => $insight->user_id,
|
||||
'input_tokens' => $result['input_tokens'],
|
||||
'output_tokens' => $result['output_tokens'],
|
||||
'total_tokens' => $result['input_tokens'] + $result['output_tokens'],
|
||||
'model' => config('services.openrouter.model', 'google/gemini-2.0-flash-exp:free'),
|
||||
'provider' => 'openrouter',
|
||||
]);
|
||||
|
||||
return back()->with('success', __('Message translated successfully!'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user