Looking for a real person to speak to?
We're here for you!  Just drop in your queries below and we'll connect with you as soon as we can.
",
                "re_fillable": false,
                "use_captcha": false,
                "redirect_url": null,
                "submitted_text": "Great, we've received your message. We'll get back to you as soon as we can :)
",
                "uppercase_labels": false,
                "submit_button_text": "Submit",
                "re_fill_button_text": "Fill Again",
                "color": "#64748b"
            }
        ```
        The form properties can only have one of the following types: 'text', 'number', 'select', 'multi_select', 'date', 'files', 'checkbox', 'url', 'email', 'phone_number', 'signature'.
        All form properties objects need to have the keys 'help', 'name', 'type', 'hidden', 'placeholder', 'prefill'.
        The placeholder property is optional (can be "null") and is used to display a placeholder text in the input field.
        The help property is optional (can be "null") and is used to display extra information about the field.
        For the type "select" and "multi_select", the input object must have a key "select" (or "multi_select") that's mapped to an object like this one:
        ```json
        {
           "options": [
              {"name": 1, "value": 1},
              {"name": 2, "value": 2},
              {"name": 3, "value": 3},
              {"name": 4, "value": 4}
           ]
        }
        ```
        For numerical rating inputs, use a "number" type input and set the property "is_rating" to "true" to turn it into a star rating input. Ex:
        ```json
        {
            "name":"How would you rate your overall experience?",
            "type":"number",
            "is_rating": true
        }
        ```
        If the form is too long, you can paginate it by adding a page break block in the list of properties:
        ```json
        {
            "name":"Page Break",
            "next_btn_text":"Next",
            "previous_btn_text":"Previous",
            "type":"nf-page-break",
        }
        ```
        If you need to add more context to the form, you can add text blocks:
        ```json
        {
            "name":"My Text",
            "type":"nf-text",
            "content": "This is a text block.
"
        }
        ```
        Give me the valid JSON object only, representing the following form: "[REPLACE]"
        Do not ask me for more information about required properties or types, only suggest me a form structure.
    EOD;
    const FORM_DESCRIPTION_PROMPT = <<setAiModel('gpt-3.5-turbo-16k')
            ->useStreaming()
            ->setSystemMessage('You are an assistant helping to generate forms.');
        $completer->completeChat([
            ["role" => "user", "content" => Str::of(self::FORM_STRUCTURE_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString()]
        ], 6000);
        $formData = $completer->getArray();
        $formDescriptionPrompt = Str::of(self::FORM_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString();
        $formShortDescription = $completer->completeChat([
            ["role" => "user", "content" => Str::of(self::FORM_SHORT_DESCRIPTION_PROMPT)->replace('[REPLACE]', $this->argument('prompt'))->toString()]
        ])->getString();
        // If description is between quotes, remove quotes
        $formShortDescription = Str::of($formShortDescription)->replaceMatches('/^"(.*)"$/', '$1')->toString();
        // Get industry & types
        $industry = $this->getIndustries($completer, $this->argument('prompt'));
        $types = $this->getTypes($completer, $this->argument('prompt'));
        // Get Related Templates
        $relatedTemplates = $this->getRelatedTemplates($industry, $types);
        // Now get description and QAs
        $formDescription = $completer->completeChat([
            ["role" => "user", "content" => $formDescriptionPrompt]
        ])->getHtml();
        $formCoverKeywords = $completer->completeChat([
            ["role" => "user", "content" => $formDescriptionPrompt],
            ["role" => "assistant", "content" => $formDescription],
            ["role" => "user", "content" => self::FORM_IMG_KEYWORDS_PROMPT]
        ])->getArray();
        $imageUrl = $this->getImageCoverUrl($formCoverKeywords['search_query']);
        $formQAs = $completer->completeChat([
            ["role" => "user", "content" => $formDescriptionPrompt],
            ["role" => "assistant", "content" => $formDescription],
            ["role" => "user", "content" => self::FORM_QAS_PROMPT]
        ])->getArray();
        $formTitle = $completer->completeChat([
            ["role" => "user", "content" => $formDescriptionPrompt],
            ["role" => "assistant", "content" => $formDescription],
            ["role" => "user", "content" => self::FORM_TITLE_PROMPT]
        ])->getString();
        $template = $this->createFormTemplate(
            $formData,
            $formTitle,
            $formDescription,
            $formShortDescription,
            $formQAs,
            $imageUrl,
            $industry,
            $types,
            $relatedTemplates
        );
        $this->info('/form-templates/' . $template->slug);
        // Set reverse related Templates
        $this->setReverseRelatedTemplates($template);
        return Command::SUCCESS;
    }
    /**
     * Get an image cover URL for the template using unsplash API
     */
    private function getImageCoverUrl($searchQuery): ?string
    {
        $url = 'https://api.unsplash.com/search/photos?query=' . urlencode($searchQuery) . '&client_id=' . config('services.unsplash.access_key');
        $response = Http::get($url)->json();
        $photoIndex = rand(0, max(count($response['results']) - 1, 10));
        if (isset($response['results'][$photoIndex]['urls']['regular'])) {
            return Str::of($response['results'][$photoIndex]['urls']['regular'])->replace('w=1080', 'w=600')->toString();
        }
        return null;
    }
    private function getIndustries(GptCompleter $completer, string $formPrompt): array
    {
        $industriesString = Template::getAllIndustries()->pluck('slug')->join(', ');
        return $completer->completeChat([
            ["role" => "user", "content" => Str::of(self::FORM_INDUSTRY_PROMPT)
                ->replace('[REPLACE]', $formPrompt)
                ->replace('[INDUSTRIES]', $industriesString)
                ->toString()]
        ])->getArray();
    }
    private function getTypes(GptCompleter $completer, string $formPrompt): array
    {
        $typesString = Template::getAllTypes()->pluck('slug')->join(', ');
        return $completer->completeChat([
            ["role" => "user", "content" => Str::of(self::FORM_TYPES_PROMPT)
                ->replace('[REPLACE]', $formPrompt)
                ->replace('[TYPES]', $typesString)
                ->toString()]
        ])->getArray();
    }
    private function getRelatedTemplates(array $industries, array $types): array
    {
        $templateScore = [];
        Template::chunk(100, function ($otherTemplates) use ($industries, $types, &$templateScore) {
            foreach ($otherTemplates as $otherTemplate) {
                $industryOverlap = count(array_intersect($industries ?? [], $otherTemplate->industry ?? []));
                $typeOverlap = count(array_intersect($types ?? [], $otherTemplate->types ?? []));
                $score = $industryOverlap + $typeOverlap;
                if ($score > 1) {
                    $templateScore[$otherTemplate->slug] = $score;
                }
            }
        });
        arsort($templateScore); // Sort by Score
        return array_slice(array_keys($templateScore), 0, self::MAX_RELATED_TEMPLATES);
    }
    private function createFormTemplate(
        array   $formData,
        string  $formTitle,
        string  $formDescription,
        string  $formShortDescription,
        array   $formQAs,
        ?string $imageUrl,
        array   $industry,
        array   $types,
        array   $relatedTemplates
    )
    {
        // Add property uuids, improve form with options
        foreach ($formData['properties'] as &$property) {
            $property['id'] = Str::uuid()->toString(); // Column ID
            // Fix ratings
            if ($property['type'] == 'number' && ($property['is_rating'] ?? false)) {
                $property['rating_max_value'] = 5;
            }
            if (($property['type'] == 'select' && count($property['select']['options']) <= 4)
                || ($property['type'] == 'multi_select' && count($property['multi_select']['options']) <= 4)) {
                $property['without_dropdown'] = true;
            }
        }
        // Clean data
        $formTitle = Str::of($formTitle)->replace('"', '')->toString();
        return Template::create([
            'name' => $formTitle,
            'description' => $formDescription,
            'short_description' => $formShortDescription,
            'questions' => $formQAs,
            'structure' => $formData,
            'image_url' => $imageUrl,
            'publicly_listed' => true,
            'industries' => $industry,
            'types' => $types,
            'related_templates' => $relatedTemplates
        ]);
    }
    private function setReverseRelatedTemplates(Template $newTemplate)
    {
        if (!$newTemplate || count($newTemplate->related_templates) === 0) return;
        $templates = Template::whereIn('slug', $newTemplate->related_templates)->get();
        foreach ($templates as $template) {
            if (count($template->related_templates) < self::MAX_RELATED_TEMPLATES) {
                $template->update(['related_templates' => array_merge($template->related_templates, [$newTemplate->slug])]);
            }
        }
    }
}