diff --git a/assets/app.js b/assets/app.js index d76b7c8..f3e7dc1 100644 --- a/assets/app.js +++ b/assets/app.js @@ -4,4 +4,5 @@ import './styles/app.css'; import './styles/audio.css'; import './styles/blog.css'; import './styles/youtube.css'; +import './styles/video.css'; import './styles/wikipedia.css'; diff --git a/assets/controllers/video_controller.js b/assets/controllers/video_controller.js new file mode 100644 index 0000000..fbb8e03 --- /dev/null +++ b/assets/controllers/video_controller.js @@ -0,0 +1,43 @@ +import {Controller} from '@hotwired/stimulus'; +import {getComponent} from '@symfony/ux-live-component'; + +export default class extends Controller { + async initialize() { + this.component = await getComponent(this.element); + + this.video = document.getElementById('videoFeed'); + this.canvas = document.getElementById('canvas'); + + const input = document.getElementById('chat-message'); + input.addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + this.submitMessage(); + } + }); + input.focus(); + + const submitButton = document.getElementById('chat-submit'); + submitButton.addEventListener('click', (event) => { + this.submitMessage(); + }); + + this.video.srcObject = await navigator.mediaDevices.getUserMedia({video: true, audio: false}); + }; + + submitMessage() { + const input = document.getElementById('chat-message'); + const instruction = input.value; + const image = this.captureImage(); + + this.component.action('submit', { instruction, image }); + input.value = ''; + } + + captureImage() { + this.canvas.width = this.video.videoWidth; + this.canvas.height = this.video.videoHeight; + const context = this.canvas.getContext('2d'); + context.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height); + return this.canvas.toDataURL('image/jpeg', 0.8); + } +} diff --git a/assets/icons/tabler/video-filled.svg b/assets/icons/tabler/video-filled.svg new file mode 100644 index 0000000..c16aab2 --- /dev/null +++ b/assets/icons/tabler/video-filled.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/styles/video.css b/assets/styles/video.css new file mode 100644 index 0000000..98c3c78 --- /dev/null +++ b/assets/styles/video.css @@ -0,0 +1,27 @@ +.video { + body&, .card-img-top { + background: #26931e; + background: linear-gradient(0deg, #186361 0%, #26931e 100%); + } + + .card-img-top { + color: #ffffff; + } + + &.chat { + #chat-submit { + &:hover { + background: #186361; + border-color: #186361; + } + } + } + + footer { + color: #ffffff; + + a { + color: #ffffff; + } + } +} diff --git a/config/routes.yaml b/config/routes.yaml index 8cf42f8..8cd723c 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -25,6 +25,13 @@ youtube: template: 'chat.html.twig' context: { chat: 'youtube' } +video: + path: '/video' + controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController' + defaults: + template: 'chat.html.twig' + context: { chat: 'video' } + wikipedia: path: '/wikipedia' controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController' diff --git a/demo.png b/demo.png index d62462c..8007e32 100644 Binary files a/demo.png and b/demo.png differ diff --git a/src/Video/TwigComponent.php b/src/Video/TwigComponent.php new file mode 100644 index 0000000..ae5ea12 --- /dev/null +++ b/src/Video/TwigComponent.php @@ -0,0 +1,55 @@ +platform->request(new GPT(GPT::GPT_4O_MINI), $messageBag, [ + 'max_tokens' => 100, + ]); + + assert($response instanceof AsyncResponse); + $response = $response->unwrap(); + assert($response instanceof TextResponse); + + $this->caption = $response->getContent(); + } +} diff --git a/templates/base.html.twig b/templates/base.html.twig index a1a49a7..18ca07e 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -34,6 +34,9 @@
Simple demonstration of text-to-speech with Whisper in combination with GPT.
+Simple demonstration of speech-to-text with Whisper in combination with GPT.
Try Audio BotSimple demonstration of vision capabilities of GPT in combination with your webcam.
+ Try Video Bot +