You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
8.1 KiB
280 lines
8.1 KiB
<?php |
|
|
|
namespace App\Services; |
|
|
|
use Illuminate\Support\Facades\DB; |
|
use Illuminate\Support\Str; |
|
use RuntimeException; |
|
use Throwable; |
|
|
|
class ProjectSearchChatService |
|
{ |
|
private const SESSION_KEY = 'project_search_chat_session_uuid'; |
|
|
|
public function isAvailable(): bool |
|
{ |
|
try { |
|
return DB::connection('project_search')->getSchemaBuilder()->hasTable('project_search_chat_sessions'); |
|
} catch (Throwable) { |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* @return array<string, mixed>|null |
|
*/ |
|
public function activeSession(string $username): ?array |
|
{ |
|
$uuid = session(self::SESSION_KEY); |
|
if (! is_string($uuid) || $uuid === '') { |
|
return null; |
|
} |
|
|
|
$row = DB::connection('project_search') |
|
->table('project_search_chat_sessions') |
|
->where('session_uuid', $uuid) |
|
->where('username', $username) |
|
->where('status', 'active') |
|
->first(); |
|
|
|
return $row ? $this->mapSession($row) : null; |
|
} |
|
|
|
/** |
|
* @param array<int, string> $sources |
|
* @return array<string, mixed> |
|
*/ |
|
public function getOrCreateActiveSession(string $username, array $sources = []): array |
|
{ |
|
if (! $this->isAvailable()) { |
|
throw new RuntimeException('Tabel chat project search belum tersedia.'); |
|
} |
|
|
|
$existing = $this->activeSession($username); |
|
if ($existing !== null) { |
|
return $existing; |
|
} |
|
|
|
$uuid = (string) Str::uuid(); |
|
|
|
$id = DB::connection('project_search') |
|
->table('project_search_chat_sessions') |
|
->insertGetId([ |
|
'session_uuid' => $uuid, |
|
'username' => $username, |
|
'status' => 'active', |
|
'sources' => json_encode($sources), |
|
'started_at' => now(), |
|
'message_count' => 0, |
|
]); |
|
|
|
session([self::SESSION_KEY => $uuid]); |
|
|
|
return [ |
|
'id' => (int) $id, |
|
'session_uuid' => $uuid, |
|
'username' => $username, |
|
'status' => 'active', |
|
'sources' => $sources, |
|
'started_at' => now()->toIso8601String(), |
|
'ended_at' => null, |
|
'message_count' => 0, |
|
]; |
|
} |
|
|
|
/** |
|
* @return array<int, array<string, mixed>> |
|
*/ |
|
public function messagesForActiveSession(string $username): array |
|
{ |
|
$session = $this->activeSession($username); |
|
if ($session === null) { |
|
return []; |
|
} |
|
|
|
return $this->messagesForSessionId((int) $session['id']); |
|
} |
|
|
|
/** |
|
* @return array<int, array<string, mixed>> |
|
*/ |
|
public function messagesForSessionId(int $sessionId): array |
|
{ |
|
return DB::connection('project_search') |
|
->table('project_search_chat_messages') |
|
->where('session_id', $sessionId) |
|
->orderBy('id') |
|
->get() |
|
->map(fn ($row) => $this->mapMessage($row)) |
|
->all(); |
|
} |
|
|
|
/** |
|
* @return array<int, array{role: string, content: string}> |
|
*/ |
|
public function conversationHistoryForOllama(int $sessionId): array |
|
{ |
|
return DB::connection('project_search') |
|
->table('project_search_chat_messages') |
|
->where('session_id', $sessionId) |
|
->whereIn('role', ['user', 'assistant']) |
|
->orderBy('id') |
|
->get(['role', 'content']) |
|
->map(fn ($row) => [ |
|
'role' => $row->role === 'assistant' ? 'assistant' : 'user', |
|
'content' => (string) $row->content, |
|
]) |
|
->all(); |
|
} |
|
|
|
/** |
|
* @return array<string, mixed> |
|
*/ |
|
public function addUserMessage(int $sessionId, string $content): array |
|
{ |
|
$message = $this->insertMessage($sessionId, 'user', $content); |
|
|
|
DB::connection('project_search') |
|
->table('project_search_chat_sessions') |
|
->where('id', $sessionId) |
|
->increment('message_count'); |
|
|
|
return $message; |
|
} |
|
|
|
/** |
|
* @param array<int, array<string, mixed>> $results |
|
* @param array<string, mixed> $meta |
|
* @return array<string, mixed> |
|
*/ |
|
public function addAssistantMessage(int $sessionId, string $content, array $results = [], array $meta = []): array |
|
{ |
|
$message = $this->insertMessage($sessionId, 'assistant', $content, $results, $meta); |
|
|
|
DB::connection('project_search') |
|
->table('project_search_chat_sessions') |
|
->where('id', $sessionId) |
|
->increment('message_count'); |
|
|
|
return $message; |
|
} |
|
|
|
public function endActiveSession(string $username): bool |
|
{ |
|
$session = $this->activeSession($username); |
|
if ($session === null) { |
|
session()->forget(self::SESSION_KEY); |
|
|
|
return false; |
|
} |
|
|
|
DB::connection('project_search') |
|
->table('project_search_chat_sessions') |
|
->where('id', $session['id']) |
|
->update([ |
|
'status' => 'ended', |
|
'ended_at' => now(), |
|
]); |
|
|
|
session()->forget(self::SESSION_KEY); |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* @return array<int, array<string, mixed>> |
|
*/ |
|
public function recentEndedSessions(string $username, int $limit = 10): array |
|
{ |
|
if (! $this->isAvailable()) { |
|
return []; |
|
} |
|
|
|
return DB::connection('project_search') |
|
->table('project_search_chat_sessions') |
|
->where('username', $username) |
|
->where('status', 'ended') |
|
->orderByDesc('ended_at') |
|
->limit($limit) |
|
->get() |
|
->map(fn ($row) => $this->mapSession($row)) |
|
->all(); |
|
} |
|
|
|
/** |
|
* @param array<int, array<string, mixed>> $results |
|
* @param array<string, mixed> $meta |
|
* @return array<string, mixed> |
|
*/ |
|
private function insertMessage(int $sessionId, string $role, string $content, array $results = [], array $meta = []): array |
|
{ |
|
$id = DB::connection('project_search') |
|
->table('project_search_chat_messages') |
|
->insertGetId([ |
|
'session_id' => $sessionId, |
|
'role' => $role, |
|
'content' => $content, |
|
'results' => $results !== [] ? json_encode($results) : null, |
|
'meta' => $meta !== [] ? json_encode($meta) : null, |
|
'created_at' => now(), |
|
]); |
|
|
|
return [ |
|
'id' => (int) $id, |
|
'session_id' => $sessionId, |
|
'role' => $role, |
|
'content' => $content, |
|
'results' => $results, |
|
'meta' => $meta, |
|
'created_at' => now()->toIso8601String(), |
|
]; |
|
} |
|
|
|
/** |
|
* @return array<string, mixed> |
|
*/ |
|
private function mapSession(object $row): array |
|
{ |
|
$sources = $row->sources ?? null; |
|
if (is_string($sources)) { |
|
$sources = json_decode($sources, true); |
|
} |
|
|
|
return [ |
|
'id' => (int) $row->id, |
|
'session_uuid' => (string) $row->session_uuid, |
|
'username' => (string) $row->username, |
|
'status' => (string) $row->status, |
|
'sources' => is_array($sources) ? $sources : [], |
|
'started_at' => $row->started_at, |
|
'ended_at' => $row->ended_at, |
|
'message_count' => (int) $row->message_count, |
|
]; |
|
} |
|
|
|
/** |
|
* @return array<string, mixed> |
|
*/ |
|
private function mapMessage(object $row): array |
|
{ |
|
$results = $row->results ?? null; |
|
$meta = $row->meta ?? null; |
|
|
|
if (is_string($results)) { |
|
$results = json_decode($results, true); |
|
} |
|
if (is_string($meta)) { |
|
$meta = json_decode($meta, true); |
|
} |
|
|
|
return [ |
|
'id' => (int) $row->id, |
|
'session_id' => (int) $row->session_id, |
|
'role' => (string) $row->role, |
|
'content' => (string) $row->content, |
|
'results' => is_array($results) ? $results : [], |
|
'meta' => is_array($meta) ? $meta : [], |
|
'created_at' => $row->created_at, |
|
]; |
|
} |
|
}
|
|
|