*/ public function generateForTemplate(Template $template, string $topic, AiProvider $provider): array { $textKeys = $template->keys->where('type', TemplateKeyType::Text); if ($textKeys->isEmpty()) { return []; } $keysDescription = $textKeys ->map(fn (TemplateKey $key): string => sprintf('- %s (%s)%s', $key->key, $key->label, $key->description ? ': '.$key->description : '')) ->implode("\n"); $systemPrompt = <<api_key) ->timeout(60) ->post(rtrim($provider->resolvedBaseUrl(), '/').'/chat/completions', [ 'model' => $provider->model, 'response_format' => ['type' => 'json_object'], 'messages' => [ ['role' => 'system', 'content' => $systemPrompt], ['role' => 'user', 'content' => $topic], ], ]); if ($response->failed()) { throw new RuntimeException('خطا در ارتباط با سرویس هوش مصنوعی: '.$response->body()); } $decoded = json_decode((string) $response->json('choices.0.message.content'), true); if (! is_array($decoded)) { throw new RuntimeException('پاسخ هوش مصنوعی قابل تفسیر نبود.'); } $normalizedDecoded = []; foreach ($decoded as $decodedKey => $decodedValue) { $normalizedDecoded[$this->normalizeKey((string) $decodedKey)] = $decodedValue; } $values = []; foreach ($textKeys as $key) { $normalizedKey = $this->normalizeKey($key->key); if (array_key_exists($normalizedKey, $normalizedDecoded)) { $values[$key->key] = (string) $normalizedDecoded[$normalizedKey]; } } return $values; } /** * Generates a full slide outline from free-form text: a set of fixed "title_values" * (filled once for the whole deck) plus exactly $slideCount "slides", each carrying * its own values for the template's repeatable keys. * * @return array{title_values: array, slides: array>} */ public function generateOutline(Template $template, string $title, string $sourceText, int $slideCount, string $language, AiProvider $provider): array { $textKeys = $template->keys->where('type', TemplateKeyType::Text); $fixedKeys = $textKeys->where('is_repeatable', false); $repeatableKeys = $textKeys->where('is_repeatable', true); if ($repeatableKeys->isEmpty()) { throw new RuntimeException('کلید تکرارشونده‌ای برای این قالب تعریف نشده است.'); } $describeKeys = fn ($keys): string => $keys ->map(fn (TemplateKey $key): string => sprintf('- %s (%s)%s', $key->key, $key->label, $key->description ? ': '.$key->description : '')) ->implode("\n"); $fixedKeysDescription = $fixedKeys->isEmpty() ? 'هیچ' : $describeKeys($fixedKeys); $repeatableKeysDescription = $describeKeys($repeatableKeys); $systemPrompt = <<api_key) ->timeout(120) ->post(rtrim($provider->resolvedBaseUrl(), '/').'/chat/completions', [ 'model' => $provider->model, 'response_format' => ['type' => 'json_object'], 'messages' => [ ['role' => 'system', 'content' => $systemPrompt], ['role' => 'user', 'content' => $userPrompt], ], ]); if ($response->failed()) { throw new RuntimeException('خطا در ارتباط با سرویس هوش مصنوعی: '.$response->body()); } $decoded = json_decode((string) $response->json('choices.0.message.content'), true); if (! is_array($decoded) || ! is_array($decoded['slides'] ?? null)) { throw new RuntimeException('پاسخ هوش مصنوعی قابل تفسیر نبود.'); } $normalizedTitleValues = $this->normalizeValueMap(is_array($decoded['title_values'] ?? null) ? $decoded['title_values'] : []); $titleValues = []; foreach ($fixedKeys as $key) { $normalizedKey = $this->normalizeKey($key->key); if (array_key_exists($normalizedKey, $normalizedTitleValues)) { $titleValues[$key->key] = (string) $normalizedTitleValues[$normalizedKey]; } } $slides = []; foreach ($decoded['slides'] as $slideData) { if (! is_array($slideData)) { continue; } $normalizedSlideData = $this->normalizeValueMap($slideData); $slideValues = []; foreach ($repeatableKeys as $key) { $normalizedKey = $this->normalizeKey($key->key); if (array_key_exists($normalizedKey, $normalizedSlideData)) { $slideValues[$key->key] = (string) $normalizedSlideData[$normalizedKey]; } } $slides[] = $slideValues; } if ($slides === []) { throw new RuntimeException('هوش مصنوعی محتوایی برای اسلایدها تولید نکرد.'); } return [ 'title_values' => $titleValues, 'slides' => array_slice($slides, 0, $slideCount), ]; } /** * @param array $values * @return array */ private function normalizeValueMap(array $values): array { $normalized = []; foreach ($values as $key => $value) { $normalized[$this->normalizeKey((string) $key)] = $value; } return $normalized; } private function normalizeKey(string $key): string { return mb_strtolower(trim($key, "{} \t\n\r\0\x0B")); } }