First Commit
This commit is contained in:
Executable
+146
@@ -0,0 +1,146 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Chat with AI'))
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Chat with AI') }}
|
||||
</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<x-language-selector variant="text-gray-600 hover:text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="py-6">
|
||||
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Chat Container -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 flex flex-col" style="height: calc(100vh - 250px); min-height: 500px;">
|
||||
<!-- Chat Header -->
|
||||
<div class="p-4 border-b border-gray-100 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Chat with AI') }}</h3>
|
||||
<p class="text-sm text-gray-500">{{ __('Ask anything to the AI models') }}</p>
|
||||
</div>
|
||||
<form action="{{ route('chat.destroy') }}" method="POST" onsubmit="return confirm('{{ __("Clear chat history?") }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-sm text-red-600 hover:text-red-800">
|
||||
{{ __('Clear History') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Warning Banner -->
|
||||
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-yellow-700">
|
||||
{{ __('Do not share sensitive personal data, use profanity, or discuss prohibited topics such as pornography, politics, religion, or prejudice. Violations may result in account restrictions.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Messages -->
|
||||
<div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
@forelse($chatHistory as $chat)
|
||||
<div class="flex flex-col space-y-2">
|
||||
<!-- User Message -->
|
||||
<div class="flex justify-end">
|
||||
<div class="bg-indigo-600 text-white rounded-lg px-4 py-2 max-w-[80%]">
|
||||
<p class="text-sm whitespace-pre-wrap">{{ $chat->message }}</p>
|
||||
<p class="text-xs text-indigo-200 mt-1">{{ $chat->created_at->format('H:i') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- AI Response -->
|
||||
@if($chat->response)
|
||||
<div class="flex justify-start">
|
||||
<div class="bg-gray-100 text-gray-800 rounded-lg px-4 py-2 max-w-[80%]">
|
||||
<div class="text-sm prose prose-sm max-w-none">
|
||||
{!! \App\Services\MarkdownService::parse($chat->response) !!}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<p class="text-xs text-gray-400">{{ $chat->created_at->format('H:i') }}</p>
|
||||
<span class="text-xs text-gray-400">|</span>
|
||||
<span class="text-xs text-gray-400">{{ ucfirst($chat->provider) }}</span>
|
||||
<span class="text-xs text-gray-400">|</span>
|
||||
<span class="text-xs text-gray-400">{{ $chat->model }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@empty
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
<p class="text-gray-500 text-lg">{{ __('Start a conversation with AI!') }}</p>
|
||||
<p class="text-gray-400 text-sm mt-2">{{ __('Type a message below to begin.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="p-4 border-t border-gray-100">
|
||||
<form action="{{ route('chat.store') }}" method="POST" class="flex gap-2">
|
||||
@csrf
|
||||
<div class="flex-1 flex gap-2">
|
||||
<input type="text"
|
||||
name="message"
|
||||
placeholder="{{ __('Type your message...') }}"
|
||||
class="flex-1 text-sm border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
autocomplete="off"
|
||||
required>
|
||||
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
||||
{{ __('Send') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
// Auto-scroll to bottom of chat
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
// Focus input on load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const input = document.querySelector('input[name="message"]');
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Executable
+397
@@ -0,0 +1,397 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('Dashboard'))
|
||||
|
||||
@section('header')
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }}
|
||||
</h2>
|
||||
<div class="flex items-center gap-4">
|
||||
<x-language-selector variant="text-gray-600 hover:text-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="py-6">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<!-- Welcome Message -->
|
||||
<div class="bg-gradient-to-r from-indigo-500 to-purple-600 rounded-xl shadow-lg p-6 mb-6 text-white">
|
||||
<h1 class="text-2xl font-bold">{{ __('Welcome back, :name!', ['name' => Auth::user()->name]) }}</h1>
|
||||
<p class="mt-2 text-indigo-100">{{ __('Here\'s your learning overview for today.') }}</p>
|
||||
</div>
|
||||
|
||||
@if(session('success'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(session('error'))
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(!Auth::user()->is_approved)
|
||||
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded-lg mb-6">
|
||||
{{ __('Your account is pending approval. You will be able to access all features once approved.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Active Subjects') }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ $activeSubjectsCount }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">{{ __('Total: :count subjects', ['count' => $subjectsCount]) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Total Messages') }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ number_format($insightsCount) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">{{ __('AI-powered learning messages') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Token Usage') }}</p>
|
||||
<p class="text-3xl font-bold text-gray-900 mt-1">{{ number_format($totalTokens) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
{{ __('Input: :input | Output: :output', [
|
||||
'input' => number_format($totalInputTokens),
|
||||
'output' => number_format($totalOutputTokens),
|
||||
]) }}
|
||||
</p>
|
||||
@php $tokenLimit = Auth::user()->token_limit; @endphp
|
||||
@if($tokenLimit)
|
||||
<div class="mt-2">
|
||||
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{{ __('Limit: :limit', ['limit' => number_format($tokenLimit)]) }}</span>
|
||||
<span>{{ round(($totalTokens / $tokenLimit) * 100) }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div class="h-2 rounded-full {{ ($totalTokens / $tokenLimit) > 0.9 ? 'bg-red-500' : (($totalTokens / $tokenLimit) > 0.7 ? 'bg-yellow-500' : 'bg-blue-500') }}"
|
||||
style="width: {{ min(100, ($totalTokens / $tokenLimit) * 100) }}%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<p class="text-xs text-gray-400 mt-2">{{ __('Unlimited') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500">{{ __('Quick Actions') }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 space-y-1">
|
||||
<a href="{{ route('admin.subjects.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
||||
{{ __('Manage Subjects') }} →
|
||||
</a>
|
||||
<a href="{{ route('admin.insights.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
||||
{{ __('View Insights') }} →
|
||||
</a>
|
||||
<a href="{{ route('admin.telegram.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800 block">
|
||||
{{ __('Telegram Settings') }} →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Row -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<!-- Token Usage Chart -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Token Usage (Last 30 Days)') }}</h3>
|
||||
<div id="tokenChart" style="min-height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Messages per Subject Chart -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Messages per Subject') }}</h3>
|
||||
<div id="subjectChart" style="min-height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weekly Activity Chart -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-4">{{ __('Weekly Activity') }}</h3>
|
||||
<div id="weeklyChart" style="min-height: 200px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Active Subjects and Recent Insights -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<!-- Active Subjects -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Active Subjects') }}</h3>
|
||||
<a href="{{ route('admin.subjects.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800">
|
||||
{{ __('Manage') }} →
|
||||
</a>
|
||||
</div>
|
||||
@forelse($activeSubjects as $userSubject)
|
||||
<div class="flex items-center justify-between py-3 border-b border-gray-100 last:border-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-2 h-2 rounded-full {{ $userSubject->is_active ? 'bg-green-500' : 'bg-gray-300' }}"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ $userSubject->subject->name }}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ ucfirst($userSubject->frequency) }} |
|
||||
{{ $userSubject->formatted_times }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('admin.subjects.toggle', $userSubject) }}" method="POST">
|
||||
@csrf
|
||||
<input type="hidden" name="is_active" value="{{ $userSubject->is_active ? '0' : '1' }}">
|
||||
<button type="submit" class="text-xs {{ $userSubject->is_active ? 'text-red-600 hover:text-red-800' : 'text-green-600 hover:text-green-800' }}">
|
||||
{{ $userSubject->is_active ? __('Deactivate') : __('Activate') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-gray-500 text-sm">{{ __('No active subjects. Subscribe to subjects to start learning!') }}</p>
|
||||
<a href="{{ route('admin.subjects.index') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
|
||||
{{ __('Browse Subjects') }} →
|
||||
</a>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Recent Messages -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 border border-gray-100">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">{{ __('Recent Messages') }}</h3>
|
||||
<a href="{{ route('admin.insights.index') }}" class="text-sm text-indigo-600 hover:text-indigo-800">
|
||||
{{ __('View All') }} →
|
||||
</a>
|
||||
</div>
|
||||
<div class="space-y-4 max-h-96 overflow-y-auto">
|
||||
@forelse($recentInsights as $insight)
|
||||
<div class="border-b border-gray-100 pb-3 last:border-0">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">
|
||||
{{ $insight->subject->name }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">
|
||||
{{ $insight->type->label() }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">
|
||||
{{ $insight->created_at->diffForHumans() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-700 line-clamp-2 prose prose-sm max-w-none">
|
||||
{!! \App\Services\MarkdownService::parse($insight->content) !!}
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ route('admin.insights.translate', $insight) }}" method="POST" class="flex items-center gap-1 ml-2">
|
||||
@csrf
|
||||
<select name="target_language" class="text-xs border border-gray-300 rounded-lg px-2 py-1 focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="pt_BR" {{ app()->getLocale() === 'pt_BR' ? 'selected' : '' }}>PT</option>
|
||||
<option value="en" {{ app()->getLocale() === 'en' ? 'selected' : '' }}>EN</option>
|
||||
<option value="es" {{ app()->getLocale() === 'es' ? 'selected' : '' }}>ES</option>
|
||||
</select>
|
||||
<button type="submit" class="text-xs text-indigo-500 hover:text-indigo-700 font-medium" title="{{ __('Translate') }}">
|
||||
{{ __('Translate') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-gray-500 text-sm">{{ __('No messages yet. Generate your first message!') }}</p>
|
||||
<a href="{{ route('admin.insights.index') }}" class="text-indigo-600 hover:text-indigo-800 text-sm mt-2 inline-block">
|
||||
{{ __('Generate Message') }} →
|
||||
</a>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Token Usage Chart
|
||||
var tokenOptions = {
|
||||
series: [
|
||||
{
|
||||
name: '{{ __("Input Tokens") }}',
|
||||
data: @json($tokenChartData['input'] ?? [])
|
||||
},
|
||||
{
|
||||
name: '{{ __("Output Tokens") }}',
|
||||
data: @json($tokenChartData['output'] ?? [])
|
||||
}
|
||||
],
|
||||
chart: {
|
||||
type: 'area',
|
||||
height: 300,
|
||||
toolbar: { show: false },
|
||||
fontFamily: 'Figtree, sans-serif',
|
||||
foreColor: '#6B7280'
|
||||
},
|
||||
colors: ['#6366F1', '#8B5CF6'],
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { curve: 'smooth', width: 2 },
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
shadeIntensity: 1,
|
||||
opacityFrom: 0.3,
|
||||
opacityTo: 0.1
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: @json($tokenChartData['labels'] ?? []),
|
||||
labels: { style: { fontSize: '11px' } }
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function(val) { return val.toLocaleString(); }
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) { return val.toLocaleString() + ' tokens'; }
|
||||
}
|
||||
},
|
||||
legend: { position: 'top' },
|
||||
grid: { borderColor: '#f1f5f9' }
|
||||
};
|
||||
|
||||
var tokenChart = new ApexCharts(document.querySelector("#tokenChart"), tokenOptions);
|
||||
tokenChart.render();
|
||||
|
||||
// Subject Chart (Donut)
|
||||
var subjectOptions = {
|
||||
series: @json($subjectChartData['values'] ?? []),
|
||||
chart: {
|
||||
type: 'donut',
|
||||
height: 300,
|
||||
fontFamily: 'Figtree, sans-serif',
|
||||
foreColor: '#6B7280'
|
||||
},
|
||||
labels: @json($subjectChartData['labels'] ?? []),
|
||||
colors: ['#6366F1', '#8B5CF6', '#EC4899', '#F59E0B', '#10B981', '#3B82F6', '#EF4444'],
|
||||
dataLabels: { enabled: false },
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
fontSize: '12px'
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
donut: {
|
||||
size: '65%',
|
||||
labels: {
|
||||
show: true,
|
||||
total: {
|
||||
show: true,
|
||||
label: '{{ __("Total") }}',
|
||||
formatter: function(w) {
|
||||
return w.globals.seriesTotals.reduce((a, b) => a + b, 0).toLocaleString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) { return val.toLocaleString() + ' messages'; }
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: { height: 250 },
|
||||
legend: { position: 'bottom' }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
var subjectChart = new ApexCharts(document.querySelector("#subjectChart"), subjectOptions);
|
||||
subjectChart.render();
|
||||
|
||||
// Weekly Activity Chart (Bar)
|
||||
var weeklyOptions = {
|
||||
series: [{
|
||||
name: '{{ __("Messages") }}',
|
||||
data: @json($weeklyActivity['values'] ?? [])
|
||||
}],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 200,
|
||||
toolbar: { show: false },
|
||||
fontFamily: 'Figtree, sans-serif',
|
||||
foreColor: '#6B7280'
|
||||
},
|
||||
colors: ['#6366F1'],
|
||||
plotOptions: {
|
||||
bar: {
|
||||
borderRadius: 4,
|
||||
columnWidth: '60%',
|
||||
distributed: true
|
||||
}
|
||||
},
|
||||
dataLabels: { enabled: false },
|
||||
xaxis: {
|
||||
categories: @json($weeklyActivity['labels'] ?? []),
|
||||
labels: { style: { fontSize: '12px' } }
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function(val) { return Math.floor(val).toLocaleString(); }
|
||||
}
|
||||
},
|
||||
grid: { borderColor: '#f1f5f9' },
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function(val) { return Math.floor(val).toLocaleString() + ' messages'; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var weeklyChart = new ApexCharts(document.querySelector("#weeklyChart"), weeklyOptions);
|
||||
weeklyChart.render();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user