First Commit
This commit is contained in:
Executable
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class AiServiceFactory
|
||||
{
|
||||
/**
|
||||
* Create an AI service instance based on the provider.
|
||||
*
|
||||
* @param AiProvider|string $provider
|
||||
* @return AiServiceInterface
|
||||
*/
|
||||
public static function make(AiProvider|string $provider): AiServiceInterface
|
||||
{
|
||||
if (is_string($provider)) {
|
||||
$provider = AiProvider::withTrashed()->where('slug', $provider)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
if (!$provider->is_active) {
|
||||
throw new InvalidArgumentException('AI provider is not active.');
|
||||
}
|
||||
|
||||
return match ($provider->slug) {
|
||||
'openrouter', 'openrouter-test' => new OpenRouterService($provider),
|
||||
'stepfun' => new StepFunService($provider),
|
||||
'llamacpp' => new LlamaCppService($provider),
|
||||
default => throw new InvalidArgumentException("Unsupported AI provider: {$provider->slug}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
interface AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* Generate a prompt using the AI provider.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function generatePrompt(array $data): array;
|
||||
|
||||
/**
|
||||
* Send a chat conversation to the AI provider.
|
||||
*
|
||||
* @param array<int, array{role: string, content: string}> $messages
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function chat(array $messages, array $data = []): array;
|
||||
}
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LlamaCppService implements AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param AiProvider $provider
|
||||
*/
|
||||
public function __construct(private AiProvider $provider)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => rtrim((string) $provider->base_url, '/') . '/',
|
||||
'timeout' => 120,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chat(array $messages, array $data = []): array
|
||||
{
|
||||
$baseUrl = rtrim((string) $this->provider->base_url, '/');
|
||||
|
||||
if (empty($baseUrl)) {
|
||||
throw new \InvalidArgumentException('Llama.cpp base URL is not configured.');
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->client->post('v1/chat/completions', [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => [
|
||||
'model' => (string) ($data['model'] ?? ''),
|
||||
'messages' => $messages,
|
||||
'temperature' => isset($data['temperature']) ? (float) $data['temperature'] : null,
|
||||
'max_tokens' => isset($data['max_tokens']) ? (int) $data['max_tokens'] : null,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($body['choices'][0]['message']['content'])) {
|
||||
throw new \RuntimeException('Invalid response structure from Llama.cpp server.');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $body['choices'][0]['message']['content'],
|
||||
'tokens_used' => $body['usage']['total_tokens'] ?? null,
|
||||
'model' => $body['model'] ?? (string) ($data['model'] ?? ''),
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('Llama.cpp API error: ' . $e->getMessage());
|
||||
throw new \RuntimeException('Failed to generate prompt with Llama.cpp: ' . $e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePrompt(array $data): array
|
||||
{
|
||||
return $this->chat(
|
||||
[['role' => 'user', 'content' => (string) ($data['prompt'] ?? '')]],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OpenRouterService implements AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param AiProvider $provider
|
||||
*/
|
||||
public function __construct(private AiProvider $provider)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => rtrim((string) $provider->base_url, '/') . '/',
|
||||
'timeout' => 120,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chat(array $messages, array $data = []): array
|
||||
{
|
||||
$apiKey = $data['api_key'] ?? $this->provider->api_key;
|
||||
|
||||
if (empty($apiKey)) {
|
||||
throw new \InvalidArgumentException('OpenRouter API key is not configured.');
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->client->post('chat/completions', [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
'HTTP-Referer' => $data['http_referer'] ?? config('app.url'),
|
||||
'X-Title' => $data['x_title'] ?? config('app.name'),
|
||||
],
|
||||
'json' => [
|
||||
'model' => (string) ($data['model'] ?? ''),
|
||||
'messages' => $messages,
|
||||
'temperature' => isset($data['temperature']) ? (float) $data['temperature'] : null,
|
||||
'max_tokens' => isset($data['max_tokens']) ? (int) $data['max_tokens'] : null,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($body['choices'][0]['message']['content'])) {
|
||||
throw new \RuntimeException('Invalid response structure from OpenRouter.');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $body['choices'][0]['message']['content'],
|
||||
'tokens_used' => $body['usage']['total_tokens'] ?? null,
|
||||
'model' => $body['model'] ?? (string) ($data['model'] ?? ''),
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('OpenRouter API error: ' . $e->getMessage());
|
||||
throw new \RuntimeException('Failed to generate prompt with OpenRouter: ' . $e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePrompt(array $data): array
|
||||
{
|
||||
return $this->chat(
|
||||
[['role' => 'user', 'content' => (string) ($data['prompt'] ?? '')]],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StepFunService implements AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param AiProvider $provider
|
||||
*/
|
||||
public function __construct(private AiProvider $provider)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => rtrim((string) $provider->base_url, '/') . '/',
|
||||
'timeout' => 120,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chat(array $messages, array $data = []): array
|
||||
{
|
||||
$apiKey = $data['api_key'] ?? $this->provider->api_key;
|
||||
|
||||
if (empty($apiKey)) {
|
||||
throw new \InvalidArgumentException('StepFun API key is not configured.');
|
||||
}
|
||||
|
||||
try {
|
||||
$payload = [
|
||||
'model' => (string) ($data['model'] ?? ''),
|
||||
'messages' => $messages,
|
||||
];
|
||||
|
||||
if (isset($data['temperature'])) {
|
||||
$payload['temperature'] = (float) $data['temperature'];
|
||||
}
|
||||
|
||||
if (isset($data['max_tokens'])) {
|
||||
$payload['max_tokens'] = (int) $data['max_tokens'];
|
||||
}
|
||||
|
||||
if (!empty($data['reasoning_effort'])) {
|
||||
$payload['reasoning_effort'] = (string) $data['reasoning_effort'];
|
||||
}
|
||||
|
||||
$response = $this->client->post('chat/completions', [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => $payload,
|
||||
]);
|
||||
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($body['choices'][0]['message']['content'])) {
|
||||
throw new \RuntimeException('Invalid response structure from StepFun.');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $body['choices'][0]['message']['content'],
|
||||
'tokens_used' => $body['usage']['total_tokens'] ?? null,
|
||||
'model' => $body['model'] ?? (string) ($data['model'] ?? ''),
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('StepFun API error: ' . $e->getMessage());
|
||||
throw new \RuntimeException('Failed to generate prompt with StepFun: ' . $e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePrompt(array $data): array
|
||||
{
|
||||
return $this->chat(
|
||||
[['role' => 'user', 'content' => (string) ($data['prompt'] ?? '')]],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class AiAgentService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AiServiceFactory $aiServiceFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Execute a prompt using the specified provider and model.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function execute(
|
||||
string $prompt,
|
||||
AiProvider $provider,
|
||||
AiModel $model,
|
||||
float $temperature = 0.7,
|
||||
int $maxTokens = 8192
|
||||
): array {
|
||||
$startTime = microtime(true);
|
||||
|
||||
$aiData = [
|
||||
'prompt' => $prompt,
|
||||
'temperature' => $temperature,
|
||||
'max_tokens' => $maxTokens,
|
||||
'model' => $model->slug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
];
|
||||
|
||||
try {
|
||||
$service = $this->aiServiceFactory->make($provider);
|
||||
$result = $service->generatePrompt($aiData);
|
||||
} catch (ConnectionException|RuntimeException $e) {
|
||||
Log::error('AI Agent execution failed: ' . $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'content' => '',
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'model' => $model->slug,
|
||||
'provider' => $provider->name,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'content' => $result['content'] ?? '',
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'model' => $model->slug,
|
||||
'provider' => $provider->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class AiProviderService
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
$paginator = AiProvider::with('models')
|
||||
->latest()
|
||||
->paginate(15);
|
||||
|
||||
return [
|
||||
'data' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(array $data): AiProvider
|
||||
{
|
||||
return AiProvider::withTrashed()->firstOrCreate(
|
||||
['slug' => $data['slug']],
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(AiProvider $provider, array $data): AiProvider
|
||||
{
|
||||
$provider->update($data);
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(AiProvider $provider): bool
|
||||
{
|
||||
try {
|
||||
$provider->delete();
|
||||
|
||||
return true;
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error('Failed to delete AI provider: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class ChatService
|
||||
{
|
||||
private const SESSION_KEY = 'chat_conversation';
|
||||
|
||||
public function __construct(
|
||||
private readonly AiServiceFactory $aiServiceFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get conversation history from session.
|
||||
*
|
||||
* @return array<int, array{role: string, content: string}>
|
||||
*/
|
||||
public function getHistory(): array
|
||||
{
|
||||
return session()->get(self::SESSION_KEY, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset conversation history.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
session()->forget(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message and get AI response.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function sendMessage(
|
||||
string $userMessage,
|
||||
AiProvider $provider,
|
||||
AiModel $model,
|
||||
float $temperature = 0.7,
|
||||
int $maxTokens = 8192
|
||||
): array {
|
||||
$history = $this->getHistory();
|
||||
|
||||
$history[] = ['role' => 'user', 'content' => $userMessage];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$service = $this->aiServiceFactory->make($provider);
|
||||
$result = $service->chat($history, [
|
||||
'temperature' => $temperature,
|
||||
'max_tokens' => $maxTokens,
|
||||
'model' => $model->slug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
]);
|
||||
} catch (ConnectionException|RuntimeException $e) {
|
||||
Log::error('Chat API error: ' . $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'content' => '',
|
||||
'role' => 'assistant',
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
];
|
||||
}
|
||||
|
||||
$assistantMessage = [
|
||||
'role' => 'assistant',
|
||||
'content' => $result['content'] ?? '',
|
||||
];
|
||||
|
||||
$history[] = $assistantMessage;
|
||||
|
||||
session()->put(self::SESSION_KEY, $history);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'content' => $result['content'] ?? '',
|
||||
'role' => 'assistant',
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+210
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\PromptProject;
|
||||
use App\Models\User;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use App\Services\Ai\AiServiceInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class PromptGenerationService
|
||||
{
|
||||
/**
|
||||
* @param AiServiceFactory $factory
|
||||
*/
|
||||
public function __construct(private AiServiceFactory $factory)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a prompt based on a prompt project.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return PromptGeneration
|
||||
*/
|
||||
public function generate(array $data): PromptGeneration
|
||||
{
|
||||
$project = $data['project'] ?? null;
|
||||
$user = $data['user'] ?? null;
|
||||
|
||||
if (!$project instanceof PromptProject) {
|
||||
throw new InvalidArgumentException('Prompt project is required.');
|
||||
}
|
||||
|
||||
if (!$user instanceof User) {
|
||||
$user = auth()->user();
|
||||
}
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new InvalidArgumentException('User is required.');
|
||||
}
|
||||
|
||||
$provider = $data['provider_id'] ?? $project->provider_id;
|
||||
$model = $data['model_id'] ?? $project->model_id;
|
||||
|
||||
if (!$provider instanceof AiProvider) {
|
||||
$provider = AiProvider::findOrFail((int) $provider);
|
||||
}
|
||||
|
||||
if (!$model instanceof AiModel) {
|
||||
$model = AiModel::findOrFail((int) $model);
|
||||
}
|
||||
|
||||
$promptText = $this->buildPrompt($project);
|
||||
|
||||
$aiData = [
|
||||
'prompt' => $promptText,
|
||||
'temperature' => (float) ($data['temperature'] ?? $project->temperature ?? 0.7),
|
||||
'max_tokens' => (int) ($data['max_tokens'] ?? $project->max_tokens ?? 1024),
|
||||
'model' => $model->slug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$service = $this->factory->make($provider);
|
||||
$result = $service->generatePrompt($aiData);
|
||||
} catch (GuzzleException|RuntimeException $e) {
|
||||
Log::error('Prompt generation failed: ' . $e->getMessage());
|
||||
|
||||
return PromptGeneration::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $user->id,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $model->id,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => ['error' => $e->getMessage()],
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'failed',
|
||||
]);
|
||||
}
|
||||
|
||||
$generation = PromptGeneration::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $user->id,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $model->id,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => $result,
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'completed',
|
||||
]);
|
||||
|
||||
return $generation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the prompt text from project data.
|
||||
*
|
||||
* @param PromptProject $project
|
||||
* @return string
|
||||
*/
|
||||
private function buildPrompt(PromptProject $project): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
if ($project->company_name) {
|
||||
$parts[] = "Company Name: " . $project->company_name;
|
||||
}
|
||||
|
||||
if ($project->company_address) {
|
||||
$parts[] = "Company Address: " . $project->company_address;
|
||||
}
|
||||
|
||||
if ($project->company_whatsapp) {
|
||||
$parts[] = "Company WhatsApp: " . $project->company_whatsapp;
|
||||
}
|
||||
|
||||
if ($project->company_website) {
|
||||
$parts[] = "Company Website: " . $project->company_website;
|
||||
}
|
||||
|
||||
if ($project->company_social_media) {
|
||||
$parts[] = "Company Social Media: " . $project->company_social_media;
|
||||
}
|
||||
|
||||
if ($project->company_business_activities) {
|
||||
$parts[] = "Company Business Activities: " . $project->company_business_activities;
|
||||
}
|
||||
|
||||
if ($project->company_services_products) {
|
||||
$parts[] = "Company Services/Products: " . $project->company_services_products;
|
||||
}
|
||||
|
||||
if ($project->objective) {
|
||||
$parts[] = "Objective: " . $project->objective;
|
||||
}
|
||||
|
||||
if ($project->context) {
|
||||
$parts[] = "Context: " . $project->context;
|
||||
}
|
||||
|
||||
if ($project->persona) {
|
||||
$parts[] = "Persona/Role: " . $project->persona;
|
||||
}
|
||||
|
||||
if ($project->tone) {
|
||||
$parts[] = "Tone/Style: " . $project->tone;
|
||||
}
|
||||
|
||||
if ($project->constraints) {
|
||||
$parts[] = "Constraints: " . $project->constraints;
|
||||
}
|
||||
|
||||
if ($project->output_format) {
|
||||
$parts[] = "Output Format: " . $project->output_format;
|
||||
}
|
||||
|
||||
if (!empty($project->examples)) {
|
||||
$parts[] = "Examples:\n" . json_encode($project->examples, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
if (!empty($project->variables)) {
|
||||
$parts[] = "Variables: " . json_encode($project->variables, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of prompt generations.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Pagination\Paginator
|
||||
*/
|
||||
public function index(): \Illuminate\Contracts\Pagination\Paginator
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$query = PromptGeneration::query()
|
||||
->with(['project', 'user', 'provider', 'model'])
|
||||
->latest();
|
||||
|
||||
if (!$user || $user->role !== 'superadmin') {
|
||||
$query->where('user_id', auth()->id());
|
||||
}
|
||||
|
||||
return $query->paginate(15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified prompt generation.
|
||||
*/
|
||||
public function destroy(PromptGeneration $generation): void
|
||||
{
|
||||
$generation->delete();
|
||||
}
|
||||
}
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\PromptProject;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class PromptProjectService
|
||||
{
|
||||
/**
|
||||
* Create a new service instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly AiServiceFactory $aiServiceFactory,
|
||||
private readonly PromptGeneration $generationModel
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
$userId = auth()->id();
|
||||
$user = auth()->user();
|
||||
|
||||
$query = PromptProject::with(['user', 'provider', 'model'])
|
||||
->latest();
|
||||
|
||||
if (!$user || $user->role !== 'superadmin') {
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
$paginator = $query->paginate(15);
|
||||
|
||||
return [
|
||||
'data' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(array $data): PromptProject
|
||||
{
|
||||
$data['examples'] = $this->convertToArray($data['examples'] ?? null);
|
||||
$data['variables'] = $this->convertToArray($data['variables'] ?? null);
|
||||
|
||||
return PromptProject::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(PromptProject $project, array $data): PromptProject
|
||||
{
|
||||
$data['examples'] = $this->convertToArray($data['examples'] ?? null);
|
||||
$data['variables'] = $this->convertToArray($data['variables'] ?? null);
|
||||
|
||||
$project->update($data);
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to array.
|
||||
*/
|
||||
private function convertToArray(mixed $value): ?array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_map('trim', explode(',', $value));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(PromptProject $project): bool
|
||||
{
|
||||
try {
|
||||
$project->delete();
|
||||
|
||||
return true;
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error('Failed to delete prompt project: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate prompt for the specified project.
|
||||
*/
|
||||
public function generate(PromptProject $project, mixed $model = null, ?int $userId = null): PromptGeneration
|
||||
{
|
||||
$provider = $project->provider;
|
||||
|
||||
if (!$provider instanceof AiProvider) {
|
||||
throw new InvalidArgumentException('AI provider is required for generation.');
|
||||
}
|
||||
|
||||
if (!$model) {
|
||||
$model = $project->model;
|
||||
}
|
||||
|
||||
if (!$model) {
|
||||
throw new InvalidArgumentException('AI model is required for generation.');
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$model = AiModel::firstOrCreate(
|
||||
['provider_id' => $provider->id, 'slug' => $model],
|
||||
['name' => $model, 'slug' => $model, 'is_active' => true]
|
||||
);
|
||||
}
|
||||
|
||||
$modelSlug = $model->slug;
|
||||
$modelId = $model->id;
|
||||
|
||||
$userId ??= auth()->id();
|
||||
|
||||
$promptText = $this->buildPrompt($project);
|
||||
|
||||
$aiData = [
|
||||
'prompt' => $promptText,
|
||||
'temperature' => (float) ($project->temperature ?? 0.7),
|
||||
'max_tokens' => (int) ($project->max_tokens ?? 1024),
|
||||
'model' => $modelSlug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$service = $this->aiServiceFactory->make($provider);
|
||||
$result = $service->generatePrompt($aiData);
|
||||
} catch (ConnectionException|RuntimeException $e) {
|
||||
Log::error('Prompt generation failed: ' . $e->getMessage());
|
||||
|
||||
return $this->generationModel::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $userId,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $modelId,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => ['error' => $e->getMessage()],
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'failed',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->generationModel::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $userId,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $modelId,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => $result,
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'completed',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the prompt text from project data.
|
||||
*/
|
||||
public function buildPrompt(PromptProject $project): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
$parts[] = "You are an expert prompt engineering assistant. Your task is to generate a high-quality, optimized prompt based on the specifications provided below.";
|
||||
|
||||
if ($project->company_name) {
|
||||
$parts[] = "Company Name: " . $project->company_name;
|
||||
}
|
||||
|
||||
if ($project->company_address) {
|
||||
$parts[] = "Company Address: " . $project->company_address;
|
||||
}
|
||||
|
||||
if ($project->company_whatsapp) {
|
||||
$parts[] = "Company WhatsApp: " . $project->company_whatsapp;
|
||||
}
|
||||
|
||||
if ($project->company_website) {
|
||||
$parts[] = "Company Website: " . $project->company_website;
|
||||
}
|
||||
|
||||
if ($project->company_social_media) {
|
||||
$parts[] = "Company Social Media: " . $project->company_social_media;
|
||||
}
|
||||
|
||||
if ($project->company_business_activities) {
|
||||
$parts[] = "Company Business Activities: " . $project->company_business_activities;
|
||||
}
|
||||
|
||||
if ($project->company_services_products) {
|
||||
$parts[] = "Company Services/Products: " . $project->company_services_products;
|
||||
}
|
||||
|
||||
if ($project->objective) {
|
||||
$parts[] = "Objective: " . $project->objective;
|
||||
}
|
||||
|
||||
if ($project->context) {
|
||||
$parts[] = "Context: " . $project->context;
|
||||
}
|
||||
|
||||
if ($project->persona) {
|
||||
$parts[] = "Persona/Role: " . $project->persona;
|
||||
}
|
||||
|
||||
if ($project->tone) {
|
||||
$parts[] = "Tone/Style: " . $project->tone;
|
||||
}
|
||||
|
||||
if ($project->constraints) {
|
||||
$parts[] = "Constraints: " . $project->constraints;
|
||||
}
|
||||
|
||||
if ($project->output_format) {
|
||||
$parts[] = "Output Format: " . $project->output_format;
|
||||
}
|
||||
|
||||
if (!empty($project->examples)) {
|
||||
$parts[] = "Examples:\n" . json_encode($project->examples, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
if (!empty($project->variables)) {
|
||||
$parts[] = "Variables: " . json_encode($project->variables, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$parts[] = "Generate the optimized prompt now. Return ONLY the prompt, without explanations, introductions, or additional text.";
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\PromptTemplate;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class PromptTemplateService
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
$paginator = PromptTemplate::with('user')
|
||||
->latest()
|
||||
->paginate(15);
|
||||
|
||||
return [
|
||||
'data' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(array $data): PromptTemplate
|
||||
{
|
||||
$data['user_id'] = auth()->id();
|
||||
|
||||
return PromptTemplate::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(PromptTemplate $template, array $data): PromptTemplate
|
||||
{
|
||||
$template->update($data);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(PromptTemplate $template): bool
|
||||
{
|
||||
try {
|
||||
$template->delete();
|
||||
|
||||
return true;
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error('Failed to delete prompt template: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class TutorialService
|
||||
{
|
||||
/**
|
||||
* Get tutorial content.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getContent(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'Prompt Engineering Tutorial',
|
||||
'description' => 'Learn how to create effective prompts for AI models.',
|
||||
'sections' => [
|
||||
[
|
||||
'id' => 'introduction',
|
||||
'title' => 'Introduction',
|
||||
'content' => 'Prompt engineering is the process of designing and optimizing prompts to effectively communicate with AI language models. A well-crafted prompt can significantly improve the quality and relevance of AI-generated responses.',
|
||||
],
|
||||
[
|
||||
'id' => 'objective',
|
||||
'title' => 'Objective',
|
||||
'content' => 'Clearly define what you want the AI to achieve. Be specific about the goal and desired outcome. A clear objective helps the AI understand the task and produce more relevant results.',
|
||||
'tips' => [
|
||||
'Start with action verbs (e.g., "Write", "Analyze", "Create")',
|
||||
'Specify the desired outcome explicitly',
|
||||
'Keep it concise but complete',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'context',
|
||||
'title' => 'Context',
|
||||
'content' => 'Provide background information that helps the AI understand the situation. Context includes relevant details, constraints, and situational awareness that influence the response.',
|
||||
'tips' => [
|
||||
'Include relevant background information',
|
||||
'Mention any constraints or limitations',
|
||||
'Provide examples of the desired scenario',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'persona',
|
||||
'title' => 'Persona / Role',
|
||||
'content' => 'Define who the AI should be. Assigning a specific role or persona helps the AI adopt the appropriate tone, expertise level, and perspective for the task.',
|
||||
'tips' => [
|
||||
'Use professional roles (e.g., "Senior Developer", "Marketing Expert")',
|
||||
'Specify expertise level (e.g., "Expert", "Beginner-friendly")',
|
||||
'Combine multiple roles if needed',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'tone',
|
||||
'title' => 'Tone / Style',
|
||||
'content' => 'Specify the communication style and tone. This ensures the AI\'s response matches your expectations for formality, friendliness, and overall style.',
|
||||
'tips' => [
|
||||
'Use descriptive adjectives (e.g., "Professional", "Casual", "Technical")',
|
||||
'Specify formality level',
|
||||
'Mention any stylistic preferences',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'constraints',
|
||||
'title' => 'Constraints',
|
||||
'content' => 'Define limitations and rules the AI must follow. Constraints help prevent unwanted content and ensure the response meets specific requirements.',
|
||||
'tips' => [
|
||||
'Set word or character limits',
|
||||
'Exclude certain topics or formats',
|
||||
'Specify mandatory elements',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'output_format',
|
||||
'title' => 'Output Format',
|
||||
'content' => 'Specify the expected structure of the response. A clear format helps the AI organize the information in a way that is most useful for you.',
|
||||
'tips' => [
|
||||
'Use markdown, JSON, or plain text',
|
||||
'Specify sections and headings',
|
||||
'Include examples of the desired format',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'examples',
|
||||
'title' => 'Examples (Few-Shot)',
|
||||
'content' => 'Provide example inputs and outputs. Few-shot learning helps the AI understand the pattern and quality expected in the response.',
|
||||
'tips' => [
|
||||
'Include 2-3 high-quality examples',
|
||||
'Show both input and desired output',
|
||||
'Ensure examples cover edge cases',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'variables',
|
||||
'title' => 'Variables',
|
||||
'content' => 'Use dynamic placeholders for reusable prompts. Variables make your prompts flexible and reusable across different scenarios.',
|
||||
'tips' => [
|
||||
'Use clear variable names (e.g., {{topic}}, {{audience}})',
|
||||
'Document expected variable values',
|
||||
'Keep variable count manageable',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'temperature',
|
||||
'title' => 'Temperature',
|
||||
'content' => 'Control the randomness of the AI response. Lower values produce more deterministic and focused outputs, while higher values increase creativity and diversity.',
|
||||
'tips' => [
|
||||
'0.0 - 0.3: Factual, deterministic, focused',
|
||||
'0.4 - 0.7: Balanced, natural, versatile',
|
||||
'0.8 - 1.0: Creative, diverse, exploratory',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'max_tokens',
|
||||
'title' => 'Max Tokens',
|
||||
'content' => 'Limit the length of the AI response. Setting an appropriate token limit helps control response length and cost.',
|
||||
'tips' => [
|
||||
'Short responses: 100-300 tokens',
|
||||
'Medium responses: 500-1000 tokens',
|
||||
'Long responses: 1500+ tokens',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'best_practices',
|
||||
'title' => 'Best Practices',
|
||||
'content' => 'Follow these guidelines to create effective prompts:',
|
||||
'tips' => [
|
||||
'Be specific and clear about your requirements',
|
||||
'Provide context and examples',
|
||||
'Iterate and refine your prompts',
|
||||
'Test with different models and settings',
|
||||
'Keep prompts concise but complete',
|
||||
'Use structured formats for complex tasks',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user