ailixir-api / app /Http /Controllers /Api /ChemistryController.php
AILIXIR Bot
Auto-sync: 201d269f287526269b8ffded5f450ff36b46c740
907b200
Raw
History Blame Contribute Delete
16.7 kB
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\ChemistryChatRequest;
use App\Http\Requests\ChemistryCompareRequest;
use App\Http\Requests\ChemistryDockingRequest;
use App\Http\Resources\ChemistryResponseResource;
use App\Models\ChemistryAnalysis;
use App\Models\ChemistryCsvJob;
use App\Models\ChemistryThread;
use App\Services\ChemistryApiService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Auth;
class ChemistryController extends BaseController
{
public function __construct(
protected ChemistryApiService $chemistryService
) {}
// ==================== Health ====================
public function health(): JsonResponse
{
$result = $this->chemistryService->healthCheck();
return response()->json(new ChemistryResponseResource($result));
}
// ==================== Threads ====================
public function createThread(): JsonResponse
{
$user = Auth::user();
$result = $this->chemistryService->createThread();
if (!$result['success']) {
return response()->json(new ChemistryResponseResource($result), 500);
}
$thread = ChemistryThread::create([
'user_id' => $user->id,
'thread_id' => $result['data']['thread_id'],
'title' => 'New Conversation',
'status' => 'active',
'last_used_at' => now(),
]);
return response()->json([
'success' => true,
'data' => [
'thread_id' => $thread->thread_id,
'id' => $thread->id,
'created_at' => $thread->created_at,
],
]);
}
public function listThreads(): JsonResponse
{
$threads = Auth::user()->chemistryThreads()
->where('status', 'active')
->orderBy('last_used_at', 'desc')
->get(['id', 'thread_id', 'title', 'last_used_at', 'created_at']);
return response()->json([
'success' => true,
'data' => $threads,
]);
}
// ==================== Chat (معدل: title من أول سؤال) ====================
public function chat(ChemistryChatRequest $request): JsonResponse
{
$user = Auth::user();
$threadId = $request->input('thread_id');
if ($threadId) {
$thread = ChemistryThread::where('user_id', $user->id)
->where('thread_id', $threadId)
->first();
if (!$thread) {
return response()->json([
'success' => false,
'error' => 'Thread not found or access denied',
], 403);
}
// ← لو الـ title لسه "New Conversation"، غيريه لأول سؤال
if ($thread->title === 'New Conversation') {
$message = $request->input('message');
$newTitle = mb_strlen($message) > 50
? mb_substr($message, 0, 50) . '...'
: $message;
$thread->update([
'title' => $newTitle,
'last_used_at' => now(),
]);
} else {
$thread->update(['last_used_at' => now()]);
}
}
$result = $this->chemistryService->chat(
$request->input('message'),
$threadId
);
if (!$result['success']) {
return response()->json(new ChemistryResponseResource($result), $result['status']);
}
ChemistryAnalysis::create([
'user_id' => $user->id,
'chemistry_thread_id' => $thread?->id,
'type' => 'chat',
'input_data' => $request->input('message'),
'response' => $result['data']['reply'] ?? null,
'processing_time_ms' => $result['data']['processing_time_ms'] ?? null,
'status' => 'success',
]);
return response()->json(new ChemistryResponseResource($result));
}
// ==================== Analyze SMILES ====================
public function analyzeSmiles(Request $request): JsonResponse
{
$user = Auth::user();
$request->validate([
'smiles' => 'required|string|max:1000',
'thread_id' => 'nullable|string|max:255',
]);
$threadId = $request->input('thread_id');
$thread = $this->validateThread($user->id, $threadId);
$smiles = $request->input('smiles');
$result = $this->chemistryService->analyzeSmiles($smiles, $threadId);
return $this->saveAndRespond($result, $user->id, 'smiles', $smiles, $thread?->id);
}
// ==================== Compare ====================
public function compare(ChemistryCompareRequest $request): JsonResponse
{
$user = Auth::user();
$threadId = $request->input('thread_id');
$thread = $this->validateThread($user->id, $threadId);
$result = $this->chemistryService->compareMolecules(
$request->input('smiles'),
$threadId
);
$input = implode(', ', $request->input('smiles'));
return $this->saveAndRespond($result, $user->id, 'compare', $input, $thread?->id);
}
// ==================== Docking ====================
public function docking(ChemistryDockingRequest $request): JsonResponse
{
$user = Auth::user();
$threadId = $request->input('thread_id');
$thread = $this->validateThread($user->id, $threadId);
$result = $this->chemistryService->analyzeDocking(
$request->input('docking_data'),
$threadId
);
return $this->saveAndRespond($result, $user->id, 'docking', $request->input('docking_data'), $thread?->id);
}
// ==================== CSV (معدل: required thread_id) ====================
public function uploadCsv(Request $request): JsonResponse
{
$user = Auth::user();
$request->validate([
'file' => 'required|file|mimes:csv,txt|max:2048',
'analysis_type' => 'nullable|in:full,quick,admet,classify',
'thread_id' => 'required|string|max:255',
]);
$file = $request->file('file');
$analysisType = $request->input('analysis_type', 'full');
$threadId = $request->input('thread_id');
// Validate thread belongs to user
$thread = ChemistryThread::where('user_id', $user->id)
->where('thread_id', $threadId)
->first();
if (!$thread) {
return response()->json([
'success' => false,
'error' => 'Thread not found or access denied',
], 403);
}
$result = $this->chemistryService->uploadCsv($file, $analysisType);
if (!$result['success']) {
return response()->json(new ChemistryResponseResource($result), $result['status']);
}
$apiJobId = $result['data']['job_id'] ?? null;
$totalRows = $result['data']['total_rows'] ?? 0;
if (!$apiJobId) {
return response()->json([
'success' => false,
'error' => 'Failed to get job ID from API',
], 500);
}
$job = ChemistryCsvJob::create([
'user_id' => $user->id,
'chemistry_thread_id' => $thread->id,
'job_id' => $apiJobId,
'filename' => $file->getClientOriginalName(),
'analysis_type' => $analysisType,
'total_rows' => $totalRows,
'status' => 'queued',
]);
// Update thread last_used_at
$thread->update(['last_used_at' => now()]);
return response()->json([
'success' => true,
'data' => [
'job_id' => $job->job_id,
'id' => $job->id,
'status' => $job->status,
'total_rows' => $job->total_rows,
],
]);
}
public function csvStatus(string $jobId): JsonResponse
{
$user = Auth::user();
$job = ChemistryCsvJob::where('user_id', $user->id)
->where('job_id', $jobId)
->first();
if (!$job) {
return response()->json([
'success' => false,
'error' => 'Job not found or access denied',
], 404);
}
$result = $this->chemistryService->getCsvStatus($jobId);
if ($result['success'] && isset($result['data'])) {
$apiData = $result['data'];
$job->update([
'status' => $apiData['status'] ?? $job->status,
'completed_rows' => $apiData['completed'] ?? $job->completed_rows,
'failed_rows' => $apiData['failed_rows'] ?? $job->failed_rows,
'progress_percent' => $apiData['progress_percent'] ?? $job->progress_percent,
]);
if ($apiData['status'] === 'done' && !$job->completed_at) {
$job->update(['completed_at' => now(), 'status' => 'done']);
}
}
return response()->json([
'success' => true,
'data' => [
'job_id' => $job->job_id,
'status' => $job->status,
'total' => $job->total_rows,
'completed' => $job->completed_rows,
'failed' => $job->failed_rows,
'progress_percent' => $job->progress_percent,
],
]);
}
public function csvResults(string $jobId)
{
$user = Auth::user();
$job = ChemistryCsvJob::where('user_id', $user->id)
->where('job_id', $jobId)
->first();
if (!$job) {
return response()->json([
'success' => false,
'error' => 'Job not found or access denied',
], 404);
}
if ($job->status !== 'done') {
return response()->json([
'success' => false,
'error' => 'Job not completed yet',
], 400);
}
// ← Return content from DB if available
if ($job->result_content) {
return response($job->result_content)
->header('Content-Type', 'text/csv')
->header('Content-Disposition', 'attachment; filename="results_' . $jobId . '.csv"');
}
$result = $this->chemistryService->getCsvResults($jobId);
if (is_string($result)) {
// ← Store content in DB instead of file path
$job->update([
'result_content' => $result,
'result_file_path' => null,
]);
return response($result)
->header('Content-Type', 'text/csv')
->header('Content-Disposition', 'attachment; filename="results_' . $jobId . '.csv"');
}
return response()->json(new ChemistryResponseResource($result));
}
public function listJobs(Request $request): JsonResponse
{
$user = Auth::user();
$query = $user->chemistryCsvJobs()
->orderBy('created_at', 'desc');
// Optional: filter by thread_id
if ($request->has('thread_id')) {
$thread = ChemistryThread::where('user_id', $user->id)
->where('thread_id', $request->input('thread_id'))
->first();
if ($thread) {
$query->where('chemistry_thread_id', $thread->id);
}
}
$jobs = $query->get([
'id',
'job_id',
'chemistry_thread_id',
'filename',
'analysis_type',
'status',
'total_rows',
'completed_rows',
'failed_rows',
'progress_percent',
'created_at'
]);
return response()->json([
'success' => true,
'data' => $jobs,
]);
}
public function deleteJob(string $jobId): JsonResponse
{
$user = Auth::user();
$job = ChemistryCsvJob::where('user_id', $user->id)
->where('job_id', $jobId)
->first();
if (!$job) {
return response()->json([
'success' => false,
'error' => 'Job not found or access denied',
], 404);
}
$result = $this->chemistryService->deleteCsvJob($jobId);
if ($job->result_file_path && Storage::exists($job->result_file_path)) {
Storage::delete($job->result_file_path);
}
$job->delete();
return response()->json([
'success' => true,
'message' => 'Job deleted successfully',
]);
}
// ==================== User History ====================
public function userHistory(Request $request): JsonResponse
{
$user = Auth::user();
$type = $request->input('type');
$query = $user->chemistryAnalyses()
->with('thread:id,thread_id,title')
->orderBy('created_at', 'desc');
if ($type) {
$query->where('type', $type);
}
$history = $query->paginate(20);
return response()->json([
'success' => true,
'data' => $history,
]);
}
// ==================== Thread Messages (جديد) ====================
public function threadMessages(Request $request): JsonResponse
{
$user = Auth::user();
$request->validate([
'thread_id' => 'required|string|max:255',
]);
$threadId = $request->input('thread_id');
$thread = ChemistryThread::where('user_id', $user->id)
->where('thread_id', $threadId)
->first();
if (!$thread) {
return response()->json([
'success' => false,
'error' => 'Thread not found or access denied',
], 403);
}
// Chat/analysis messages
$messages = ChemistryAnalysis::where('user_id', $user->id)
->where('chemistry_thread_id', $thread->id)
->orderBy('created_at', 'asc')
->get(['id', 'type', 'input_data', 'response', 'status', 'created_at']);
// CSV jobs related to this thread
$csvJobs = ChemistryCsvJob::where('user_id', $user->id)
->where('chemistry_thread_id', $thread->id)
->orderBy('created_at', 'asc')
->get([
'id',
'job_id',
'filename',
'analysis_type',
'status',
'total_rows',
'completed_rows',
'failed_rows',
'progress_percent',
'result_content',
'created_at',
'completed_at'
]);
return response()->json([
'success' => true,
'data' => [
'thread_id' => $thread->thread_id,
'title' => $thread->title,
'messages' => $messages,
'csv_jobs' => $csvJobs,
],
]);
}
// ==================== Helper Methods ====================
private function validateThread(int $userId, ?string $threadId): ?ChemistryThread
{
if (!$threadId) return null;
return ChemistryThread::where('user_id', $userId)
->where('thread_id', $threadId)
->first();
}
private function saveAndRespond(array $result, int $userId, string $type, string $input, ?int $threadId = null): JsonResponse
{
if (!$result['success']) {
ChemistryAnalysis::create([
'user_id' => $userId,
'chemistry_thread_id' => $threadId,
'type' => $type,
'input_data' => $input,
'status' => 'failed',
'error_message' => is_string($result['error']) ? $result['error'] : json_encode($result['error']),
]);
return response()->json(new ChemistryResponseResource($result), $result['status']);
}
ChemistryAnalysis::create([
'user_id' => $userId,
'chemistry_thread_id' => $threadId,
'type' => $type,
'input_data' => $input,
'response' => $result['data']['reply'] ?? null,
'processing_time_ms' => $result['data']['processing_time_ms'] ?? null,
'status' => 'success',
]);
return response()->json(new ChemistryResponseResource($result));
}
}