Merge pull request #1 from investbrainapp/dev

feat: Improved ai chat
This commit is contained in:
hackerESQ
2024-11-01 23:21:34 -05:00
committed by GitHub
2 changed files with 161 additions and 57 deletions
+21 -3
View File
@@ -6,15 +6,33 @@
display: none;
}
.ai-chat ul {
.ai-chat ul, .ai-chat ol, .ai-chat ol li > ul {
margin-left: 1.1rem;
}
.ai-chat ul li {
.ai-chat ul > li {
padding-left: 1.1rem;
list-style-type: disc;
}
.ai-chat ol > li {
padding-left: 1.1rem;
list-style-type: decimal;
}
.ai-chat ol li li {
padding-left: 0rem;
list-style-type: none;
}
.ai-chat ol li li::before {
content: " - ";
}
.ai-chat li, .ai-chat p {
padding-bottom: .25em;
padding-bottom: .25rem;
}
.ai-chat code {
font-size: .75rem;
}
+140 -54
View File
@@ -60,17 +60,19 @@ new class extends Component {
$this->resetPrompt();
$this->streaming = true;
$this->js('$wire.generate()');
$this->js('$wire.generateCompletion()');
}
public function generate(): void
public function generateCompletion(): void
{
try {
$stream = OpenAI::chat()->createStreamed([
'model' => config('openai.model'),
'messages' => [
['role' => 'system', 'content' => $this->system_prompt],
['role' => 'system', 'content' => "Today's date is "
.now()->format('Y-m-d')
.".\n\n".$this->system_prompt],
...array_slice($this->messages, -10)
],
]);
@@ -96,6 +98,76 @@ new class extends Component {
$this->chatable->chats()->save(new AiChat(['role' => 'assistant', 'content' => $this->answer]));
array_push($this->messages, ['role' => 'assistant', 'content' => $this->answer]);
$this->resetPrompt();
$this->js('$wire.generateSuggestedPrompts()');
}
public function generateSuggestedPrompts(): void
{
try {
$suggested_prompts = OpenAI::chat()->create([
'model' => config('openai.model'),
'response_format' => [
'type' => 'json_schema',
'json_schema' => [
'name' => 'suggested_prompts_schema',
'strict' => true,
'schema' => [
"type" => "object",
"properties" => [
"suggested_prompts" => [
"type" => "array",
"items" => [
"type" => "object",
"properties" => [
"text" => [
"type" => "string",
"description" => "The prompt question (no more than 5 words)"
],
"value" => [
"type" => "string",
"description" => "The detailed version of the question"
]
],
"required" => ["text", "value"],
"additionalProperties" => false
]
]
],
"required" => ["suggested_prompts"],
"additionalProperties" => false
]
]
],
'messages' => [
['role' => 'system', 'content' => "
Your role is to assist investors in asking thoughtful questions to their investment advisors.
When you help investors ask good questions, you should ensure the questions are based on the
provided context. Be sure to keep the questions short!
The questions you recommend might be based on natural follow up from the given context, requests
to clarify undefined terms, common decision frameworks, possible risks or benefits, or commonly
understood investing concepts.
Your response should only include valid JSON.
"],
['role' => 'user', 'content' => "
Generate a list of ". rand(1,5) ." follow up questions a savvy investor might ask their advisor
based on the following conversation:
\n\n
".json_encode(array_slice($this->messages, -4))
],
],
]);
$this->suggested_prompts = json_decode($suggested_prompts->choices[0]->message->content, true)['suggested_prompts'];
} catch (\Exception $e) {
$this->suggested_prompts = [];
$this->error($e->getMessage());
return;
}
}
public function resetPrompt(): void
@@ -132,41 +204,53 @@ new class extends Component {
});
}
}"
class="flex flex-col"
class="fixed z-50 bottom-8 right-8"
>
{{-- toggle button --}}
<x-button
x-show="!open"
@click="$dispatch('toggle-ai-chat')"
class="btn btn-circle btn-lg btn-primary fixed bottom-10 right-10"
class="flex btn btn-circle md:btn-lg btn-primary"
>
<x-slot:label>
<x-icon name="o-sparkles" class="w-8 h-8"></x-icon>
<x-icon name="o-sparkles" class="w-6 h-6 md:w-8 md:h-8"></x-icon>
</x-slot:label>
</x-button>
{{-- popup --}}
<div
x-on:toggle-ai-chat.window="open = !open"
x-show="open"
x-trap="open"
x-bind:inert="!open"
x-transition.opacity
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform translate-y-full"
x-transition:enter-end="opacity-100 transform translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform translate-y-full"
x-cloak
key="ai-chat"
class="fixed
bottom-0 right-0 w-full h-screen
md:bottom-[7rem] md:right-10 md:w-[35rem] md:h-auto"
class="fixed bg-base-100 shadow-2xl rounded-none md:rounded-lg
inset-0 h-screen w-full
md:inset-auto md:right-6 md:bottom-6 md:w-[32rem] md:h-[46rem]"
>
<x-card class="shadow-2xl" title="{{ __('AI Chat') }}" x-intersect="scrollChatWindow()">
{{-- close button --}}
<x-button
icon="o-x-mark"
class="absolute top-5 right-4 btn-ghost btn-circle btn-sm"
title="{{ __('Close') }}"
@click="open = false"
/>
<div
class="absolute inset-0 flex flex-col overflow-hidden p-4"
x-intersect="scrollChatWindow()"
>
<div class="flex grow-0 justify-between items-center pb-4 ">
<h2 class="text-lg text-bold">{{ __('AI Chat') }}</h2>
<x-button
icon="o-x-mark"
class="absolute top-5 right-4 btn-ghost btn-circle btn-sm"
title="{{ __('Close') }}"
@click="open = false"
/>
</div>
{{-- chat window --}}
<div class="h-[25rem] overflow-y-scroll ai-chat" x-ref="chatWindow">
<div class="grow overflow-hidden overflow-y-scroll ai-chat" x-ref="chatWindow">
<div class="flex gap-3 mb-5 flex-1">
<span class="
@@ -248,42 +332,44 @@ new class extends Component {
</div>
{{-- prompt input --}}
<form submit="startCompletion" class="mt-3">
<div class="">
@foreach($suggested_prompts as $prompt)
<x-button
class="btn-xs btn-primary btn-outline mr-1 mb-2"
label="{{ $prompt['text'] }}"
wire:click="startCompletion('{{ $prompt['value'] }}')"
/>
@endforeach
</div>
<div class="flex justify-between align-bottom space-x-2 mt-1">
<div class="w-full">
<div class="mt-3 grow-0">
<form submit="startCompletion" >
<div class="">
@foreach($suggested_prompts as $prompt)
<x-button
class="btn-xs btn-primary btn-outline mr-1 mb-2"
wire:click="startCompletion('{{ addslashes($prompt['value']) }}')"
>{{ $prompt['text'] }}</x-button>
@endforeach
<x-textarea
wire:model="prompt"
class="h-24 resize-none "
placeholder="{{ __('Have a question? AI might be able to help...') }}"
wire:keydown.enter.prevent="startCompletion"
></x-textarea>
</div>
<x-button
spinner="generate"
wire:click="startCompletion"
class="btn btn-ghost h-24"
icon="o-paper-airplane"
></x-button>
</div>
<div class="w-full mt-2">
<p class="text-xs text-secondary leading-tight">{{ __('Advice generated by AI may contain errors. Use at your own risk. Always consult a licensed investment advisor.') }} </p>
</div>
</form>
</x-card>
<div class="flex justify-between align-bottom space-x-2 mt-1">
<div class="w-full">
<x-textarea
wire:model="prompt"
class="h-24 resize-none "
placeholder="{{ __('Have a question? AI might be able to help...') }}"
wire:keydown.enter.prevent="startCompletion"
autofocus
></x-textarea>
</div>
<x-button
spinner="generateCompletion"
wire:click="startCompletion"
class="btn btn-ghost h-24"
icon="o-paper-airplane"
></x-button>
</div>
<div class="w-full mt-2">
<p class="text-xs text-secondary leading-tight">{{ __('Advice generateCompletiond by AI may contain errors. Use at your own risk. Always consult a licensed investment advisor.') }} </p>
</div>
</form>
</div>
</div>
</div>
</div>