2026-04-16 00:23:13 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\App;
|
|
|
|
|
|
2026-04-16 12:59:10 +00:00
|
|
|
use App\Ai\Agents\SocialMediaAssistant;
|
|
|
|
|
use App\Ai\Tools\AttachmentCollector;
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
use App\Enums\Ai\Intent;
|
|
|
|
|
use App\Enums\Ai\UsageType;
|
2026-04-16 00:23:13 +00:00
|
|
|
use App\Features\AiImagesLimit;
|
|
|
|
|
use App\Features\AiVideosLimit;
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
use App\Http\Requests\App\Assistant\StoreAssistantMessageRequest;
|
2026-04-16 00:23:13 +00:00
|
|
|
use App\Models\AiUsageLog;
|
|
|
|
|
use App\Models\Post;
|
|
|
|
|
use App\Services\Ai\IntentDetector;
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
use Laravel\Pennant\Feature;
|
2026-04-16 13:22:55 +00:00
|
|
|
use RuntimeException;
|
2026-04-16 00:23:13 +00:00
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
2026-04-16 13:22:55 +00:00
|
|
|
use Throwable;
|
2026-04-16 00:23:13 +00:00
|
|
|
|
|
|
|
|
class PostAssistantController extends Controller
|
|
|
|
|
{
|
|
|
|
|
public function index(Request $request, Post $post): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if ($post->workspace_id !== $workspace->id) {
|
|
|
|
|
abort(Response::HTTP_FORBIDDEN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$messages = $post->aiMessages()
|
|
|
|
|
->with('user')
|
|
|
|
|
->oldest()
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
return response()->json(['messages' => $messages]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function store(
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
StoreAssistantMessageRequest $request,
|
2026-04-16 00:23:13 +00:00
|
|
|
Post $post,
|
|
|
|
|
IntentDetector $intentDetector,
|
2026-04-16 12:59:10 +00:00
|
|
|
AttachmentCollector $collector,
|
2026-04-16 00:23:13 +00:00
|
|
|
): JsonResponse {
|
|
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
|
|
|
|
if ($post->workspace_id !== $workspace->id) {
|
|
|
|
|
abort(Response::HTTP_FORBIDDEN);
|
|
|
|
|
}
|
|
|
|
|
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
$validated = $request->validated();
|
2026-04-16 00:23:13 +00:00
|
|
|
|
|
|
|
|
$prompt = data_get($validated, 'body');
|
|
|
|
|
|
|
|
|
|
$userMessage = $post->aiMessages()->create([
|
|
|
|
|
'user_id' => $request->user()->id,
|
|
|
|
|
'role' => 'user',
|
|
|
|
|
'content' => $prompt,
|
|
|
|
|
]);
|
|
|
|
|
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
if ($request->hasFile('image')) {
|
|
|
|
|
$media = $workspace->addMedia($request->file('image'), 'assets');
|
|
|
|
|
|
|
|
|
|
$userMessage->update([
|
|
|
|
|
'attachments' => [['id' => $media->id, 'path' => $media->path, 'url' => $media->url, 'type' => 'image', 'mime_type' => $media->mime_type]],
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 00:23:13 +00:00
|
|
|
$userMessage->load('user');
|
|
|
|
|
|
|
|
|
|
$intent = $intentDetector->detect($prompt);
|
|
|
|
|
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
if ($intent === Intent::Blocked) {
|
|
|
|
|
$assistantMessage = $post->aiMessages()->create([
|
|
|
|
|
'role' => 'assistant',
|
|
|
|
|
'content' => __('assistant.content_blocked'),
|
|
|
|
|
'metadata' => ['intent' => $intent->value, 'error' => true],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'user_message' => $userMessage,
|
|
|
|
|
'assistant_message' => $assistantMessage,
|
|
|
|
|
], Response::HTTP_CREATED);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 00:23:13 +00:00
|
|
|
try {
|
2026-04-16 13:22:55 +00:00
|
|
|
$post->loadMissing('postPlatforms');
|
2026-04-16 12:59:10 +00:00
|
|
|
|
2026-04-16 13:22:55 +00:00
|
|
|
$assistantMessages = $post->aiMessages()
|
2026-04-16 12:59:10 +00:00
|
|
|
->where('role', 'assistant')
|
2026-04-16 13:22:55 +00:00
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
$imagesInThread = $assistantMessages->sum(
|
|
|
|
|
fn ($m) => collect($m->attachments ?? [])->where('type', 'image')->count()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$videosInThread = $assistantMessages->sum(
|
|
|
|
|
fn ($m) => collect($m->attachments ?? [])->where('type', 'video')->count()
|
|
|
|
|
);
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
|
|
|
|
|
$imageLimit = (int) Feature::for($workspace->account)->value(AiImagesLimit::class);
|
|
|
|
|
$imageUsed = AiUsageLog::monthlyCount($workspace->account_id, UsageType::Image);
|
|
|
|
|
$imageRemaining = max(0, $imageLimit - $imageUsed);
|
|
|
|
|
|
|
|
|
|
$videoLimit = (int) Feature::for($workspace->account)->value(AiVideosLimit::class);
|
|
|
|
|
$videoUsed = AiUsageLog::monthlyCount($workspace->account_id, UsageType::Video);
|
|
|
|
|
$videoRemaining = max(0, $videoLimit - $videoUsed);
|
|
|
|
|
|
|
|
|
|
$stateContext = sprintf(
|
|
|
|
|
"[Session state — use this to track progress and respect quotas]\n".
|
|
|
|
|
"- Images already generated in this conversation: %d\n".
|
|
|
|
|
"- Videos already generated in this conversation: %d\n".
|
|
|
|
|
"- Monthly quota remaining: %d images, %d videos\n",
|
|
|
|
|
$imagesInThread,
|
|
|
|
|
$videosInThread,
|
|
|
|
|
$imageRemaining,
|
|
|
|
|
$videoRemaining,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$promptWithState = "{$stateContext}\n{$prompt}";
|
|
|
|
|
|
2026-04-16 12:59:10 +00:00
|
|
|
$collector->clear();
|
2026-04-16 00:23:13 +00:00
|
|
|
|
2026-04-16 12:59:10 +00:00
|
|
|
$response = (new SocialMediaAssistant(
|
|
|
|
|
workspace: $workspace,
|
|
|
|
|
post: $post,
|
|
|
|
|
userId: $request->user()->id,
|
|
|
|
|
))->prompt($promptWithState);
|
2026-04-16 00:23:13 +00:00
|
|
|
|
2026-04-16 12:59:10 +00:00
|
|
|
$responseContent = $response->text;
|
|
|
|
|
$attachments = $collector->all();
|
2026-04-16 00:23:13 +00:00
|
|
|
|
2026-04-16 12:59:10 +00:00
|
|
|
$generatedIntent = $intent->value;
|
|
|
|
|
foreach ($attachments as $attachment) {
|
|
|
|
|
if (isset($attachment['type'])) {
|
|
|
|
|
$generatedIntent = $attachment['type'];
|
|
|
|
|
break;
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
}
|
2026-04-16 00:23:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$assistantMessage = $post->aiMessages()->create([
|
|
|
|
|
'role' => 'assistant',
|
|
|
|
|
'content' => $responseContent,
|
|
|
|
|
'attachments' => $attachments,
|
2026-04-16 12:59:10 +00:00
|
|
|
'metadata' => ['intent' => $generatedIntent],
|
2026-04-16 00:23:13 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'user_message' => $userMessage,
|
|
|
|
|
'assistant_message' => $assistantMessage,
|
|
|
|
|
], Response::HTTP_CREATED);
|
2026-04-16 13:22:55 +00:00
|
|
|
} catch (Throwable $e) {
|
2026-04-16 00:23:13 +00:00
|
|
|
Log::error('PostAssistantController error', ['error' => $e->getMessage()]);
|
|
|
|
|
|
2026-04-16 13:22:55 +00:00
|
|
|
$errorMessage = $e instanceof RuntimeException ? $e->getMessage() : __('assistant.error');
|
2026-04-16 00:23:13 +00:00
|
|
|
|
|
|
|
|
$assistantMessage = $post->aiMessages()->create([
|
|
|
|
|
'role' => 'assistant',
|
|
|
|
|
'content' => $errorMessage,
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
'metadata' => ['intent' => $intent->value, 'error' => true],
|
2026-04-16 00:23:13 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'user_message' => $userMessage,
|
|
|
|
|
'assistant_message' => $assistantMessage,
|
|
|
|
|
], Response::HTTP_CREATED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|