Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ ELEVENLABS_API_KEY=
AI_TEXT_PROVIDER=openai
AI_AUDIO_PROVIDER=elevenlabs

# Optional DF caption style. Default uses natural-writing guidance without this file.
# Set an absolute path to a readable UTF-8 guide of at most 8 KiB to offer DF.
# The guide is sent only when DF is selected; keep it suitable for the chosen provider.
# AI_DF_STYLE_PATH=/absolute/path/to/writing-style-df.md

# AI Model Overrides (optional)
# Each provider picks a sensible default model per capability on its own -
# these are NOT shared across capabilities, so overriding one (e.g. a text
Expand Down
3 changes: 2 additions & 1 deletion app/Actions/Ai/AssistPostContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ public static function execute(
?Platform $platform,
array $selection,
?SocialAccount $account = null,
?string $writingStyleInstructions = null,
): string {
$platform = $account->platform ?? $platform;
$limit = $account?->maxContentLength() ?? $platform?->maxContentLength();

$response = (new PostWritingAssistant($mode, $currentContent, $user->locale, $platform, $previousContent, $limit))
$response = (new PostWritingAssistant($mode, $currentContent, $user->locale, $platform, $previousContent, $limit, $writingStyleInstructions))
->withModelSelection($selection)
->prompt(
$mode->requiresPrompt() ? trim((string) $prompt) : 'Revise the existing caption as instructed.',
Expand Down
2 changes: 2 additions & 0 deletions app/Ai/Agents/PostWritingAssistant.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ public function __construct(
public ?Platform $platform = null,
public ?string $previousContent = null,
public ?int $hardMaxChars = null,
public readonly ?string $writingStyleInstructions = null,
) {}

public function instructions(): string
{
return view('prompts.post_content.assistant', [
'mode' => $this->mode->value,
'writing_style_instructions' => $this->writingStyleInstructions,
'writes_new_text' => $this->mode->requiresPrompt(),
'language' => $this->locale->promptLanguage(),
'current_content' => $this->currentContent,
Expand Down
3 changes: 3 additions & 0 deletions app/Http/Controllers/App/PostAiAssistantController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Enums\SocialAccount\Platform;
use App\Http\Requests\App\Ai\AssistPostContentRequest;
use App\Support\Ai\AiModelCatalog;
use App\Support\Ai\WritingStyleCatalog;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\JsonResponse;
Expand All @@ -30,6 +31,7 @@ public function __invoke(AssistPostContentRequest $request): JsonResponse
}

$selection = app(AiModelCatalog::class)->resolve($request->validated('model_id'));
$writingStyleInstructions = app(WritingStyleCatalog::class)->resolve($request->validated('style_id'));

try {
$content = AssistPostContent::execute(
Expand All @@ -41,6 +43,7 @@ public function __invoke(AssistPostContentRequest $request): JsonResponse
previousContent: $request->input('previous_content'),
platform: $request->enum('platform', Platform::class),
selection: $selection,
writingStyleInstructions: $writingStyleInstructions,
account: $request->filled('social_account_id')
? $workspace->socialAccounts()->find($request->input('social_account_id'))
: null,
Expand Down
2 changes: 2 additions & 0 deletions app/Http/Middleware/App/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use App\Models\Plan;
use App\Models\Workspace;
use App\Support\Ai\AiModelCatalog;
use App\Support\Ai\WritingStyleCatalog;
use App\Support\HeicConverter;
use App\Support\LinkTlds;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -111,6 +112,7 @@ public function shareOnce(Request $request): array
'githubAuthEnabled' => fn (): bool => SocialAuthProvider::GitHub->isEnabled(),
...($user !== null ? [
'aiModels' => fn (): array => app(AiModelCatalog::class)->forFrontend(),
'aiWritingStyles' => fn (): array => app(WritingStyleCatalog::class)->forFrontend(),
'mediaSources' => fn (): array => ['menu' => MediaSource::menu()],
'mediaUploadLimits' => fn (): array => [
'max_bytes' => [
Expand Down
1 change: 1 addition & 0 deletions app/Http/Requests/App/Ai/AssistPostContentRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function rules(): array

return [
'model_id' => ['nullable', 'string', 'max:64'],
'style_id' => ['nullable', 'string', 'max:64'],
'mode' => ['required', Rule::enum(PostAssistantMode::class)],
'prompt' => [
Rule::requiredIf(fn (): bool => $mode?->requiresPrompt() ?? false),
Expand Down
61 changes: 61 additions & 0 deletions app/Support/Ai/WritingStyleCatalog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace App\Support\Ai;

use Illuminate\Validation\ValidationException;

final class WritingStyleCatalog
{
public function resolve(?string $id = null): ?string
{
if ($id === null || $id === 'default') {
return null;
}

$path = config('ai_writing.df_style_path');
if ($id !== 'df' || ! is_string($path) || ! str_starts_with($path, '/')
|| str_contains($path, '://') || str_contains($path, "\0")) {
$this->unavailable();
}

$resolved = realpath($path);
if ($resolved === false || ! is_file($resolved) || ! is_readable($resolved)) {
$this->unavailable();
}

$handle = @fopen($resolved, 'rb');
if ($handle === false) {
$this->unavailable();
}

try {
$instructions = stream_get_contents($handle, 8193);
} finally {
fclose($handle);
}

if ($instructions === false || strlen($instructions) > 8192
|| trim($instructions) === '' || ! mb_check_encoding($instructions, 'UTF-8')
|| preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $instructions)) {
$this->unavailable();
}

return trim($instructions);
}

/** @return array{default: string, options: list<string>} */
public function forFrontend(): array
{
return [
'default' => 'default',
'options' => filled(config('ai_writing.df_style_path')) ? ['default', 'df'] : ['default'],
];
}

private function unavailable(): never
{
throw ValidationException::withMessages(['style_id' => __('posts.composer.ai_style_unavailable')]);
}
}
7 changes: 7 additions & 0 deletions config/ai_writing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

declare(strict_types=1);

return [
'df_style_path' => env('AI_DF_STYLE_PATH'),
];
4 changes: 4 additions & 0 deletions lang/ar/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'النص المقترح',
'assistant_back' => 'رجوع',
'assistant_error' => 'تعذر إنشاء اقتراح. حاول مرة أخرى.',
'ai_style' => 'الأسلوب',
'ai_style_default' => 'الافتراضي',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'هذا الأسلوب غير متاح. اختر الافتراضي أو حاول لاحقًا.',
'ai_model' => 'نموذج الذكاء الاصطناعي',
'ai_model_unavailable_option' => 'النموذج غير متاح',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/de/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
'assistant_suggestion' => 'Vorgeschlagener Text',
'assistant_back' => 'Zurück',
'assistant_error' => 'Der Vorschlag konnte nicht erstellt werden. Bitte versuche es erneut.',
'ai_style' => 'Stil',
'ai_style_default' => 'Standard',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Dieser Schreibstil ist nicht verfügbar. Wähle Standard oder versuche es später erneut.',
'ai_model' => 'KI-Modell',
'ai_model_unavailable_option' => 'Modell nicht verfügbar',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/el/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Προτεινόμενο κείμενο',
'assistant_back' => 'Πίσω',
'assistant_error' => 'Δεν ήταν δυνατή η δημιουργία πρότασης. Δοκιμάστε ξανά.',
'ai_style' => 'Ύφος',
'ai_style_default' => 'Προεπιλογή',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Αυτό το ύφος δεν είναι διαθέσιμο. Επιλέξτε το προεπιλεγμένο ή δοκιμάστε ξανά αργότερα.',
'ai_model' => 'Μοντέλο AI',
'ai_model_unavailable_option' => 'Μη διαθέσιμο μοντέλο',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/en/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Suggested caption',
'assistant_back' => 'Back',
'assistant_error' => 'Could not generate a suggestion. Please try again.',
'ai_style' => 'Style',
'ai_style_default' => 'Default',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'This writing style is unavailable. Choose Default or try again later.',
'ai_model' => 'AI model',
'ai_model_unavailable_option' => 'Unavailable model',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/es/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Texto sugerido',
'assistant_back' => 'Volver',
'assistant_error' => 'No se pudo generar una sugerencia. Inténtalo de nuevo.',
'ai_style' => 'Estilo',
'ai_style_default' => 'Predeterminado',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Este estilo no está disponible. Elige Predeterminado o inténtalo más tarde.',
'ai_model' => 'Modelo de IA',
'ai_model_unavailable_option' => 'Modelo no disponible',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/fr/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Texte proposé',
'assistant_back' => 'Retour',
'assistant_error' => 'Impossible de générer une suggestion. Veuillez réessayer.',
'ai_style' => 'Style',
'ai_style_default' => 'Par défaut',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Ce style est indisponible. Choisissez le style par défaut ou réessayez plus tard.',
'ai_model' => 'Modèle IA',
'ai_model_unavailable_option' => 'Modèle indisponible',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/it/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Testo suggerito',
'assistant_back' => 'Indietro',
'assistant_error' => 'Impossibile generare un suggerimento. Riprova.',
'ai_style' => 'Stile',
'ai_style_default' => 'Predefinito',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Questo stile non è disponibile. Scegli Predefinito o riprova più tardi.',
'ai_model' => 'Modello IA',
'ai_model_unavailable_option' => 'Modello non disponibile',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/ja/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => '提案された文章',
'assistant_back' => '戻る',
'assistant_error' => '提案を生成できませんでした。もう一度お試しください。',
'ai_style' => 'スタイル',
'ai_style_default' => '標準',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'この文体は利用できません。標準を選ぶか、後でお試しください。',
'ai_model' => 'AIモデル',
'ai_model_unavailable_option' => '利用できないモデル',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/ko/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => '제안된 문구',
'assistant_back' => '뒤로',
'assistant_error' => '제안을 생성하지 못했습니다. 다시 시도해 주세요.',
'ai_style' => '스타일',
'ai_style_default' => '기본',
'ai_style_df' => 'DF',
'ai_style_unavailable' => '이 문체는 사용할 수 없습니다. 기본을 선택하거나 나중에 다시 시도하세요.',
'ai_model' => 'AI 모델',
'ai_model_unavailable_option' => '사용할 수 없는 모델',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/nl/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Voorgestelde tekst',
'assistant_back' => 'Terug',
'assistant_error' => 'Er kon geen voorstel worden gemaakt. Probeer het opnieuw.',
'ai_style' => 'Stijl',
'ai_style_default' => 'Standaard',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Deze schrijfstijl is niet beschikbaar. Kies Standaard of probeer het later opnieuw.',
'ai_model' => 'AI-model',
'ai_model_unavailable_option' => 'Model niet beschikbaar',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/pl/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Sugerowana treść',
'assistant_back' => 'Wróć',
'assistant_error' => 'Nie udało się wygenerować propozycji. Spróbuj ponownie.',
'ai_style' => 'Styl',
'ai_style_default' => 'Domyślny',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Ten styl jest niedostępny. Wybierz Domyślny lub spróbuj ponownie później.',
'ai_model' => 'Model AI',
'ai_model_unavailable_option' => 'Model niedostępny',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/pt-BR/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Legenda sugerida',
'assistant_back' => 'Voltar',
'assistant_error' => 'Não foi possível gerar uma sugestão. Tente novamente.',
'ai_style' => 'Estilo',
'ai_style_default' => 'Padrão',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Este estilo está indisponível. Escolha Padrão ou tente novamente mais tarde.',
'ai_model' => 'Modelo de IA',
'ai_model_unavailable_option' => 'Modelo indisponível',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/ru/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Предложенный текст',
'assistant_back' => 'Назад',
'assistant_error' => 'Не удалось создать предложение. Попробуйте ещё раз.',
'ai_style' => 'Стиль',
'ai_style_default' => 'По умолчанию',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Этот стиль недоступен. Выберите стиль по умолчанию или повторите попытку позже.',
'ai_model' => 'Модель ИИ',
'ai_model_unavailable_option' => 'Модель недоступна',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/tr/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
'assistant_suggestion' => 'Önerilen metin',
'assistant_back' => 'Geri',
'assistant_error' => 'Öneri oluşturulamadı. Lütfen tekrar deneyin.',
'ai_style' => 'Stil',
'ai_style_default' => 'Varsayılan',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Bu yazım stili kullanılamıyor. Varsayılanı seçin veya daha sonra tekrar deneyin.',
'ai_model' => 'Yapay zekâ modeli',
'ai_model_unavailable_option' => 'Model kullanılamıyor',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/uk/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => 'Запропонований текст',
'assistant_back' => 'Назад',
'assistant_error' => 'Не вдалося створити пропозицію. Спробуйте ще раз.',
'ai_style' => 'Стиль',
'ai_style_default' => 'Типовий',
'ai_style_df' => 'DF',
'ai_style_unavailable' => 'Цей стиль недоступний. Виберіть типовий стиль або повторіть спробу пізніше.',
'ai_model' => 'Модель ШІ',
'ai_model_unavailable_option' => 'Модель недоступна',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
4 changes: 4 additions & 0 deletions lang/zh/posts.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
'assistant_suggestion' => '建议文案',
'assistant_back' => '返回',
'assistant_error' => '无法生成建议,请重试。',
'ai_style' => '风格',
'ai_style_default' => '默认',
'ai_style_df' => 'DF',
'ai_style_unavailable' => '此写作风格不可用。请选择默认风格或稍后重试。',
'ai_model' => 'AI 模型',
'ai_model_unavailable_option' => '模型不可用',
'ai_model_destination_azure' => 'UZH Azure',
Expand Down
Loading
Loading