feat: integrate demo app
* demo-repo/restructure: (31 commits) refactor: restructure into demo folder chore: update to llm-chain 0.22 (#27) refactor: more error handling in vidoe example chore: symfony 7.3 update fix: pin chroma db version and clean up (#26) feat: add demo of GPT vision capabilities based on video stream (#22) chore: dependecy update (#23) feat: extend wikipedia system prompt by tools (#21) chore: update to lib v0.19 (#20) refactor: optimize audio chat ui (#19) feat: audio example (#18) chore: updating dependencies (#17) chore: composer update incl twig cve patch (#16) chore: llm chain update with system_prompt support (#15) chore: install bundle 0.12 (#14) chore: update to llm-chain 0.11.0 (#13) chore: library update (#12) refactor: follow up on example structure to have them cleaner and more separated (#11) fix: typed animation only on xhr responses (#10) refactor: moving classes to a more component like structure (#9) ...
@@ -42,7 +42,7 @@ return (new PhpCsFixer\Config())
|
||||
->setRiskyAllowed(true)
|
||||
->setFinder(
|
||||
(new PhpCsFixer\Finder())
|
||||
->in([__DIR__.'/src', __DIR__.'/examples', __DIR__.'/fixtures'])
|
||||
->in([__DIR__.'/demo', __DIR__.'/examples', __DIR__.'/fixtures', __DIR__.'/src'])
|
||||
->append([__FILE__])
|
||||
->notPath('#/Fixtures/#')
|
||||
)
|
||||
|
||||
23
demo/.env
Normal file
@@ -0,0 +1,23 @@
|
||||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
# https://symfony.com/doc/current/configuration/secrets.html
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=ccb9dca72dce53c683eaaf775bfdb253
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
CHROMADB_HOST=chromadb
|
||||
OPENAI_API_KEY=sk-...
|
||||
7
demo/.env.test
Normal file
@@ -0,0 +1,7 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||
OPENAI_API_KEY=sk-proj-testing1234
|
||||
8
demo/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
Please do not submit any Pull Requests here. They will be closed.
|
||||
---
|
||||
|
||||
Please submit your PR here instead:
|
||||
https://github.com/symfony/ai
|
||||
|
||||
This repository is what we call a "subtree split": a read-only subset of that main repository.
|
||||
We're looking forward to your PR there!
|
||||
20
demo/.github/workflows/close-pull-request.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Close Pull Request
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: |
|
||||
Thanks for your Pull Request! We love contributions.
|
||||
|
||||
However, you should instead open your PR on the main repository:
|
||||
https://github.com/symfony/ai
|
||||
|
||||
This repository is what we call a "subtree split": a read-only subset of that main repository.
|
||||
We're looking forward to your PR there!
|
||||
30
demo/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/dev/dev.decrypt.private.php
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
###> symfony/asset-mapper ###
|
||||
/public/assets/
|
||||
/assets/vendor/
|
||||
###< symfony/asset-mapper ###
|
||||
|
||||
###> php-cs-fixer/shim ###
|
||||
/.php-cs-fixer.php
|
||||
/.php-cs-fixer.cache
|
||||
###< php-cs-fixer/shim ###
|
||||
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
.phpunit.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
chromadb
|
||||
coverage
|
||||
19
demo/LICENSE
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2025-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
92
demo/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Symfony AI - Demo Application
|
||||
|
||||
Symfony application demoing Symfony AI components.
|
||||
|
||||
## Examples
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
What you need to run this demo:
|
||||
|
||||
* Internet Connection
|
||||
* Terminal & Browser
|
||||
* [Git](https://git-scm.com/) & [GitHub Account](https://github.com)
|
||||
* [Docker](https://www.docker.com/) with [Docker Compose Plugin](https://docs.docker.com/compose/)
|
||||
* Your Favorite IDE or Editor
|
||||
* An [OpenAI API Key](https://platform.openai.com/docs/api-reference/create-and-export-an-api-key)
|
||||
|
||||
## Technology
|
||||
|
||||
This small demo sits on top of following technologies:
|
||||
|
||||
* [PHP >= 8.4](https://www.php.net/releases/8.4/en.php)
|
||||
* [Symfony 7.3 incl. Twig, Asset Mapper & UX](https://symfony.com/)
|
||||
* [Bootstrap 5](https://getbootstrap.com/docs/5.0/getting-started/introduction/)
|
||||
* [OpenAI's GPT & Embeddings](https://platform.openai.com/docs/overview)
|
||||
* [ChromaDB Vector Store](https://www.trychroma.com/)
|
||||
* [FrankenPHP](https://frankenphp.dev/)
|
||||
|
||||
## Setup
|
||||
|
||||
The setup is split into three parts, the Symfony application, the OpenAI configuration, and initializing the Chroma DB.
|
||||
|
||||
### 1. Symfony App
|
||||
|
||||
Checkout the repository, start the docker environment and install dependencies:
|
||||
|
||||
```shell
|
||||
git clone git@github.com:symfony/ai-demo.git
|
||||
cd ai-demo
|
||||
docker compose up -d
|
||||
docker compose run composer install
|
||||
```
|
||||
|
||||
Now you should be able to open https://localhost/ in your browser,
|
||||
and the chatbot UI should be available for you to start chatting.
|
||||
|
||||
> [!NOTE]
|
||||
> You might have to bypass the security warning of your browser with regard to self-signed certificates.
|
||||
|
||||
### 2. OpenAI Configuration
|
||||
|
||||
For using GPT and embedding models from OpenAI, you need to configure an OpenAI API key as environment variable.
|
||||
This requires you to have an OpenAI account, create a valid API key and set it as `OPENAI_API_KEY` in `.env.local` file.
|
||||
|
||||
```shell
|
||||
echo "OPENAI_API_KEY='sk-...'" > .env.local
|
||||
```
|
||||
|
||||
Verify the success of this step by running the following command:
|
||||
|
||||
```shell
|
||||
docker compose exec app bin/console debug:dotenv
|
||||
```
|
||||
|
||||
You should be able to see the `OPENAI_API_KEY` in the list of environment variables.
|
||||
|
||||
### 3. Chroma DB Initialization
|
||||
|
||||
The [Chroma DB](https://www.trychroma.com/) is a vector store that is used to store embeddings of the chatbot's context.
|
||||
|
||||
To initialize the Chroma DB, you need to run the following command:
|
||||
|
||||
```shell
|
||||
docker compose exec app bin/console app:blog:embed -vv
|
||||
```
|
||||
|
||||
Now you should be able to run the test command and get some results:
|
||||
|
||||
```shell
|
||||
docker compose exec app bin/console app:blog:query
|
||||
```
|
||||
|
||||
**Don't forget to set up the project in your favorite IDE or editor.**
|
||||
|
||||
## Functionality
|
||||
|
||||
* The chatbot application is a simple and small Symfony 7.3 application.
|
||||
* The UI is coupled to a [Twig LiveComponent](https://symfony.com/bundles/ux-live-component/current/index.html), that integrates different `Chat` implementations on top of the user's session.
|
||||
* You can reset the chat context by hitting the `Reset` button in the top right corner.
|
||||
* You find three different usage scenarios in the upper navbar.
|
||||
8
demo/assets/app.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import './bootstrap.js';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import './styles/app.css';
|
||||
import './styles/audio.css';
|
||||
import './styles/blog.css';
|
||||
import './styles/youtube.css';
|
||||
import './styles/video.css';
|
||||
import './styles/wikipedia.css';
|
||||
5
demo/assets/bootstrap.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||
|
||||
const app = startStimulusApp();
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
30
demo/assets/controllers.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"controllers": {
|
||||
"@symfony/ux-live-component": {
|
||||
"live": {
|
||||
"enabled": true,
|
||||
"fetch": "eager",
|
||||
"autoimport": {
|
||||
"@symfony/ux-live-component/dist/live.min.css": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@symfony/ux-turbo": {
|
||||
"turbo-core": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"mercure-turbo-stream": {
|
||||
"enabled": false,
|
||||
"fetch": "eager"
|
||||
}
|
||||
},
|
||||
"@symfony/ux-typed": {
|
||||
"typed": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entrypoints": []
|
||||
}
|
||||
83
demo/assets/controllers/audio_controller.js
Normal file
@@ -0,0 +1,83 @@
|
||||
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.scrollToBottom();
|
||||
|
||||
const resetButton = document.getElementById('chat-reset');
|
||||
resetButton.addEventListener('click', (event) => {
|
||||
this.component.action('reset');
|
||||
});
|
||||
|
||||
const startButton = document.getElementById('micro-start');
|
||||
const stopButton = document.getElementById('micro-stop');
|
||||
const botThinkingButton = document.getElementById('bot-thinking');
|
||||
|
||||
startButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
startButton.classList.add('d-none');
|
||||
stopButton.classList.remove('d-none');
|
||||
this.startRecording();
|
||||
});
|
||||
stopButton.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
stopButton.classList.add('d-none');
|
||||
botThinkingButton.classList.remove('d-none');
|
||||
this.mediaRecorder.stop();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:started', (e,r) => {
|
||||
if (r.actions.includes('reset')) {
|
||||
return;
|
||||
}
|
||||
document.getElementById('welcome')?.remove();
|
||||
document.getElementById('loading-message').removeAttribute('class');
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:finished', () => {
|
||||
document.getElementById('loading-message').setAttribute('class', 'd-none');
|
||||
botThinkingButton.classList.add('d-none');
|
||||
startButton.classList.remove('d-none');
|
||||
});
|
||||
|
||||
this.component.on('render:finished', () => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
};
|
||||
|
||||
async startRecording() {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
||||
this.mediaRecorder = new MediaRecorder(stream);
|
||||
let audioChunks = [];
|
||||
|
||||
this.mediaRecorder.ondataavailable = (event) => {
|
||||
audioChunks.push(event.data);
|
||||
};
|
||||
|
||||
this.mediaRecorder.onstop = async () => {
|
||||
const audioBlob = new Blob(audioChunks, {type: 'audio/wav'});
|
||||
this.mediaRecorder.stream.getAudioTracks().forEach(track => track.stop());
|
||||
|
||||
const base64String = await this.blobToBase64(audioBlob);
|
||||
this.component.action('submit', { audio: base64String });
|
||||
};
|
||||
|
||||
this.mediaRecorder.start();
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
const chatBody = document.getElementById('chat-body');
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}
|
||||
|
||||
blobToBase64(blob) {
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(blob);
|
||||
reader.onloadend = () => resolve(reader.result.split(',')[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
59
demo/assets/controllers/chat_controller.js
Normal file
@@ -0,0 +1,59 @@
|
||||
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.scrollToBottom();
|
||||
|
||||
const input = document.getElementById('chat-message');
|
||||
input.addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
this.submitMessage();
|
||||
}
|
||||
});
|
||||
input.focus();
|
||||
|
||||
const resetButton = document.getElementById('chat-reset');
|
||||
resetButton.addEventListener('click', (event) => {
|
||||
this.component.action('reset');
|
||||
});
|
||||
|
||||
const submitButton = document.getElementById('chat-submit');
|
||||
submitButton.addEventListener('click', (event) => {
|
||||
this.submitMessage();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:started', (e,r) => {
|
||||
if (r.actions.includes('reset')) {
|
||||
return;
|
||||
}
|
||||
document.getElementById('welcome')?.remove();
|
||||
document.getElementById('loading-message').removeAttribute('class');
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:finished', () => {
|
||||
document.getElementById('loading-message').setAttribute('class', 'd-none');
|
||||
});
|
||||
|
||||
this.component.on('render:finished', () => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
};
|
||||
|
||||
submitMessage() {
|
||||
const input = document.getElementById('chat-message');
|
||||
const message = input.value;
|
||||
document
|
||||
.getElementById('loading-message')
|
||||
.getElementsByClassName('user-message')[0].innerHTML = message;
|
||||
this.component.action('submit', { message });
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
const chatBody = document.getElementById('chat-body');
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}
|
||||
}
|
||||
67
demo/assets/controllers/video_controller.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import {Controller} from '@hotwired/stimulus';
|
||||
import {getComponent} from '@symfony/ux-live-component';
|
||||
|
||||
/**
|
||||
* Heavily inspired by https://github.com/ngxson/smolvlm-realtime-webcam
|
||||
*/
|
||||
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();
|
||||
});
|
||||
|
||||
await this.initCamera();
|
||||
};
|
||||
|
||||
async initCamera() {
|
||||
try {
|
||||
this.stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
|
||||
this.video.srcObject = this.stream;
|
||||
console.log('Camera access granted. Ready to start.');
|
||||
} catch (err) {
|
||||
console.error('Error accessing camera:', err);
|
||||
alert(`Error accessing camera: ${err.name}. Make sure you've granted permission and are on HTTPS or localhost.`);
|
||||
}
|
||||
}
|
||||
|
||||
submitMessage() {
|
||||
const input = document.getElementById('chat-message');
|
||||
const instruction = input.value;
|
||||
const image = this.captureImage();
|
||||
|
||||
if (null === image) {
|
||||
console.warn('No image captured. Cannot submit message.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.component.action('submit', { instruction, image });
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
captureImage() {
|
||||
if (!this.stream || !this.video.videoWidth) {
|
||||
console.warn('Video stream not ready for capture.');
|
||||
return null;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
59
demo/assets/controllers/wikipedia_controller.js
Normal file
@@ -0,0 +1,59 @@
|
||||
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.scrollToBottom();
|
||||
|
||||
const input = document.getElementById('chat-message');
|
||||
input.addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
this.submitMessage();
|
||||
}
|
||||
});
|
||||
input.focus();
|
||||
|
||||
const resetButton = document.getElementById('chat-reset');
|
||||
resetButton.addEventListener('click', (event) => {
|
||||
this.component.action('reset');
|
||||
});
|
||||
|
||||
const submitButton = document.getElementById('chat-submit');
|
||||
submitButton.addEventListener('click', (event) => {
|
||||
this.submitMessage();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:started', (e,r) => {
|
||||
if (r.actions.includes('reset')) {
|
||||
return;
|
||||
}
|
||||
document.getElementById('welcome')?.remove();
|
||||
document.getElementById('loading-message').removeAttribute('class');
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:finished', () => {
|
||||
document.getElementById('loading-message').setAttribute('class', 'd-none');
|
||||
});
|
||||
|
||||
this.component.on('render:finished', () => {
|
||||
this.scrollToBottom();
|
||||
});
|
||||
};
|
||||
|
||||
submitMessage() {
|
||||
const input = document.getElementById('chat-message');
|
||||
const message = input.value;
|
||||
document
|
||||
.getElementById('loading-message')
|
||||
.getElementsByClassName('user-message')[0].innerHTML = message;
|
||||
this.component.action('submit', { message });
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
const chatBody = document.getElementById('chat-body');
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}
|
||||
}
|
||||
95
demo/assets/controllers/youtube_controller.js
Normal file
@@ -0,0 +1,95 @@
|
||||
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.scrollToBottom();
|
||||
|
||||
const input = document.getElementById('chat-message');
|
||||
input.addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
this.submitMessage();
|
||||
}
|
||||
});
|
||||
input.focus();
|
||||
|
||||
const resetButton = document.getElementById('chat-reset');
|
||||
resetButton.addEventListener('click', (event) => {
|
||||
this.component.action('reset');
|
||||
});
|
||||
|
||||
if (document.getElementById('welcome')) {
|
||||
this.initStartButton();
|
||||
}
|
||||
|
||||
const submitButton = document.getElementById('chat-submit');
|
||||
submitButton.addEventListener('click', (event) => {
|
||||
this.submitMessage();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:started', (e,r) => {
|
||||
if (r.actions.includes('reset') || r.actions.includes('start')) {
|
||||
return;
|
||||
}
|
||||
document.getElementById('welcome')?.remove();
|
||||
document.getElementById('loading-message').removeAttribute('class');
|
||||
this.scrollToBottom();
|
||||
});
|
||||
|
||||
this.component.on('loading.state:finished', () => {
|
||||
document.getElementById('loading-message').setAttribute('class', 'd-none');
|
||||
});
|
||||
|
||||
this.component.on('render:finished', () => {
|
||||
this.scrollToBottom();
|
||||
if (document.getElementById('welcome')) {
|
||||
this.initStartButton();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
initStartButton() {
|
||||
const input = document.getElementById('youtube-id');
|
||||
input.disabled = false;
|
||||
input.value = '';
|
||||
input.focus();
|
||||
const startButton = document.getElementById('chat-start');
|
||||
startButton.disabled = false;
|
||||
startButton.addEventListener('click', (event) => {
|
||||
this.start();
|
||||
});
|
||||
document.getElementById('chat-message').disabled = true;
|
||||
document.getElementById('chat-submit').disabled = true;
|
||||
}
|
||||
|
||||
start() {
|
||||
const input = document.getElementById('youtube-id');
|
||||
input.disabled = true;
|
||||
const videoId = input.value;
|
||||
const button = document.getElementById('chat-start');
|
||||
button.disabled = true;
|
||||
button.innerHTML = 'Loading...';
|
||||
document
|
||||
.getElementById('loading-message')
|
||||
.getElementsByClassName('user-message')[0].innerHTML = 'Starting chat for video ID: ' + videoId;
|
||||
this.component.action('start', { videoId });
|
||||
document.getElementById('chat-message').disabled = false;
|
||||
document.getElementById('chat-submit').disabled = false;
|
||||
}
|
||||
|
||||
submitMessage() {
|
||||
const input = document.getElementById('chat-message');
|
||||
const message = input.value;
|
||||
document
|
||||
.getElementById('loading-message')
|
||||
.getElementsByClassName('user-message')[0].innerHTML = message;
|
||||
this.component.action('submit', { message });
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
const chatBody = document.getElementById('chat-body');
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}
|
||||
}
|
||||
1
demo/assets/icons/bi/youtube.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16"><path fill="currentColor" d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104l.022.26l.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105l-.009.104c-.05.572-.124 1.14-.235 1.558a2.01 2.01 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006l-.087-.004l-.171-.007l-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.01 2.01 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31 31 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103l.003-.052l.008-.104l.022-.26l.01-.104c.048-.519.119-1.023.22-1.402a2.01 2.01 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007l.172-.006l.086-.003l.171-.007A100 100 0 0 1 7.858 2zM6.4 5.209v4.818l4.157-2.408z"/></svg>
|
||||
|
After Width: | Height: | Size: 882 B |
1
demo/assets/icons/fluent/bot-24-filled.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M17.753 14a2.25 2.25 0 0 1 2.25 2.25v.904A3.75 3.75 0 0 1 18.696 20c-1.565 1.344-3.806 2-6.696 2s-5.128-.656-6.69-2a3.75 3.75 0 0 1-1.306-2.843v-.908A2.25 2.25 0 0 1 6.254 14zM11.9 2.006L12 2a.75.75 0 0 1 .743.648l.007.102l-.001.749h3.5a2.25 2.25 0 0 1 2.25 2.25v4.505a2.25 2.25 0 0 1-2.25 2.25h-8.5a2.25 2.25 0 0 1-2.25-2.25V5.75A2.25 2.25 0 0 1 7.75 3.5l3.5-.001V2.75a.75.75 0 0 1 .649-.743L12 2zM9.749 6.5a1.25 1.25 0 1 0 0 2.498a1.25 1.25 0 0 0 0-2.498m4.493 0a1.25 1.25 0 1 0 0 2.498a1.25 1.25 0 0 0 0-2.498"/></svg>
|
||||
|
After Width: | Height: | Size: 635 B |
1
demo/assets/icons/iconoir/microphone-mute-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><g fill="currentColor" fill-rule="evenodd" stroke-width="1.5" clip-rule="evenodd"><path d="M2.47 2.47a.75.75 0 0 1 1.06 0l18 18a.75.75 0 1 1-1.06 1.06l-18-18a.75.75 0 0 1 0-1.06"/><path d="M8.25 4.384a3.134 3.134 0 0 1 3.134-3.134H12A3.75 3.75 0 0 1 15.75 5v5.5a.75.75 0 0 1-1.23.576L9.378 6.791A3.13 3.13 0 0 1 8.25 4.384M14 14.75A5.75 5.75 0 0 1 8.25 9h1.5A4.25 4.25 0 0 0 14 13.25z"/><path d="M5 9.25a.75.75 0 0 1 .75.75v1a6.25 6.25 0 1 0 12.5 0v-1a.75.75 0 0 1 1.5 0v1a7.75 7.75 0 0 1-15.5 0v-1A.75.75 0 0 1 5 9.25"/><path d="M12 17.25a.75.75 0 0 1 .75.75v3.25H15a.75.75 0 0 1 0 1.5H9a.75.75 0 0 1 0-1.5h2.25V18a.75.75 0 0 1 .75-.75"/></g></svg>
|
||||
|
After Width: | Height: | Size: 674 B |
1
demo/assets/icons/iconoir/microphone-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-width="1.5"><rect width="6" height="12" x="9" y="2" fill="currentColor" rx="3"/><path stroke-linecap="round" stroke-linejoin="round" d="M5 10v1a7 7 0 0 0 7 7v0a7 7 0 0 0 7-7v-1m-7 8v4m0 0H9m3 0h3"/></g></svg>
|
||||
|
After Width: | Height: | Size: 277 B |
1
demo/assets/icons/iconoir/timer-solid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M12 5.25a8.75 8.75 0 1 0 0 17.5a8.75 8.75 0 0 0 0-17.5m.75 4.75a.75.75 0 0 0-1.5 0v4a.75.75 0 0 0 1.5 0zm-4.5-8A.75.75 0 0 1 9 1.25h6a.75.75 0 0 1 0 1.5H9A.75.75 0 0 1 8.25 2" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 277 B |
1
demo/assets/icons/material-symbols/cancel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>
|
||||
|
After Width: | Height: | Size: 381 B |
1
demo/assets/icons/mdi/github.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"/></svg>
|
||||
|
After Width: | Height: | Size: 621 B |
1
demo/assets/icons/mdi/symfony.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m4.37 3.7c1.02-.03 1.78.43 1.84 1.14c.01.31-.17.91-.79.93c-.47.02-.79-.27-.8-.68c-.01-.16.26-.67.26-.76c-.01-.27-.41-.28-.52-.27c-1.5.05-1.9 2.07-2.22 3.72l-.14.87c.84.13 1.46-.03 1.8-.25c.48-.31-.14-.63-.06-.99c.08-.37.41-.54.67-.55c.37-.01.63.37.62.76c-.03.64-.86 1.52-2.53 1.48c-.22 0-.41-.02-.59-.04c-.61 3.1-.99 4.94-2.35 6.52c-1.17 1.39-2.36 1.6-2.89 1.62c-1 .04-1.67-.49-1.67-1.2c-.03-.68.57-1.06.97-1.07c.53-.02.9.37.91.81c.02.37-.18.49-.31.56c-.07.07-.22.15-.21.3c0 .07.07.22.29.21c.42-.01.69-.22.89-.36c.96-.8 1.34-2.21 1.83-4.77c.26-1.45.45-2.38.73-3.3c-.68-.51-1.1-1.15-2.01-1.38c-.63-.19-1.01-.04-1.28.3c-.31.41-.21.93.09 1.24c1.15 1.28 1.49 1.84 1.36 2.6c-.2 1.21-1.64 2.13-3.34 1.61c-1.45-.45-1.72-1.47-1.55-2.04c.16-.49.55-.59.94-.47c.42.13.58.63.46 1.02c-.02.04-.22.41-.27.53c-.09.31.33.52.62.61c.65.2 1.28-.14 1.43-.67c.15-.48-.15-.82-.28-.95c-.89-.98-1.51-1.85-1.21-2.83c.12-.37.36-.77.72-1.04c.75-.55 1.57-.65 2.34-.41c1.01.27 1.49.94 2.12 1.45c.35-1.02.84-2.03 1.57-2.88c.66-.77 1.54-1.33 2.56-1.37"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
demo/assets/icons/mdi/wikipedia.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="m14.97 18.95l-2.56-6.03c-1.02 1.99-2.14 4.08-3.1 6.03c-.01.01-.47 0-.47 0C7.37 15.5 5.85 12.1 4.37 8.68C4.03 7.84 2.83 6.5 2 6.5v-.45h5.06v.45c-.6 0-1.62.4-1.36 1.05c.72 1.54 3.24 7.51 3.93 9.03c.47-.94 1.8-3.42 2.37-4.47c-.45-.88-1.87-4.18-2.29-5c-.32-.54-1.13-.61-1.75-.61c0-.15.01-.25 0-.44l4.46.01v.4c-.61.03-1.18.24-.92.82c.6 1.24.95 2.13 1.5 3.28c.17-.34 1.07-2.19 1.5-3.16c.26-.65-.13-.91-1.21-.91c.01-.12.01-.33.01-.43c1.39-.01 3.48-.01 3.85-.02v.42c-.71.03-1.44.41-1.82.99L13.5 11.3c.18.51 1.96 4.46 2.15 4.9l3.85-8.83c-.3-.72-1.16-.87-1.5-.87v-.45l4 .03v.42c-.88 0-1.43.5-1.75 1.25c-.8 1.79-3.25 7.49-4.85 11.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 744 B |
1
demo/assets/icons/mingcute/send-fill.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><g fill="none"><path d="M24 0v24H0V0zM12.594 23.258l-.012.002l-.071.035l-.02.004l-.014-.004l-.071-.036c-.01-.003-.019 0-.024.006l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.016-.018m.264-.113l-.014.002l-.184.093l-.01.01l-.003.011l.018.43l.005.012l.008.008l.201.092c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022m-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.003-.011l.018-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M20.235 5.686c.432-1.195-.726-2.353-1.921-1.92L3.709 9.048c-1.199.434-1.344 2.07-.241 2.709l4.662 2.699l4.163-4.163a1 1 0 0 1 1.414 1.414L9.544 15.87l2.7 4.662c.638 1.103 2.274.957 2.708-.241z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 886 B |
1
demo/assets/icons/solar/code-linear.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5" d="m17 7.83l1.697 1.526c1.542 1.389 2.313 2.083 2.313 2.974c0 .89-.771 1.585-2.314 2.973L17 16.83M13.987 5L12 12.415l-1.987 7.415M7 7.83L5.304 9.356C3.76 10.745 2.99 11.44 2.99 12.33s.771 1.585 2.314 2.973L7 16.83"/></svg>
|
||||
|
After Width: | Height: | Size: 329 B |
1
demo/assets/icons/solar/user-bold.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><circle cx="12" cy="6" r="4" fill="currentColor"/><path fill="currentColor" d="M20 17.5c0 2.485 0 4.5-8 4.5s-8-2.015-8-4.5S7.582 13 12 13s8 2.015 8 4.5"/></svg>
|
||||
|
After Width: | Height: | Size: 245 B |
1
demo/assets/icons/symfony.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 257"><circle cx="128" cy="128.827" r="128" fill="#1a171b"/><path fill="#fff" d="M183.706 48.124c-12.986.453-24.32 7.61-32.757 17.51c-9.342 10.855-15.557 23.73-20.035 36.872c-8.01-6.565-14.19-15.064-27.041-18.77c-9.933-2.852-20.366-1.674-29.96 5.474c-4.545 3.395-7.676 8.527-9.165 13.351c-3.855 12.537 4.053 23.694 7.645 27.7l7.853 8.416c1.619 1.65 5.518 5.955 3.612 12.127c-2.06 6.71-10.15 11.055-18.448 8.495c-3.706-1.13-9.03-3.891-7.838-7.779c.493-1.59 1.631-2.78 2.241-4.155c.56-1.181.827-2.067.997-2.587c1.516-4.95-.555-11.39-5.857-13.025c-4.946-1.516-10.007-.315-11.969 6.054c-2.225 7.235 1.237 20.366 19.783 26.084c21.729 6.676 40.11-5.155 42.717-20.586c1.642-9.665-2.722-16.845-10.717-26.08l-6.514-7.204c-3.946-3.942-5.301-10.661-1.217-15.825c3.446-4.356 8.354-6.215 16.392-4.029c11.733 3.186 16.963 11.327 25.69 17.893c-3.603 11.819-5.958 23.682-8.09 34.32l-1.299 7.931c-6.238 32.721-11 50.688-23.375 61.003c-2.493 1.773-6.057 4.427-11.429 4.612c-2.816.087-3.726-1.85-3.765-2.694c-.067-1.977 1.599-2.883 2.706-3.773c1.654-.902 4.155-2.398 3.985-7.191c-.18-5.664-4.872-10.575-11.654-10.35c-5.08.173-12.823 4.954-12.532 13.705c.303 9.039 8.728 15.813 21.43 15.384c6.79-.233 21.952-2.997 36.895-20.76c17.392-20.362 22.256-43.705 25.915-60.79l4.084-22.556c2.269.272 4.695.453 7.334.516c21.661.457 32.496-10.763 32.657-18.924c.107-4.939-3.241-9.799-7.928-9.689c-3.355.095-7.57 2.328-8.582 6.968c-.988 4.552 6.893 8.66.733 12.65c-4.376 2.832-12.221 4.828-23.269 3.206l2.009-11.103c4.1-21.055 9.157-46.954 28.341-47.584c1.398-.071 6.514.063 6.633 3.446c.035 1.13-.245 1.418-1.568 4.005c-1.347 2.017-1.855 3.734-1.792 5.707c.185 5.376 4.273 8.909 10.185 8.696c7.916-.256 10.193-7.963 10.063-11.921c-.32-9.3-10.122-15.175-23.1-14.75"/></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
demo/assets/icons/tabler/video-filled.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor"><path d="M20.117 7.625a1 1 0 0 0-.564.1L15 10v4l4.553 2.275A1 1 0 0 0 21 15.383V8.617a1 1 0 0 0-.883-.992"/><path d="M5 5C3.355 5 2 6.355 2 8v8c0 1.645 1.355 3 3 3h8c1.645 0 3-1.355 3-3V8c0-1.645-1.355-3-3-3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 305 B |
65
demo/assets/styles/app.css
Normal file
@@ -0,0 +1,65 @@
|
||||
body {
|
||||
min-height: 100vh;
|
||||
|
||||
footer, footer a {
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.index {
|
||||
.card-img-top {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.chat {
|
||||
.card {
|
||||
border: 1px solid #bcbcbc;
|
||||
background-color: rgba(250, 250, 250, 0.9);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: #efefef;
|
||||
|
||||
svg {
|
||||
margin-top: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
height: 700px;
|
||||
|
||||
.user-message {
|
||||
border-radius: 10px 10px 0 10px;
|
||||
color: #292929;
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
border-radius: 10px 10px 10px 0;
|
||||
color: #292929;
|
||||
|
||||
&.loading {
|
||||
color: rgba(41, 41, 41, 0.5);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 2px solid white;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
background: #efefef;
|
||||
|
||||
input:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
demo/assets/styles/audio.css
Normal file
@@ -0,0 +1,73 @@
|
||||
.audio {
|
||||
body&, .card-img-top {
|
||||
background: #df662f;
|
||||
background: linear-gradient(0deg, #df662f 0%, #a80a1d 100%);
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&.chat {
|
||||
.user-message {
|
||||
background: #df662f;
|
||||
color: #ffffff;
|
||||
|
||||
#loading-message & {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
color: #ffffff;
|
||||
background: #215d9a;
|
||||
|
||||
&.loading {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
a {
|
||||
color: #c8d8ef;
|
||||
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
color: #ffb1ca;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
&.bot {
|
||||
outline: 1px solid #c0dbf4;
|
||||
background: #c0dbf4;
|
||||
}
|
||||
|
||||
&.user {
|
||||
outline: 1px solid #f3b396;
|
||||
background: #f3b396;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome h4 {
|
||||
color: #2c5282;
|
||||
}
|
||||
|
||||
#chat-reset, #chat-submit {
|
||||
&:hover {
|
||||
background: #a80a1d;
|
||||
border-color: #a80a1d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
color: #ffffff;
|
||||
|
||||
a {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
demo/assets/styles/blog.css
Normal file
@@ -0,0 +1,61 @@
|
||||
.blog {
|
||||
body&, .card-img-top {
|
||||
background: #2c5282;
|
||||
background: linear-gradient(0deg, #2c5282 0%, #3c366b 100%);
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&.chat {
|
||||
.user-message {
|
||||
background: #d5054e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
color: #ffffff;
|
||||
background: #3182ce;
|
||||
|
||||
&.loading {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
a {
|
||||
color: #c8d8ef;
|
||||
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
color: #ffb1ca;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
&.bot {
|
||||
outline: 1px solid #b8d8fb;
|
||||
background: #b8d8fb;
|
||||
}
|
||||
|
||||
&.user {
|
||||
outline: 1px solid #ffb1ca;
|
||||
background: #ffb1ca;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome h4 {
|
||||
color: #2c5282;
|
||||
}
|
||||
|
||||
#chat-reset, #chat-submit {
|
||||
&:hover {
|
||||
background: #d5054e;
|
||||
border-color: #d5054e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
demo/assets/styles/video.css
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
demo/assets/styles/wikipedia.css
Normal file
@@ -0,0 +1,31 @@
|
||||
.wikipedia {
|
||||
body&, .card-img-top {
|
||||
background: url('/wiki.png') no-repeat right 50px bottom 50px fixed, linear-gradient(0deg, rgb(246, 246, 246) 0%, rgb(197, 197, 197) 100%);
|
||||
}
|
||||
|
||||
&.chat {
|
||||
.card-body {
|
||||
background-image: linear-gradient(135deg, #f2f2f2 16.67%, #ebebeb 16.67%, #ebebeb 50%, #f2f2f2 50%, #f2f2f2 66.67%, #ebebeb 66.67%, #ebebeb 100%);
|
||||
background-size: 21.21px 21.21px;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
background: #ffffff;
|
||||
|
||||
a {
|
||||
color: #3e2926;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
&.bot, &.user {
|
||||
outline: 1px solid #eaeaea;
|
||||
background: #eaeaea;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
demo/assets/styles/youtube.css
Normal file
@@ -0,0 +1,49 @@
|
||||
.youtube {
|
||||
body&, .card-img-top {
|
||||
background: rgb(34,34,34);
|
||||
background: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(71, 71, 71) 100%);
|
||||
}
|
||||
|
||||
.card-img-top {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
&.chat {
|
||||
.user-message {
|
||||
background: #3e2926;
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
color: #ffffff;
|
||||
background: #df3535;
|
||||
|
||||
&.loading {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
&.bot {
|
||||
outline: 1px solid #ffcccc;
|
||||
background: #ffcccc;
|
||||
}
|
||||
|
||||
&.user {
|
||||
outline: 1px solid #9e8282;
|
||||
background: #9e8282;
|
||||
}
|
||||
}
|
||||
|
||||
#welcome h4 {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
#chat-reset, #chat-submit {
|
||||
&:hover {
|
||||
background: #ff0000;
|
||||
border-color: #ff0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
demo/bin/console
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
if (!is_dir(dirname(__DIR__).'/vendor')) {
|
||||
throw new LogicException('Dependencies are missing. Try running "composer install".');
|
||||
}
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
|
||||
return new Application($kernel);
|
||||
};
|
||||
30
demo/compose.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
app:
|
||||
image: dunglas/frankenphp
|
||||
volumes:
|
||||
- ./:/app
|
||||
- ../src/agent:/src/agent
|
||||
- ../src/ai-bundle:/src/ai-bundle
|
||||
- ../src/platform:/src/platform
|
||||
- ../src/store:/src/store
|
||||
ports:
|
||||
- 443:443
|
||||
tty: true
|
||||
|
||||
composer:
|
||||
image: composer:latest
|
||||
volumes:
|
||||
- ./:/app
|
||||
- ../src/agent:/src/agent
|
||||
- ../src/ai-bundle:/src/ai-bundle
|
||||
- ../src/platform:/src/platform
|
||||
- ../src/store:/src/store
|
||||
|
||||
chromadb:
|
||||
image: chromadb/chroma:0.5.23
|
||||
volumes:
|
||||
- ./chromadb:/chroma/chroma
|
||||
environment:
|
||||
- IS_PERSISTENT=TRUE
|
||||
- PERSIST_DIRECTORY=/chroma/chroma # this is the default path, change it as needed
|
||||
- ANONYMIZED_TELEMETRY=FALSE
|
||||
121
demo/composer.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"name": "symfony-ai/demo",
|
||||
"type": "project",
|
||||
"description": "Symfony AI Demo Application",
|
||||
"license": "MIT",
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"codewithkyrian/chromadb-php": "^0.4.0",
|
||||
"league/commonmark": "^2.7",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"symfony/ai-bundle": "@dev",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/asset-mapper": "7.3.*",
|
||||
"symfony/clock": "7.3.*",
|
||||
"symfony/console": "7.3.*",
|
||||
"symfony/css-selector": "7.3.*",
|
||||
"symfony/dom-crawler": "7.3.*",
|
||||
"symfony/dotenv": "7.3.*",
|
||||
"symfony/flex": "^2.5",
|
||||
"symfony/framework-bundle": "7.3.*",
|
||||
"symfony/http-client": "7.3.*",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/runtime": "7.3.*",
|
||||
"symfony/twig-bundle": "7.3.*",
|
||||
"symfony/uid": "7.3.*",
|
||||
"symfony/ux-icons": "^2.25",
|
||||
"symfony/ux-live-component": "^2.25",
|
||||
"symfony/ux-turbo": "^2.25",
|
||||
"symfony/ux-typed": "^2.25",
|
||||
"symfony/yaml": "7.3.*",
|
||||
"twig/extra-bundle": "^3.21",
|
||||
"twig/markdown-extra": "^3.21",
|
||||
"twig/twig": "^3.21"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-mbstring": "*",
|
||||
"symfony/polyfill-php72": "*",
|
||||
"symfony/polyfill-php73": "*",
|
||||
"symfony/polyfill-php74": "*",
|
||||
"symfony/polyfill-php80": "*",
|
||||
"symfony/polyfill-php81": "*",
|
||||
"symfony/polyfill-php82": "*",
|
||||
"symfony/polyfill-php83": "*",
|
||||
"symfony/polyfill-php84": "*"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"nyholm/nsa": "^1.3",
|
||||
"php-cs-fixer/shim": "^3.75",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^11.5",
|
||||
"symfony/browser-kit": "7.3.*",
|
||||
"symfony/debug-bundle": "7.3.*",
|
||||
"symfony/stopwatch": "7.3.*",
|
||||
"symfony/web-profiler-bundle": "7.3.*"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true,
|
||||
"symfony/flex": true,
|
||||
"symfony/runtime": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.4.7"
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "7.3.*"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
||||
"importmap:install": "symfony-cmd"
|
||||
},
|
||||
"pipeline": [
|
||||
"composer validate --strict",
|
||||
"php -l src/**/*.php tests/**/*.php",
|
||||
"bin/console lint:twig templates",
|
||||
"bin/console lint:yaml config",
|
||||
"bin/console lint:container",
|
||||
"PHP_CS_FIXER_IGNORE_ENV=1 vendor/bin/php-cs-fixer fix",
|
||||
"phpstan analyse",
|
||||
"XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=coverage"
|
||||
]
|
||||
},
|
||||
"repositories": [
|
||||
{ "type": "path", "url": "../src/agent" },
|
||||
{ "type": "path", "url": "../src/ai-bundle" },
|
||||
{ "type": "path", "url": "../src/platform" },
|
||||
{ "type": "path", "url": "../src/store" }
|
||||
]
|
||||
}
|
||||
8667
demo/composer.lock
generated
Normal file
26
demo/config/bundles.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
|
||||
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true],
|
||||
Symfony\UX\LiveComponent\LiveComponentBundle::class => ['all' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||
Symfony\UX\Icons\UXIconsBundle::class => ['all' => true],
|
||||
Symfony\UX\Typed\TypedBundle::class => ['all' => true],
|
||||
Symfony\AI\AIBundle\AIBundle::class => ['all' => true],
|
||||
];
|
||||
65
demo/config/packages/ai.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
ai:
|
||||
platform:
|
||||
openai:
|
||||
api_key: '%env(OPENAI_API_KEY)%'
|
||||
agent:
|
||||
blog:
|
||||
# platform: 'symfony_ai.platform.anthropic'
|
||||
model:
|
||||
name: 'GPT'
|
||||
version: 'gpt-4o-mini'
|
||||
tools:
|
||||
- 'Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch'
|
||||
- service: 'clock'
|
||||
name: 'clock'
|
||||
description: 'Provides the current date and time.'
|
||||
method: 'now'
|
||||
youtube:
|
||||
model:
|
||||
name: 'GPT'
|
||||
version: 'gpt-4o-mini'
|
||||
tools: false
|
||||
wikipedia:
|
||||
model:
|
||||
name: 'GPT'
|
||||
version: 'gpt-4o-mini'
|
||||
options:
|
||||
temperature: 0.5
|
||||
system_prompt: 'Please answer the users question based on Wikipedia and provide a link to the article.'
|
||||
include_tools: true
|
||||
tools:
|
||||
- 'Symfony\AI\Agent\Toolbox\Tool\Wikipedia'
|
||||
audio:
|
||||
model:
|
||||
name: 'GPT'
|
||||
version: 'gpt-4o-mini'
|
||||
system_prompt: 'You are a friendly chatbot that likes to have a conversation with users and asks them some questions.'
|
||||
tools:
|
||||
# Agent in agent 🤯
|
||||
- service: 'symfony_ai.agent.blog'
|
||||
name: 'symfony_blog'
|
||||
description: 'Can answer questions based on the Symfony blog.'
|
||||
is_agent: true
|
||||
store:
|
||||
chroma_db:
|
||||
symfonycon:
|
||||
collection: 'symfony_blog'
|
||||
indexer:
|
||||
default:
|
||||
model:
|
||||
name: 'Embeddings'
|
||||
version: 'text-embedding-ada-002'
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
# Symfony\AI\Agent\Toolbox\Tool\Clock: ~
|
||||
# Symfony\AI\Agent\Toolbox\Tool\OpenMeteo: ~
|
||||
# Symfony\AI\Agent\Toolbox\Tool\SerpApi:
|
||||
# $apiKey: '%env(SERP_API_KEY)%'
|
||||
Symfony\AI\Agent\Toolbox\Tool\Wikipedia: ~
|
||||
Symfony\AI\Agent\Toolbox\Tool\SimilaritySearch:
|
||||
$model: '@symfony_ai.indexer.default.model'
|
||||
|
||||
11
demo/config/packages/asset_mapper.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
framework:
|
||||
asset_mapper:
|
||||
# The paths to make available to the asset mapper.
|
||||
paths:
|
||||
- assets/
|
||||
missing_import_mode: strict
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
asset_mapper:
|
||||
missing_import_mode: warn
|
||||
19
demo/config/packages/cache.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
framework:
|
||||
cache:
|
||||
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||
#prefix_seed: your_vendor_name/app_name
|
||||
|
||||
# The "app" cache stores to the filesystem by default.
|
||||
# The data in this cache should persist between deploys.
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
# Namespaced pools use the above "app" backend by default
|
||||
#pools:
|
||||
#my.dedicated.cache: null
|
||||
8
demo/config/packages/chromadb.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
Codewithkyrian\ChromaDB\Factory:
|
||||
calls:
|
||||
- withHost: ['%env(CHROMADB_HOST)%']
|
||||
|
||||
Codewithkyrian\ChromaDB\Client:
|
||||
factory: ['@Codewithkyrian\ChromaDB\Factory', 'connect']
|
||||
lazy: true
|
||||
5
demo/config/packages/debug.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
when@dev:
|
||||
debug:
|
||||
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||
# See the "server:dump" command to start a new server.
|
||||
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
||||
16
demo/config/packages/framework.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
#csrf_protection: true
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_factory_id: session.storage.factory.mock_file
|
||||
62
demo/config/packages/monolog.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
monolog:
|
||||
channels:
|
||||
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
|
||||
when@dev:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
||||
|
||||
when@test:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!event"]
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
|
||||
when@prod:
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||
nested:
|
||||
type: stream
|
||||
path: php://stderr
|
||||
level: debug
|
||||
formatter: monolog.formatter.json
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine"]
|
||||
deprecation:
|
||||
type: stream
|
||||
channels: [deprecation]
|
||||
path: php://stderr
|
||||
formatter: monolog.formatter.json
|
||||
3
demo/config/packages/property_info.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
framework:
|
||||
property_info:
|
||||
with_constructor_extractor: true
|
||||
10
demo/config/packages/routing.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
framework:
|
||||
router:
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
#default_uri: http://localhost
|
||||
|
||||
when@prod:
|
||||
framework:
|
||||
router:
|
||||
strict_requirements: null
|
||||
6
demo/config/packages/twig.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
twig:
|
||||
file_name_pattern: '*.twig'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
5
demo/config/packages/twig_component.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
twig_component:
|
||||
anonymous_template_directory: 'components/'
|
||||
defaults:
|
||||
# Namespace & directory for components
|
||||
App\Twig\Components\: 'components/'
|
||||
19
demo/config/packages/web_profiler.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar:
|
||||
enabled: true
|
||||
ajax_replace: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
only_exceptions: false
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
||||
14
demo/config/preload.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||
}
|
||||
40
demo/config/routes.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
index:
|
||||
path: '/'
|
||||
controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController'
|
||||
defaults:
|
||||
template: 'index.html.twig'
|
||||
|
||||
blog:
|
||||
path: '/blog'
|
||||
controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController'
|
||||
defaults:
|
||||
template: 'chat.html.twig'
|
||||
context: { chat: 'blog' }
|
||||
|
||||
audio:
|
||||
path: '/audio'
|
||||
controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController'
|
||||
defaults:
|
||||
template: 'chat.html.twig'
|
||||
context: { chat: 'audio' }
|
||||
|
||||
youtube:
|
||||
path: '/youtube'
|
||||
controller: 'Symfony\Bundle\FrameworkBundle\Controller\TemplateController'
|
||||
defaults:
|
||||
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'
|
||||
defaults:
|
||||
template: 'chat.html.twig'
|
||||
context: { chat: 'wikipedia' }
|
||||
4
demo/config/routes/framework.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
when@dev:
|
||||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
prefix: /_error
|
||||
5
demo/config/routes/ux_live_component.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
live_component:
|
||||
resource: '@LiveComponentBundle/config/routes.php'
|
||||
prefix: '/_components'
|
||||
# adjust prefix to add localization to your components
|
||||
#prefix: '/{_locale}/_components'
|
||||
8
demo/config/routes/web_profiler.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
||||
12
demo/config/secrets/dev/dev.OPENAI_API_KEY.66949e.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return "\x07\x7B\x97\x1E\xD7\x80g\x3F\xE9\x8F\xB6\xFF\x24~\x912\xFF\xD7\x2A\x5B\x1F\xDFS\xCBc\x29\xBD\xA2\xAB\x11\xBC\x0F-E\x7B\xB5\x7C\xD3\xCD\xCC\xF1E\x9B\x140-8\x3A\x84\xB6\xD8\x40\x21\x40x2\x10VNY\x5E\xE6\x9C\xF3\x13\xF1RZ\x14\xF6\xD8\xAC\x9E\x12\xD6\x20\x0C\x1F\x9E\x00\x9B\x3F\xDCv\x06T\x17\x2F\x2C4\x29\xD5\x1E\x0B\xE24\xE3z\xF6\x04\x0Ek\x03\x24";
|
||||
12
demo/config/secrets/dev/dev.encrypt.public.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return "\x60\xB0\x2B\x00\xA2W\x1F\x1E\x0B\x18\xBBT\x5D\x04\xD1S\x1C\x05\x03\x91\xFF\xBA\x5Cnj\x13\xCD\x11\x2C\xEBqD";
|
||||
14
demo/config/secrets/dev/dev.list.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'OPENAI_API_KEY' => null,
|
||||
];
|
||||
20
demo/config/services.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/'
|
||||
exclude:
|
||||
- '../src/DependencyInjection/'
|
||||
- '../src/Entity/'
|
||||
- '../src/Kernel.php'
|
||||
BIN
demo/demo.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
42
demo/importmap.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'app' => [
|
||||
'path' => './assets/app.js',
|
||||
'entrypoint' => true,
|
||||
],
|
||||
'@symfony/ux-live-component' => [
|
||||
'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js',
|
||||
],
|
||||
'@symfony/stimulus-bundle' => [
|
||||
'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
|
||||
],
|
||||
'bootstrap' => [
|
||||
'version' => '5.3.3',
|
||||
],
|
||||
'@popperjs/core' => [
|
||||
'version' => '2.11.8',
|
||||
],
|
||||
'bootstrap/dist/css/bootstrap.min.css' => [
|
||||
'version' => '5.3.3',
|
||||
'type' => 'css',
|
||||
],
|
||||
'@hotwired/stimulus' => [
|
||||
'version' => '3.2.2',
|
||||
],
|
||||
'@hotwired/turbo' => [
|
||||
'version' => '8.0.4',
|
||||
],
|
||||
'typed.js' => [
|
||||
'version' => '2.1.0',
|
||||
],
|
||||
];
|
||||
8
demo/phpstan.dist.neon
Normal file
@@ -0,0 +1,8 @@
|
||||
parameters:
|
||||
level: 8
|
||||
paths:
|
||||
- bin/
|
||||
- config/
|
||||
- public/
|
||||
- src/
|
||||
- tests/
|
||||
30
demo/phpunit.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
colors="true"
|
||||
requireCoverageMetadata="true"
|
||||
beStrictAboutCoverageMetadata="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true">
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
</php>
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
BIN
demo/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
18
demo/public/index.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use App\Kernel;
|
||||
|
||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||
|
||||
return function (array $context) {
|
||||
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||
};
|
||||
BIN
demo/public/wiki.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
79
demo/src/Audio/Chat.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Audio;
|
||||
|
||||
use Symfony\AI\Agent\AgentInterface;
|
||||
use Symfony\AI\Platform\Bridge\OpenAI\Whisper;
|
||||
use Symfony\AI\Platform\Message\Content\Audio;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
use Symfony\AI\Platform\PlatformInterface;
|
||||
use Symfony\AI\Platform\Response\AsyncResponse;
|
||||
use Symfony\AI\Platform\Response\TextResponse;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class Chat
|
||||
{
|
||||
private const SESSION_KEY = 'audio-chat';
|
||||
|
||||
public function __construct(
|
||||
private readonly PlatformInterface $platform,
|
||||
private readonly RequestStack $requestStack,
|
||||
#[Autowire(service: 'symfony_ai.agent.audio')]
|
||||
private readonly AgentInterface $agent,
|
||||
) {
|
||||
}
|
||||
|
||||
public function say(string $base64audio): void
|
||||
{
|
||||
// Convert base64 to temporary binary file
|
||||
$path = tempnam(sys_get_temp_dir(), 'audio-').'.wav';
|
||||
file_put_contents($path, base64_decode($base64audio));
|
||||
|
||||
$response = $this->platform->request(new Whisper(), Audio::fromFile($path));
|
||||
\assert($response instanceof AsyncResponse);
|
||||
$response = $response->unwrap();
|
||||
\assert($response instanceof TextResponse);
|
||||
|
||||
$this->submitMessage($response->getContent());
|
||||
}
|
||||
|
||||
public function loadMessages(): MessageBag
|
||||
{
|
||||
return $this->requestStack->getSession()->get(self::SESSION_KEY, new MessageBag());
|
||||
}
|
||||
|
||||
public function submitMessage(string $message): void
|
||||
{
|
||||
$messages = $this->loadMessages();
|
||||
|
||||
$messages->add(Message::ofUser($message));
|
||||
$response = $this->agent->call($messages);
|
||||
|
||||
\assert($response instanceof TextResponse);
|
||||
|
||||
$messages->add(Message::ofAssistant($response->getContent()));
|
||||
|
||||
$this->saveMessages($messages);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->requestStack->getSession()->remove(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
private function saveMessages(MessageBag $messages): void
|
||||
{
|
||||
$this->requestStack->getSession()->set(self::SESSION_KEY, $messages);
|
||||
}
|
||||
}
|
||||
49
demo/src/Audio/TwigComponent.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Audio;
|
||||
|
||||
use Symfony\AI\Platform\Message\MessageInterface;
|
||||
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
||||
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
||||
|
||||
#[AsLiveComponent('audio')]
|
||||
final class TwigComponent
|
||||
{
|
||||
use DefaultActionTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly Chat $chat,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageInterface[]
|
||||
*/
|
||||
public function getMessages(): array
|
||||
{
|
||||
return $this->chat->loadMessages()->withoutSystemMessage()->getMessages();
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function submit(#[LiveArg] string $audio): void
|
||||
{
|
||||
$this->chat->say($audio);
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function reset(): void
|
||||
{
|
||||
$this->chat->reset();
|
||||
}
|
||||
}
|
||||
70
demo/src/Blog/Chat.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog;
|
||||
|
||||
use Symfony\AI\Agent\AgentInterface;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
use Symfony\AI\Platform\Response\TextResponse;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class Chat
|
||||
{
|
||||
private const SESSION_KEY = 'blog-chat';
|
||||
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
#[Autowire(service: 'symfony_ai.agent.blog')]
|
||||
private readonly AgentInterface $agent,
|
||||
) {
|
||||
}
|
||||
|
||||
public function loadMessages(): MessageBag
|
||||
{
|
||||
$messages = new MessageBag(
|
||||
Message::forSystem(<<<PROMPT
|
||||
You are an helpful assistant that knows about the latest blog content of the Symfony's framework website.
|
||||
To search for content you use the tool 'similarity_search' for generating the answer. Only use content
|
||||
that you get from searching with that tool or your previous answers. Don't make up information and if you
|
||||
can't find something, just say so. Also provide links to the blog posts you use as sources.
|
||||
PROMPT
|
||||
)
|
||||
);
|
||||
|
||||
return $this->requestStack->getSession()->get(self::SESSION_KEY, $messages);
|
||||
}
|
||||
|
||||
public function submitMessage(string $message): void
|
||||
{
|
||||
$messages = $this->loadMessages();
|
||||
|
||||
$messages->add(Message::ofUser($message));
|
||||
$response = $this->agent->call($messages);
|
||||
|
||||
\assert($response instanceof TextResponse);
|
||||
|
||||
$messages->add(Message::ofAssistant($response->getContent()));
|
||||
|
||||
$this->saveMessages($messages);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->requestStack->getSession()->remove(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
private function saveMessages(MessageBag $messages): void
|
||||
{
|
||||
$this->requestStack->getSession()->set(self::SESSION_KEY, $messages);
|
||||
}
|
||||
}
|
||||
41
demo/src/Blog/Command/EmbedCommand.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog\Command;
|
||||
|
||||
use App\Blog\Embedder;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('app:blog:embed', description: 'Create embeddings for Symfony blog and push to ChromaDB.')]
|
||||
final class EmbedCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Embedder $embedder,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Loading RSS of Symfony blog as embeddings into ChromaDB');
|
||||
|
||||
$this->embedder->embedBlog();
|
||||
|
||||
$io->success('Symfony Blog Successfully Embedded!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
79
demo/src/Blog/Command/QueryCommand.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog\Command;
|
||||
|
||||
use Codewithkyrian\ChromaDB\Client;
|
||||
use Symfony\AI\Platform\Bridge\OpenAI\Embeddings;
|
||||
use Symfony\AI\Platform\PlatformInterface;
|
||||
use Symfony\AI\Platform\Response\AsyncResponse;
|
||||
use Symfony\AI\Platform\Response\VectorResponse;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand('app:blog:query', description: 'Test command for querying the blog collection in Chroma DB.')]
|
||||
final class QueryCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Client $chromaClient,
|
||||
private readonly PlatformInterface $platform,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Testing Chroma DB Connection');
|
||||
|
||||
$io->comment('Connecting to Chroma DB ...');
|
||||
$collection = $this->chromaClient->getOrCreateCollection('symfony_blog');
|
||||
$io->table(['Key', 'Value'], [
|
||||
['ChromaDB Version', $this->chromaClient->version()],
|
||||
['Collection Name', $collection->name],
|
||||
['Collection ID', $collection->id],
|
||||
['Total Documents', $collection->count()],
|
||||
]);
|
||||
|
||||
$search = $io->ask('What do you want to know about?', 'New Symfony Features');
|
||||
$io->comment(\sprintf('Converting "%s" to vector & searching in Chroma DB ...', $search));
|
||||
$io->comment('Results are limited to 4 most similar documents.');
|
||||
|
||||
$platformResponse = $this->platform->request(new Embeddings(), $search);
|
||||
\assert($platformResponse instanceof AsyncResponse);
|
||||
$platformResponse = $platformResponse->unwrap();
|
||||
\assert($platformResponse instanceof VectorResponse);
|
||||
$queryResponse = $collection->query(
|
||||
queryEmbeddings: [$platformResponse->getContent()[0]->getData()],
|
||||
nResults: 4,
|
||||
);
|
||||
|
||||
if (1 === \count($queryResponse->ids, \COUNT_RECURSIVE)) {
|
||||
$io->error('No results found!');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
foreach ($queryResponse->ids[0] as $i => $id) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$io->section($queryResponse->metadatas[0][$i]['title']);
|
||||
/* @phpstan-ignore-next-line */
|
||||
$io->block($queryResponse->metadatas[0][$i]['description']);
|
||||
}
|
||||
|
||||
$io->success('Chroma DB Connection & Similarity Search Test Successful!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
35
demo/src/Blog/Embedder.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog;
|
||||
|
||||
use Symfony\AI\Store\Document\Metadata;
|
||||
use Symfony\AI\Store\Document\TextDocument;
|
||||
use Symfony\AI\Store\Indexer;
|
||||
|
||||
final readonly class Embedder
|
||||
{
|
||||
public function __construct(
|
||||
private FeedLoader $loader,
|
||||
private Indexer $indexer,
|
||||
) {
|
||||
}
|
||||
|
||||
public function embedBlog(): void
|
||||
{
|
||||
$documents = [];
|
||||
foreach ($this->loader->load() as $post) {
|
||||
$documents[] = new TextDocument($post->id, $post->toString(), new Metadata($post->toArray()));
|
||||
}
|
||||
|
||||
$this->indexer->index($documents);
|
||||
}
|
||||
}
|
||||
49
demo/src/Blog/FeedLoader.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog;
|
||||
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class FeedLoader
|
||||
{
|
||||
public function __construct(
|
||||
private HttpClientInterface $httpClient,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Post[]
|
||||
*/
|
||||
public function load(): array
|
||||
{
|
||||
$response = $this->httpClient->request('GET', 'https://feeds.feedburner.com/symfony/blog');
|
||||
|
||||
$posts = [];
|
||||
$crawler = new Crawler($response->getContent());
|
||||
$crawler->filter('item')->each(function (Crawler $node) use (&$posts) {
|
||||
$title = $node->filter('title')->text();
|
||||
$posts[] = new Post(
|
||||
Uuid::v5(Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'), $title),
|
||||
$title,
|
||||
$node->filter('link')->text(),
|
||||
$node->filter('description')->text(),
|
||||
(new Crawler($node->filter('content\:encoded')->text()))->text(),
|
||||
$node->filter('dc\:creator')->text(),
|
||||
new \DateTimeImmutable($node->filter('pubDate')->text()),
|
||||
);
|
||||
});
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
||||
62
demo/src/Blog/Post.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog;
|
||||
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
final readonly class Post
|
||||
{
|
||||
public function __construct(
|
||||
public Uuid $id,
|
||||
public string $title,
|
||||
public string $link,
|
||||
public string $description,
|
||||
public string $content,
|
||||
public string $author,
|
||||
public \DateTimeImmutable $date,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return <<<TEXT
|
||||
Title: {$this->title}
|
||||
From: {$this->author} on {$this->date->format('Y-m-d')}
|
||||
Description: {$this->description}
|
||||
{$this->content}
|
||||
TEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* id: string,
|
||||
* title: string,
|
||||
* link: string,
|
||||
* description: string,
|
||||
* content: string,
|
||||
* author: string,
|
||||
* date: string,
|
||||
* }
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id->toRfc4122(),
|
||||
'title' => $this->title,
|
||||
'link' => $this->link,
|
||||
'description' => $this->description,
|
||||
'content' => $this->content,
|
||||
'author' => $this->author,
|
||||
'date' => $this->date->format('Y-m-d'),
|
||||
];
|
||||
}
|
||||
}
|
||||
49
demo/src/Blog/TwigComponent.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Blog;
|
||||
|
||||
use Symfony\AI\Platform\Message\MessageInterface;
|
||||
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
||||
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
||||
|
||||
#[AsLiveComponent('blog')]
|
||||
final class TwigComponent
|
||||
{
|
||||
use DefaultActionTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly Chat $chat,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageInterface[]
|
||||
*/
|
||||
public function getMessages(): array
|
||||
{
|
||||
return $this->chat->loadMessages()->withoutSystemMessage()->getMessages();
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function submit(#[LiveArg] string $message): void
|
||||
{
|
||||
$this->chat->submitMessage($message);
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function reset(): void
|
||||
{
|
||||
$this->chat->reset();
|
||||
}
|
||||
}
|
||||
20
demo/src/Kernel.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||
|
||||
class Kernel extends BaseKernel
|
||||
{
|
||||
use MicroKernelTrait;
|
||||
}
|
||||
62
demo/src/Video/TwigComponent.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Video;
|
||||
|
||||
use Symfony\AI\Platform\Bridge\OpenAI\GPT;
|
||||
use Symfony\AI\Platform\Message\Content\Image;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
use Symfony\AI\Platform\PlatformInterface;
|
||||
use Symfony\AI\Platform\Response\AsyncResponse;
|
||||
use Symfony\AI\Platform\Response\TextResponse;
|
||||
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveProp;
|
||||
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
||||
|
||||
#[AsLiveComponent('video')]
|
||||
final class TwigComponent
|
||||
{
|
||||
use DefaultActionTrait;
|
||||
|
||||
#[LiveProp]
|
||||
public string $caption = 'Please define an instruction and hit submit.';
|
||||
|
||||
public function __construct(
|
||||
private readonly PlatformInterface $platform,
|
||||
) {
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function submit(#[LiveArg] string $instruction, #[LiveArg] string $image): void
|
||||
{
|
||||
$messageBag = new MessageBag(
|
||||
Message::forSystem(<<<PROMPT
|
||||
You are a video captioning assistant. You are provided with a video frame and an instruction.
|
||||
You must generate a caption or answer based on the provided video frame and the user's instruction.
|
||||
You are not in a conversation with the user and there will be no follow-up questions or messages.
|
||||
PROMPT),
|
||||
Message::ofUser($instruction, Image::fromDataUrl($image))
|
||||
);
|
||||
|
||||
$response = $this->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();
|
||||
}
|
||||
}
|
||||
60
demo/src/Wikipedia/Chat.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Wikipedia;
|
||||
|
||||
use Symfony\AI\Agent\AgentInterface;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
use Symfony\AI\Platform\Response\TextResponse;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class Chat
|
||||
{
|
||||
private const SESSION_KEY = 'wikipedia-chat';
|
||||
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
#[Autowire(service: 'symfony_ai.agent.wikipedia')]
|
||||
private readonly AgentInterface $chain,
|
||||
) {
|
||||
}
|
||||
|
||||
public function loadMessages(): MessageBag
|
||||
{
|
||||
return $this->requestStack->getSession()->get(self::SESSION_KEY, new MessageBag());
|
||||
}
|
||||
|
||||
public function submitMessage(string $message): void
|
||||
{
|
||||
$messages = $this->loadMessages();
|
||||
|
||||
$messages->add(Message::ofUser($message));
|
||||
$response = $this->chain->call($messages);
|
||||
|
||||
\assert($response instanceof TextResponse);
|
||||
|
||||
$messages->add(Message::ofAssistant($response->getContent()));
|
||||
|
||||
$this->saveMessages($messages);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->requestStack->getSession()->remove(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
private function saveMessages(MessageBag $messages): void
|
||||
{
|
||||
$this->requestStack->getSession()->set(self::SESSION_KEY, $messages);
|
||||
}
|
||||
}
|
||||
49
demo/src/Wikipedia/TwigComponent.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Wikipedia;
|
||||
|
||||
use Symfony\AI\Platform\Message\MessageInterface;
|
||||
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
||||
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
||||
|
||||
#[AsLiveComponent('wikipedia')]
|
||||
final class TwigComponent
|
||||
{
|
||||
use DefaultActionTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly Chat $wikipedia,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageInterface[]
|
||||
*/
|
||||
public function getMessages(): array
|
||||
{
|
||||
return $this->wikipedia->loadMessages()->withoutSystemMessage()->getMessages();
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function submit(#[LiveArg] string $message): void
|
||||
{
|
||||
$this->wikipedia->submitMessage($message);
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function reset(): void
|
||||
{
|
||||
$this->wikipedia->reset();
|
||||
}
|
||||
}
|
||||
82
demo/src/YouTube/Chat.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\YouTube;
|
||||
|
||||
use Symfony\AI\Agent\AgentInterface;
|
||||
use Symfony\AI\Platform\Message\Message;
|
||||
use Symfony\AI\Platform\Message\MessageBag;
|
||||
use Symfony\AI\Platform\Response\TextResponse;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
final class Chat
|
||||
{
|
||||
private const SESSION_KEY = 'youtube-chat';
|
||||
|
||||
public function __construct(
|
||||
private readonly RequestStack $requestStack,
|
||||
#[Autowire(service: 'symfony_ai.agent.youtube')]
|
||||
private readonly AgentInterface $agent,
|
||||
private readonly TranscriptFetcher $transcriptFetcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public function loadMessages(): MessageBag
|
||||
{
|
||||
return $this->requestStack->getSession()->get(self::SESSION_KEY, new MessageBag());
|
||||
}
|
||||
|
||||
public function start(string $videoId): void
|
||||
{
|
||||
$transcript = $this->transcriptFetcher->fetchTranscript($videoId);
|
||||
$system = <<<PROMPT
|
||||
You are an helpful assistant that answers questions about a YouTube video based on a transcript.
|
||||
If you can't answer a question, say so.
|
||||
|
||||
Video ID: {$videoId}
|
||||
Transcript:
|
||||
{$transcript}
|
||||
PROMPT;
|
||||
|
||||
$messages = new MessageBag(
|
||||
Message::forSystem($system),
|
||||
Message::ofAssistant('What do you want to know about that video?'),
|
||||
);
|
||||
|
||||
$this->reset();
|
||||
$this->saveMessages($messages);
|
||||
}
|
||||
|
||||
public function submitMessage(string $message): void
|
||||
{
|
||||
$messages = $this->loadMessages();
|
||||
|
||||
$messages->add(Message::ofUser($message));
|
||||
$response = $this->agent->call($messages);
|
||||
|
||||
\assert($response instanceof TextResponse);
|
||||
|
||||
$messages->add(Message::ofAssistant($response->getContent()));
|
||||
|
||||
$this->saveMessages($messages);
|
||||
}
|
||||
|
||||
public function reset(): void
|
||||
{
|
||||
$this->requestStack->getSession()->remove(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
private function saveMessages(MessageBag $messages): void
|
||||
{
|
||||
$this->requestStack->getSession()->set(self::SESSION_KEY, $messages);
|
||||
}
|
||||
}
|
||||
63
demo/src/YouTube/TranscriptFetcher.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\YouTube;
|
||||
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
final class TranscriptFetcher
|
||||
{
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $client,
|
||||
) {
|
||||
}
|
||||
|
||||
public function fetchTranscript(string $videoId): string
|
||||
{
|
||||
// Fetch the HTML content of the YouTube video page
|
||||
$htmlResponse = $this->client->request('GET', 'https://youtube.com/watch?v='.$videoId);
|
||||
$html = $htmlResponse->getContent();
|
||||
|
||||
// Use DomCrawler to parse the HTML
|
||||
$crawler = new Crawler($html);
|
||||
|
||||
// Extract the script containing the ytInitialPlayerResponse
|
||||
$scriptContent = $crawler->filter('script')->reduce(function (Crawler $node) {
|
||||
return str_contains($node->text(), 'var ytInitialPlayerResponse = {');
|
||||
})->text();
|
||||
|
||||
// Extract and parse the JSON data from the script
|
||||
$start = strpos($scriptContent, 'var ytInitialPlayerResponse = ') + \strlen('var ytInitialPlayerResponse = ');
|
||||
$dataString = substr($scriptContent, $start);
|
||||
$dataString = substr($dataString, 0, strrpos($dataString, ';') ?: null);
|
||||
$data = json_decode(trim($dataString), true);
|
||||
|
||||
// Extract the URL for the captions
|
||||
if (!isset($data['captions']['playerCaptionsTracklistRenderer']['captionTracks'][0]['baseUrl'])) {
|
||||
throw new \Exception('Captions are not available for this video.');
|
||||
}
|
||||
$captionsUrl = $data['captions']['playerCaptionsTracklistRenderer']['captionTracks'][0]['baseUrl'];
|
||||
|
||||
// Fetch and parse the captions XML
|
||||
$xmlResponse = $this->client->request('GET', $captionsUrl);
|
||||
$xmlContent = $xmlResponse->getContent();
|
||||
$xmlCrawler = new Crawler($xmlContent);
|
||||
|
||||
// Collect all text elements from the captions
|
||||
$transcript = $xmlCrawler->filter('text')->each(function (Crawler $node) {
|
||||
return $node->text().' TranscriptFetcher.php';
|
||||
});
|
||||
|
||||
// Combine all the text elements into one string
|
||||
return implode(\PHP_EOL, $transcript);
|
||||
}
|
||||
}
|
||||
79
demo/src/YouTube/TwigComponent.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\YouTube;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\AI\Platform\Message\MessageInterface;
|
||||
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveAction;
|
||||
use Symfony\UX\LiveComponent\Attribute\LiveArg;
|
||||
use Symfony\UX\LiveComponent\DefaultActionTrait;
|
||||
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
#[AsLiveComponent('youtube')]
|
||||
final class TwigComponent
|
||||
{
|
||||
use DefaultActionTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly Chat $youTube,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function start(#[LiveArg] string $videoId): void
|
||||
{
|
||||
if (str_contains($videoId, 'youtube.com')) {
|
||||
$videoId = $this->getVideoIdFromUrl($videoId);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->youTube->start($videoId);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Unable to start YouTube chat.', ['exception' => $e]);
|
||||
$this->youTube->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MessageInterface[]
|
||||
*/
|
||||
public function getMessages(): array
|
||||
{
|
||||
return $this->youTube->loadMessages()->withoutSystemMessage()->getMessages();
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function submit(#[LiveArg] string $message): void
|
||||
{
|
||||
$this->youTube->submitMessage($message);
|
||||
}
|
||||
|
||||
#[LiveAction]
|
||||
public function reset(): void
|
||||
{
|
||||
$this->youTube->reset();
|
||||
}
|
||||
|
||||
private function getVideoIdFromUrl(string $url): string
|
||||
{
|
||||
$query = parse_url($url, \PHP_URL_QUERY);
|
||||
|
||||
if (!$query) {
|
||||
throw new \InvalidArgumentException('Unable to parse YouTube URL.');
|
||||
}
|
||||
|
||||
return u($query)->after('v=')->before('&')->toString();
|
||||
}
|
||||
}
|
||||
256
demo/symfony.lock
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"doctrine/deprecations": {
|
||||
"version": "1.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
||||
}
|
||||
},
|
||||
"php-cs-fixer/shim": {
|
||||
"version": "3.55",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.0",
|
||||
"ref": "16422bf8eac6c3be42afe07d37e2abc89d2bdf6b"
|
||||
},
|
||||
"files": [
|
||||
".php-cs-fixer.dist.php"
|
||||
]
|
||||
},
|
||||
"php-llm/llm-chain-bundle": {
|
||||
"version": "dev-main"
|
||||
},
|
||||
"phpstan/phpstan": {
|
||||
"version": "1.10",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||
},
|
||||
"files": [
|
||||
"phpstan.dist.neon"
|
||||
]
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"version": "11.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "9.6",
|
||||
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"phpunit.xml.dist",
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/ai-bundle": {
|
||||
"version": "dev-integrate-demo"
|
||||
},
|
||||
"symfony/asset-mapper": {
|
||||
"version": "7.1",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.4",
|
||||
"ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a"
|
||||
},
|
||||
"files": [
|
||||
"assets/app.js",
|
||||
"assets/styles/app.css",
|
||||
"config/packages/asset_mapper.yaml",
|
||||
"importmap.php"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
|
||||
},
|
||||
"files": [
|
||||
"bin/console"
|
||||
]
|
||||
},
|
||||
"symfony/debug-bundle": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/debug.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/flex": {
|
||||
"version": "2.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "1.0",
|
||||
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
||||
},
|
||||
"files": [
|
||||
".env"
|
||||
]
|
||||
},
|
||||
"symfony/framework-bundle": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/cache.yaml",
|
||||
"config/packages/framework.yaml",
|
||||
"config/preload.php",
|
||||
"config/routes/framework.yaml",
|
||||
"config/services.yaml",
|
||||
"public/index.php",
|
||||
"src/Controller/.gitignore",
|
||||
"src/Kernel.php"
|
||||
]
|
||||
},
|
||||
"symfony/monolog-bundle": {
|
||||
"version": "3.10",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "3.7",
|
||||
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/monolog.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/property-info": {
|
||||
"version": "7.3",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.3",
|
||||
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/property_info.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "21b72649d5622d8f7da329ffb5afb232a023619d"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/routing.yaml",
|
||||
"config/routes.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/stimulus-bundle": {
|
||||
"version": "2.17",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.13",
|
||||
"ref": "6acd9ff4f7fd5626d2962109bd4ebab351d43c43"
|
||||
},
|
||||
"files": [
|
||||
"assets/bootstrap.js",
|
||||
"assets/controllers.json",
|
||||
"assets/controllers/chat_controller.js"
|
||||
]
|
||||
},
|
||||
"symfony/twig-bundle": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.4",
|
||||
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/twig.yaml",
|
||||
"templates/base.html.twig"
|
||||
]
|
||||
},
|
||||
"symfony/uid": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "7.0",
|
||||
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
|
||||
}
|
||||
},
|
||||
"symfony/ux-icons": {
|
||||
"version": "2.17",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.17",
|
||||
"ref": "803a3bbd5893f9584969ab8670290cdfb6a0a5b5"
|
||||
},
|
||||
"files": [
|
||||
"assets/icons/symfony.svg"
|
||||
]
|
||||
},
|
||||
"symfony/ux-live-component": {
|
||||
"version": "2.17",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.6",
|
||||
"ref": "73e69baf18f47740d6f58688c5464b10cdacae06"
|
||||
},
|
||||
"files": [
|
||||
"config/routes/ux_live_component.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/ux-turbo": {
|
||||
"version": "v2.17.0"
|
||||
},
|
||||
"symfony/ux-twig-component": {
|
||||
"version": "2.17",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "2.13",
|
||||
"ref": "67814b5f9794798b885cec9d3f48631424449a01"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/twig_component.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/ux-typed": {
|
||||
"version": "v2.22.0"
|
||||
},
|
||||
"symfony/web-profiler-bundle": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.1",
|
||||
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/web_profiler.yaml",
|
||||
"config/routes/web_profiler.yaml"
|
||||
]
|
||||
},
|
||||
"twig/extra-bundle": {
|
||||
"version": "v3.9.3"
|
||||
}
|
||||
}
|
||||
53
demo/templates/_message.html.twig
Normal file
@@ -0,0 +1,53 @@
|
||||
{% if message.role.value == 'assistant' %}
|
||||
{{ _self.bot(message.content, latest: latest) }}
|
||||
{% else %}
|
||||
{{ _self.user(message.content) }}
|
||||
{% endif %}
|
||||
|
||||
{% macro bot(content, loading = false, latest = false) %}
|
||||
<div class="d-flex align-items-baseline mb-4">
|
||||
<div class="bot avatar rounded-3 shadow-sm">
|
||||
{{ ux_icon('fluent:bot-24-filled', { height: '45px', width: '45px' }) }}
|
||||
</div>
|
||||
<div class="ps-2">
|
||||
{% if loading %}
|
||||
<div class="bot-message loading d-inline-block p-2 px-3 m-1 border border-light-subtle shadow-sm">
|
||||
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||
<i>{{ content }}</i>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bot-message d-inline-block p-2 px-3 m-1 border border-light-subtle shadow-sm">
|
||||
{% if latest and app.request.xmlHttpRequest %}
|
||||
<span
|
||||
data-controller="symfony--ux-typed"
|
||||
data-symfony--ux-typed-show-cursor-value="false"
|
||||
data-symfony--ux-typed-type-speed-value="0"
|
||||
data-symfony--ux-typed-strings-value="{{ [content|markdown_to_html]|json_encode|e('html_attr') }}"
|
||||
></span>
|
||||
{% else %}
|
||||
{{ content|markdown_to_html }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user(content, loading = false) %}
|
||||
<div class="d-flex align-items-baseline text-end justify-content-end mb-4">
|
||||
<div class="pe-2">
|
||||
{% for item in content %}
|
||||
<div class="user-message d-inline-block p-2 px-3 m-1 border border-light-subtle shadow-sm">
|
||||
{% if loading %}
|
||||
<span class="spinner-border spinner-border-sm me-1"></span><i>{{ item.text }}</i>
|
||||
{% else %}
|
||||
{{ item.text }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="user avatar rounded-3 shadow-sm">
|
||||
{{ ux_icon('solar:user-bold', { width: '45px', height: '45px' }) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
57
demo/templates/base.html.twig
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ app.request.locale }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Symfony AI Demo{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/ico" href="{{ asset('favicon.ico') }}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="{{ block('body_class') }}">
|
||||
<nav class="navbar bg-white shadow-lg navbar-expand">
|
||||
<div class="container">
|
||||
<a class="navbar-brand ms-2 p-0" href="{{ path('index') }}">
|
||||
{{ ux_icon('fluent:bot-24-filled', { height: '40px', width: '40px' }) }}
|
||||
<strong>Symfony AI</strong> Demo
|
||||
</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav ms-auto me-2 mb-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('blog') }}">{{ ux_icon('mdi:symfony', { height: '20px', width: '20px' }) }} Symfony Blog Bot</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('youtube') }}">{{ ux_icon('bi:youtube', { height: '20px', width: '20px' }) }} YouTube Transcript Bot</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('wikipedia') }}">{{ ux_icon('mdi:wikipedia', { height: '20px', width: '20px' }) }} Wikipedia Research Bot</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('audio') }}">{{ ux_icon('iconoir:microphone-solid', { height: '20px', width: '20px' }) }} Audio Bot</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('video') }}">{{ ux_icon('tabler:video-filled', { height: '20px', width: '20px' }) }} Video Bot</a>
|
||||
</li>
|
||||
<li class="nav-item"><span class="nav-link">|</span></li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/symfony/ai" target="_blank">{{ ux_icon('mdi:github', { height: '20px', width: '20px' }) }} GitHub</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container pt-5">
|
||||
{% block content %}{% endblock %}
|
||||
<footer class="py-4 text-center">
|
||||
Demo application for <a href="https://github.com/symfony/ai" target="_blank">Symfony AI</a>
|
||||
•
|
||||
Feel free to propose more examples on <a href="https://github.com/symfony/ai" target="_blank">GitHub</a>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
9
demo/templates/chat.html.twig
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body_class 'chat ' ~ chat %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
{{ component(chat) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
38
demo/templates/components/audio.html.twig
Normal file
@@ -0,0 +1,38 @@
|
||||
{% import "_message.html.twig" as message %}
|
||||
|
||||
<div class="card mx-auto shadow-lg" {{ attributes.defaults(stimulus_controller('audio')) }}>
|
||||
<div class="card-header p-2">
|
||||
{{ ux_icon('iconoir:microphone-solid', { height: '32px', width: '32px' }) }}
|
||||
<strong class="ms-1 pt-1 d-inline-block">Conversational Bot</strong>
|
||||
<button id="chat-reset" class="btn btn-sm btn-outline-secondary float-end">{{ ux_icon('material-symbols:cancel') }} Reset Chat</button>
|
||||
</div>
|
||||
<div id="chat-body" class="card-body p-4 overflow-auto">
|
||||
{% for message in this.messages %}
|
||||
{% include '_message.html.twig' with { message, latest: loop.last } %}
|
||||
{% else %}
|
||||
<div id="welcome" class="text-center mt-5 py-5 bg-white rounded-5 shadow-sm w-75 mx-auto">
|
||||
{{ ux_icon('iconoir:microphone-solid', { height: '200px', width: '200px' }) }}
|
||||
<h4 class="mt-5">Audio Bot</h4>
|
||||
<span class="text-muted">Please hit the button below to start talking and again to stop</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div id="loading-message" class="d-none">
|
||||
{{ message.user([{text:'Converting your speech to text ...'}], true) }}
|
||||
{{ message.bot('The Bot is looking for an answer ...', true) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-2 text-center">
|
||||
<button id="micro-start" class="btn btn-primary" type="button">
|
||||
{{ ux_icon('iconoir:microphone-solid', { height: '24px', width: '24px' }) }}
|
||||
<strong>Say something</strong>
|
||||
</button>
|
||||
<button id="micro-stop" class="btn btn-danger d-none" type="button">
|
||||
{{ ux_icon('iconoir:microphone-mute-solid', { height: '24px', width: '24px' }) }}
|
||||
<strong>Stop</strong>
|
||||
</button>
|
||||
<button id="bot-thinking" class="btn btn-secondary disabled d-none" type="button">
|
||||
{{ ux_icon('iconoir:timer-solid', { height: '24px', width: '24px' }) }}
|
||||
<strong>Bot is thinking</strong>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
30
demo/templates/components/blog.html.twig
Normal file
@@ -0,0 +1,30 @@
|
||||
{% import "_message.html.twig" as message %}
|
||||
|
||||
<div class="card mx-auto shadow-lg" {{ attributes.defaults(stimulus_controller('chat')) }}>
|
||||
<div class="card-header p-2">
|
||||
{{ ux_icon('mdi:symfony', { height: '32px', width: '32px' }) }}
|
||||
<strong class="ms-1 pt-1 d-inline-block">Symfony Blog Bot</strong>
|
||||
<button id="chat-reset" class="btn btn-sm btn-outline-secondary float-end">{{ ux_icon('material-symbols:cancel') }} Reset Chat</button>
|
||||
</div>
|
||||
<div id="chat-body" class="card-body p-4 overflow-auto">
|
||||
{% for message in this.messages %}
|
||||
{% include '_message.html.twig' with { message, latest: loop.last } %}
|
||||
{% else %}
|
||||
<div id="welcome" class="text-center mt-5 py-5 bg-white rounded-5 shadow-sm w-75 mx-auto">
|
||||
{{ ux_icon('mdi:symfony', { height: '200px', width: '200px' }) }}
|
||||
<h4 class="mt-5">Retrieval Augmented Generation based on the Symfony blog</h4>
|
||||
<span class="text-muted">Please use the text input at the bottom to start chatting.</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div id="loading-message" class="d-none">
|
||||
{{ message.user([{text:''}]) }}
|
||||
{{ message.bot('The Symfony Bot is looking for an answer ...', true) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-2">
|
||||
<div class="input-group">
|
||||
<input id="chat-message" type="text" class="form-control border-0" placeholder="Write a message ...">
|
||||
<button id="chat-submit" class="btn btn-outline-secondary border-0" type="button">{{ ux_icon('mingcute:send-fill', { height: '25px', width: '25px' }) }} Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
23
demo/templates/components/video.html.twig
Normal file
@@ -0,0 +1,23 @@
|
||||
{% import "_message.html.twig" as message %}
|
||||
|
||||
<div class="card mx-auto shadow-lg" {{ attributes.defaults(stimulus_controller('video')) }}>
|
||||
<div class="card-header p-2">
|
||||
{{ ux_icon('tabler:video-filled', { height: '32px', width: '32px' }) }}
|
||||
<strong class="ms-1 pt-1 d-inline-block">Video Bot</strong>
|
||||
</div>
|
||||
<div id="chat-body" class="card-body p-2 overflow-auto">
|
||||
<div id="welcome" class="text-center mt-5 p-4 bg-white rounded-5 shadow-sm w-75 mx-auto">
|
||||
<div class="mb-2">
|
||||
<video id="videoFeed" autoplay playsinline></video>
|
||||
</div>
|
||||
<canvas id="canvas" class="d-none"></canvas>
|
||||
<i class="text-muted">{{ this.caption }}</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-2">
|
||||
<div class="input-group">
|
||||
<input id="chat-message" type="text" class="form-control border-0" placeholder="What do you see?">
|
||||
<button id="chat-submit" class="btn btn-outline-secondary border-0" type="button">{{ ux_icon('mingcute:send-fill', { height: '25px', width: '25px' }) }} Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
30
demo/templates/components/wikipedia.html.twig
Normal file
@@ -0,0 +1,30 @@
|
||||
{% import "_message.html.twig" as message %}
|
||||
|
||||
<div class="card mx-auto shadow-lg" {{ attributes.defaults(stimulus_controller('wikipedia')) }}>
|
||||
<div class="card-header p-2">
|
||||
{{ ux_icon('mdi:wikipedia', { height: '32px', width: '32px' }) }}
|
||||
<strong class="ms-1 pt-1 d-inline-block">Wikipedia Research Bot</strong>
|
||||
<button id="chat-reset" class="btn btn-sm btn-outline-secondary float-end">{{ ux_icon('material-symbols:cancel') }} Reset Chat</button>
|
||||
</div>
|
||||
<div id="chat-body" class="card-body p-4 overflow-auto">
|
||||
{% for message in this.messages %}
|
||||
{% include '_message.html.twig' with { message, latest: loop.last } %}
|
||||
{% else %}
|
||||
<div id="welcome" class="text-center mt-5 py-5 bg-white rounded-5 shadow-sm w-75 mx-auto">
|
||||
{{ ux_icon('mdi:wikipedia', { height: '200px', width: '200px' }) }}
|
||||
<h4 class="mt-5">Wikipedia Research</h4>
|
||||
<span class="text-muted">Please provide the bot with a topic down below to start the research.</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div id="loading-message" class="d-none">
|
||||
{{ message.user([{text:''}]) }}
|
||||
{{ message.bot('The Wikipedia Bot is doing some research ...', true) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-2">
|
||||
<div class="input-group">
|
||||
<input id="chat-message" type="text" class="form-control border-0" placeholder="Write a message ...">
|
||||
<button id="chat-submit" class="btn btn-outline-secondary border-0" type="button">{{ ux_icon('mingcute:send-fill', { height: '25px', width: '25px' }) }} Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
38
demo/templates/components/youtube.html.twig
Normal file
@@ -0,0 +1,38 @@
|
||||
{% import "_message.html.twig" as message %}
|
||||
|
||||
<div class="card mx-auto shadow-lg" {{ attributes.defaults(stimulus_controller('youtube')) }}>
|
||||
<div class="card-header p-2">
|
||||
{{ ux_icon('bi:youtube', { height: '32px', width: '32px' }) }}
|
||||
<strong class="ms-1 pt-1 d-inline-block">YouTube Transcript Bot</strong>
|
||||
<button id="chat-reset" class="btn btn-sm btn-outline-secondary float-end">{{ ux_icon('material-symbols:cancel') }} Reset Chat</button>
|
||||
</div>
|
||||
<div id="chat-body" class="card-body p-4 overflow-auto">
|
||||
{% set messages = this.messages %}
|
||||
{% for message in messages %}
|
||||
{% include '_message.html.twig' with { message, latest: loop.last } %}
|
||||
{% else %}
|
||||
<div id="welcome" class="text-center mt-5 py-5 bg-white rounded-5 shadow-sm w-75 mx-auto">
|
||||
{{ ux_icon('bi:youtube', { color: '#FF0000', height: '200px', width: '200px' }) }}
|
||||
<h4 class="mt-5">Chat about a YouTube Video</h4>
|
||||
<div class="w-75 mx-auto text-start">
|
||||
<label for="youtube-id" class="form-label">Enter the ID of the YouTube video (e.g. <code>6uXW-ulpj0s</code>) to initialize the chat:</label>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="basic-addon3">https://youtube.com/watch?v=</span>
|
||||
<input type="text" id="youtube-id" class="form-control" placeholder="YouTube Video ID">
|
||||
<button class="btn btn-secondary" type="button" id="chat-start">Start</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div id="loading-message" class="d-none">
|
||||
{{ message.user([{text:''}]) }}
|
||||
{{ message.bot('The Youtube Bot is looking for an answer ...', true) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-2">
|
||||
<div class="input-group">
|
||||
<input id="chat-message" {{ messages|length == 0 ? 'disabled'}} type="text" class="form-control border-0" placeholder="Write a message ...">
|
||||
<button id="chat-submit" {{ messages|length == 0 ? 'disabled'}} class="btn btn-outline-secondary border-0" type="button">{{ ux_icon('mingcute:send-fill', { height: '25px', width: '25px' }) }} Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
117
demo/templates/index.html.twig
Normal file
@@ -0,0 +1,117 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body_class 'index' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<h1 class="text-dark-emphasis">Welcome to the <strong class="text-dark">Symfony AI</strong> Demo</h1>
|
||||
<p class="my-4 text-dark">
|
||||
This is a small demo app that can be used to explore the capabilities of Symfony AI Components together with
|
||||
Symfony UX and Twig Live Components.<br />
|
||||
Central to this demo are five chatbot examples that are implemented in <code>src/**/Chat.php</code> and AI
|
||||
configuration can be found in <code>config/packages/ai.yaml</code>.
|
||||
</p>
|
||||
<h3 class="text-dark">Examples</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card blog bg-body shadow-sm">
|
||||
<div class="card-img-top py-2">
|
||||
{{ ux_icon('mdi:symfony', { height: '150px', width: '150px' }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Symfony Blog Bot</h5>
|
||||
<p class="card-text">Retrieval Augmented Generation (RAG) based on Symfony's blog dumped to a vector store.</p>
|
||||
<a href="{{ path('blog') }}" class="btn btn-outline-dark d-block">Try Symfony Blog Bot</a>
|
||||
</div>
|
||||
{# Profiler route only available in dev #}
|
||||
{% if 'dev' == app.environment %}
|
||||
<div class="card-footer">
|
||||
{{ ux_icon('solar:code-linear', { height: '20px', width: '20px' }) }}
|
||||
<a href="{{ path('_profiler_open_file', { file: 'src/Blog/Chat.php', line: 21 }) }}">See Implementation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card youtube bg-body shadow-sm">
|
||||
<div class="card-img-top py-2">
|
||||
{{ ux_icon('bi:youtube', { height: '150px', width: '150px' }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">YouTube Transcript Bot</h5>
|
||||
<p class="card-text">Question answering started with a YouTube video ID which gets converted into a transcript.</p>
|
||||
<a href="{{ path('youtube') }}" class="btn btn-outline-dark d-block">Try YouTube Transcript Bot</a>
|
||||
</div>
|
||||
{# Profiler route only available in dev #}
|
||||
{% if 'dev' == app.environment %}
|
||||
<div class="card-footer">
|
||||
{{ ux_icon('solar:code-linear', { height: '20px', width: '20px' }) }}
|
||||
<a href="{{ path('_profiler_open_file', { file: 'src/YouTube/Chat.php', line: 21 }) }}">See Implementation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card wikipedia bg-body shadow-sm">
|
||||
<div class="card-img-top py-2">
|
||||
{{ ux_icon('mdi:wikipedia', { height: '150px', width: '150px' }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Wikipedia Research Bot</h5>
|
||||
<p class="card-text">A chatbot equipped with tools to search and read on Wikipedia about topics the user asks for.</p>
|
||||
<a href="{{ path('wikipedia') }}" class="btn btn-outline-dark d-block">Try Wikipedia Research Bot</a>
|
||||
</div>
|
||||
{# Profiler route only available in dev #}
|
||||
{% if 'dev' == app.environment %}
|
||||
<div class="card-footer">
|
||||
{{ ux_icon('solar:code-linear', { height: '20px', width: '20px' }) }}
|
||||
<a href="{{ path('_profiler_open_file', { file: 'src/Wikipedia/Chat.php', line: 21 }) }}">See Implementation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-2"></div>
|
||||
<div class="col-md-4">
|
||||
<div class="card audio bg-body shadow-sm">
|
||||
<div class="card-img-top py-2">
|
||||
{{ ux_icon('iconoir:microphone-solid', { height: '150px', width: '150px' }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Audio Bot</h5>
|
||||
<p class="card-text">Simple demonstration of speech-to-text with Whisper in combination with GPT.</p>
|
||||
<a href="{{ path('audio') }}" class="btn btn-outline-dark d-block">Try Audio Bot</a>
|
||||
</div>
|
||||
{# Profiler route only available in dev #}
|
||||
{% if 'dev' == app.environment %}
|
||||
<div class="card-footer">
|
||||
{{ ux_icon('solar:code-linear', { height: '20px', width: '20px' }) }}
|
||||
<a href="{{ path('_profiler_open_file', { file: 'src/Audio/Chat.php', line: 25 }) }}">See Implementation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card video bg-body shadow-sm">
|
||||
<div class="card-img-top py-2">
|
||||
{{ ux_icon('tabler:video-filled', { height: '150px', width: '150px' }) }}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Video Bot</h5>
|
||||
<p class="card-text">Simple demonstration of vision capabilities of GPT in combination with your webcam.</p>
|
||||
<a href="{{ path('video') }}" class="btn btn-outline-dark d-block">Try Video Bot</a>
|
||||
</div>
|
||||
{# Profiler route only available in dev #}
|
||||
{% if 'dev' == app.environment %}
|
||||
<div class="card-footer">
|
||||
{{ ux_icon('solar:code-linear', { height: '20px', width: '20px' }) }}
|
||||
<a href="{{ path('_profiler_open_file', { file: 'src/Video/TwigComponent.php', line: 41 }) }}">See Implementation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
55
demo/tests/Blog/LoaderTest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Blog;
|
||||
|
||||
use App\Blog\FeedLoader;
|
||||
use App\Blog\Post;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
|
||||
#[CoversClass(FeedLoader::class)]
|
||||
#[UsesClass(Post::class)]
|
||||
final class LoaderTest extends TestCase
|
||||
{
|
||||
public function testLoad(): void
|
||||
{
|
||||
$response = MockResponse::fromFile(__DIR__.'/fixtures/blog.rss');
|
||||
$client = new MockHttpClient($response);
|
||||
|
||||
$loader = new FeedLoader($client);
|
||||
$posts = $loader->load();
|
||||
|
||||
self::assertCount(10, $posts);
|
||||
|
||||
self::assertSame('A Week of Symfony #936 (2-8 December 2024)', $posts[0]->title);
|
||||
self::assertSame('https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed', $posts[0]->link);
|
||||
self::assertStringContainsString('This week, Symfony celebrated the SymfonyCon 2024 Vienna conference with great success.', $posts[0]->description);
|
||||
self::assertStringContainsString('Select a track for a guided path through 100+ video tutorial courses about Symfony', $posts[0]->content);
|
||||
self::assertSame('Javier Eguiluz', $posts[0]->author);
|
||||
self::assertEquals(new \DateTimeImmutable('8.12.2024 09:39:00 +0100'), $posts[0]->date);
|
||||
|
||||
self::assertSame('A Week of Symfony #935 (25 November - 1 December 2024)', $posts[1]->title);
|
||||
self::assertSame('Symfony 7.2 curated new features', $posts[2]->title);
|
||||
self::assertSame('Symfony 7.2.0 released', $posts[3]->title);
|
||||
self::assertSame('Symfony 5.4.49 released', $posts[4]->title);
|
||||
self::assertSame('SymfonyCon Vienna 2024: See you next week!', $posts[5]->title);
|
||||
self::assertSame('New in Symfony 7.2: Misc. Improvements (Part 2)', $posts[6]->title);
|
||||
self::assertSame('Symfony 7.1.9 released', $posts[7]->title);
|
||||
self::assertSame('Symfony 6.4.16 released', $posts[8]->title);
|
||||
self::assertSame('Symfony 5.4.48 released', $posts[9]->title);
|
||||
}
|
||||
}
|
||||
71
demo/tests/Blog/PostTest.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Blog;
|
||||
|
||||
use App\Blog\Post;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[CoversClass(Post::class)]
|
||||
final class PostTest extends TestCase
|
||||
{
|
||||
public function testPostToString(): void
|
||||
{
|
||||
$post = new Post(
|
||||
Uuid::v4(),
|
||||
'Hello, World!',
|
||||
'https://example.com/hello-world',
|
||||
'This is a test description.',
|
||||
'This is a test post.',
|
||||
'John Doe',
|
||||
new \DateTimeImmutable('2024-12-08 09:39:00'),
|
||||
);
|
||||
|
||||
$expected = <<<TEXT
|
||||
Title: Hello, World!
|
||||
From: John Doe on 2024-12-08
|
||||
Description: This is a test description.
|
||||
This is a test post.
|
||||
TEXT;
|
||||
|
||||
self::assertSame($expected, $post->toString());
|
||||
}
|
||||
|
||||
public function testPostToArray(): void
|
||||
{
|
||||
$id = Uuid::v4();
|
||||
$post = new Post(
|
||||
$id,
|
||||
'Hello, World!',
|
||||
'https://example.com/hello-world',
|
||||
'This is a test description.',
|
||||
'This is a test post.',
|
||||
'John Doe',
|
||||
new \DateTimeImmutable('2024-12-08 09:39:00'),
|
||||
);
|
||||
|
||||
$expected = [
|
||||
'id' => $id->toRfc4122(),
|
||||
'title' => 'Hello, World!',
|
||||
'link' => 'https://example.com/hello-world',
|
||||
'description' => 'This is a test description.',
|
||||
'content' => 'This is a test post.',
|
||||
'author' => 'John Doe',
|
||||
'date' => '2024-12-08',
|
||||
];
|
||||
|
||||
self::assertSame($expected, $post->toArray());
|
||||
}
|
||||
}
|
||||
996
demo/tests/Blog/fixtures/blog.rss
Normal file
@@ -0,0 +1,996 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0"
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
>
|
||||
<channel>
|
||||
<title>Symfony Blog</title>
|
||||
<atom:link href="https://feeds.feedburner.com/symfony/blog" rel="self" type="application/rss+xml" />
|
||||
<link>https://symfony.com/blog/</link>
|
||||
<description>Most recent posts published on the Symfony project blog</description>
|
||||
<pubDate>Tue, 10 Dec 2024 23:56:55 +0100</pubDate>
|
||||
<lastBuildDate>Sun, 08 Dec 2024 09:39:00 +0100</lastBuildDate>
|
||||
<language>en</language>
|
||||
<item>
|
||||
<title><![CDATA[A Week of Symfony #936 (2-8 December 2024)]]></title>
|
||||
<link>https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>This week, Symfony celebrated the SymfonyCon 2024 Vienna conference with great success. This annual event brought together the global Symfony community to exchange ideas, learn new things, and collaborate on contributions to the Symfony project. In addition,…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p>This week, Symfony celebrated the <a href="https://live.symfony.com/2024-vienna-con/">SymfonyCon 2024 Vienna</a> conference with great success. This annual event brought together the global Symfony community to exchange ideas, learn new things, and collaborate on contributions to the Symfony project. In addition, the upcoming <a href="https://symfony.com/releases/7.3">Symfony 7.3</a> version introduced support for <a href="https://github.com/symfony/symfony/commit/12e4e53038afad4817eab8466a90043912e67e42">pre-compressing web assets</a> and a new <a href="https://github.com/symfony/symfony/commit/4612ff2ce30ab0727e28613508f9e0553d40cdbb">userIsGranted() security method</a> to test user authorization without relying on the session.</p>
|
||||
|
||||
<h2>Symfony development highlights</h2>
|
||||
|
||||
<p>This week, 57 pull requests were merged (28 in code and 29 in docs) and 28 issues were closed (22 in code and 6 in docs). Excluding merges, 35 authors made 2,784 additions and 954 deletions. See details for <a href="https://github.com/symfony/symfony/pulse">code</a> and <a href="https://github.com/symfony/symfony-docs/pulse">docs</a>.</p>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/6.4">6.4 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/8c8bab246b1995f7d34006c073d5f30fc93f457f">8c8bab2</a>: [TwigBridge] fix Twig 3.17 compatibility</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/ffc4dc611c03df513f82ceb7177e196c8763255a">ffc4dc6</a>: [HttpClient] always set CURLOPT_CUSTOMREQUEST to the correct HTTP method in CurlHttpClient</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/74e916300e6d0f35d4040f0d3cb7a68dba339e60">74e9163</a>: [PropertyInfo] evaluate access flags for properties with asymmetric visibility</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/51d824a91ac4291c5f358288b8a2d69ff058791e">51d824a</a>: [Console] fix division by 0 error</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/caa1be76ade4659e3e443b3e7085435260829032">caa1be7</a>: [ErrorHandler] fix error message in test with PHP 8.5</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/e25242af119b13d37483d110dc49ed0aa550e00c">e25242a</a>: [TwigBridge] add tests covering trans_default_domain with dynamic expressions</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/91835ef8031f81fbfb40704e72b2d376084387b9">91835ef</a>: [FrameworkBundle] fix notifier push channel bus abstract arg</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/7.1">7.1 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/2d9f2a561c8f0c9382fee995e2f9864522130add">2d9f2a5</a>: [Scheduler] remove unused code</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/7.2">7.2 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/cc1802d9583349cc19f8982a854f4e96d0c9d4de">cc1802d</a>: [TwigBridge] generate conflict-free variable names</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/6076b8799ed924e8e3696764f926f93399fd37d3">6076b87</a>, <a href="https://github.com/symfony/symfony/commit/dcf824a59d96ba4de5a67278554bc2914675ea11">dcf824a</a>: [Mailer] fix null check on region in Sendgrid mailer</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/f24ac9ee9700b1e66436ae02ed460c8e7772cf58">f24ac9e</a>: [FrameworkBundle] make uri_signer lazy and improve error when kernel.secret is empty</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/27c0f65c02c09331f6dbcf50582e0e5fc8a25aef">27c0f65</a>: [Notifier] fix desktop channel bus abstract arg</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/7.3">7.3 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/2d1838a780258188b8ec1fccb6de543c78133d14">2d1838a</a>: [VarDumper] add caster for Socket instances</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/3a804f53e605f9bdd79485dbf2213a763a41022e">3a804f5</a>: [Mailer, Notifier] add webhooks signature verification on Sweego bridges</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/12e4e53038afad4817eab8466a90043912e67e42">12e4e53</a>: [AssetMapper] add support for assets pre-compression</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/b6d6adf11399fa571514b1dedd3b6864a5585e3c">b6d6adf</a>: [FrameworkBundle] rename TranslationUpdateCommand to TranslationExtract command to match the command name</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/b7ed0a4401acffed6fe18c6fc132dab4972a63e7">b7ed0a4</a>: [ErrorHandler] support non-empty-string/non-empty-list when patching return types</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/12ff1bfadf7972274b70844e62c076a340b1f255">12ff1bf</a>: [Uid] add @return non-empty-string annotations to AbstractUid and relevant functions</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/4612ff2ce30ab0727e28613508f9e0553d40cdbb">4612ff2</a>: [Security, SecurityBundle] add a userIsGranted() method to test user authorization without relying on the session</li>
|
||||
</ul>
|
||||
|
||||
<h2>Newest issues and pull requests</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/59072">Turn asset-map:compile into a cache-warmer</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/59085">Allow #[AsTaggedItem] to be repeated</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/pull/59129">Add user_is_granted() function to twig</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/pull/59106">[Security] Deprecate UserInterface::eraseCredentials()</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/pull/59084">[Scheduler] Add scheduler:task:run command</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Symfony Jobs</h2>
|
||||
|
||||
<p>These are some of the most recent Symfony job offers:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Symfony Developer</strong> at Adria Solutions<br>
|
||||
Full-time - £45,000 – £60,000 / year<br>
|
||||
Remote + part-time onsite (Cardiff, United Kingdom)<br>
|
||||
<a href="https://symfony.com/jobs/76c1d5e">View details</a></li>
|
||||
<li><strong>Backend Symfony Developer</strong> at Bold Company<br>
|
||||
Full-time - €4,200 / month<br>
|
||||
Remote + part-time onsite (Rotterdam, Netherlands)<br>
|
||||
<a href="https://symfony.com/jobs/7f511ca">View details</a></li>
|
||||
<li><strong>Symfony Developer</strong> at Kennisnet<br>
|
||||
Full-time - €4,104 – €5,673 / month<br>
|
||||
Remote + part-time onsite (Zoetermeer, Netherlands)<br>
|
||||
<a href="https://symfony.com/jobs/83f8ae4">View details</a></li>
|
||||
</ul>
|
||||
|
||||
<p>You can <a href="https://symfony.com/jobs">publish a Symfony job offer for free</a> on symfony.com.</p>
|
||||
|
||||
<h2>SymfonyCasts Updates</h2>
|
||||
|
||||
<p><a href="https://symfonycasts.com/">SymfonyCasts</a> is the official way to learn Symfony.
|
||||
Select a track for a guided path through 100+ video tutorial courses about
|
||||
Symfony, PHP and JavaScript.</p>
|
||||
|
||||
<p>This week, SymfonyCasts published the following updates:</p>
|
||||
|
||||
<ul>
|
||||
<li>(Video) <a href="https://symfonycasts.com/screencast/symfony7-doctrine/persisting-fixtures">Symfony 7 - Doctrine, Symfony 7 & the Database: Inserting Data via Fixtures</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>They talked about us</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://symfonystation.mobileatom.net/Symfony-Station-Communique-06-December-2024">Symfony Station Communiqué - 06 December 2024</a></li>
|
||||
<li><a href="https://mtccreatives.medium.com/how-i-optimized-lazy-loading-in-symfony-doctrine-to-improve-performance-43c195cfc4e8">How I Optimized Lazy Loading in Symfony Doctrine to Improve Performance</a></li>
|
||||
<li><a href="https://dev.to/inspector/symfony-monitoring-library-implementation-1d4g">Symfony monitoring library implementation</a></li>
|
||||
<li><a href="https://dunglas.dev/2024/12/http-compression-in-php-new-symfony-assetmapper-feature/">HTTP compression in PHP (new Symfony AssetMapper feature)</a></li>
|
||||
<li><a href="https://symfonystation.mobileatom.net/Grav-Theme">Building a Simple Grav CMS Theme with Twig, PHP, and CSS</a></li>
|
||||
<li><a href="https://sylius.com/blog/sylius-2-is-live/">Sylius 2 is live!</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Call to Action</h2>
|
||||
|
||||
<ul>
|
||||
<li>Follow Symfony <a href="https://x.com/symfony">on X</a>, <a href="https://mastodon.social/@symfony">on Mastodon</a>, <a href="https://bsky.app/profile/symfony.bsky.social">on Bluesky</a> and <a href="https://www.threads.net/@symfony">on Threads</a> and share this article.</li>
|
||||
<li><a href="https://feeds.feedburner.com/symfony/blog">Subscribe to the Symfony blog RSS</a> and never miss a Symfony story again.</li>
|
||||
</ul>
|
||||
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
|
||||
<pubDate>Sun, 08 Dec 2024 09:39:00 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/a-week-of-symfony-936-2-8-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[A Week of Symfony #935 (25 November - 1 December 2024)]]></title>
|
||||
<link>https://symfony.com/blog/a-week-of-symfony-935-25-november-1-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>This week, the stable Symfony 7.2.0 version was released, featuring tens of new additions. Additionally, we announced the Black Friday Symfony promotions. Furthermore, the maintenance releases for Symfony 5.4.48, 6.4.16, and 7.1.9 are now available. Finally,…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p>This week, the stable <a href="https://symfony.com/blog/symfony-7-2-0-released">Symfony 7.2.0 version</a> was released, featuring <a href="https://symfony.com/blog/symfony-7-2-curated-new-features">tens of new additions</a>. Additionally, we announced the <a href="https://symfony.com/blog/black-friday-2024-offers-from-the-symfony-ecosystem">Black Friday Symfony promotions</a>. Furthermore, the maintenance releases for Symfony <a href="https://symfony.com/blog/symfony-5-4-48-released">5.4.48</a>, <a href="https://symfony.com/blog/symfony-6-4-16-released">6.4.16</a>, and <a href="https://symfony.com/blog/symfony-7-1-9-released">7.1.9</a> are now available. Finally, next week, the global <a href="https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week">Symfony community will gather in Vienna</a> for the SymfonyCon 2024 conference.</p>
|
||||
|
||||
<h2>Symfony development highlights</h2>
|
||||
|
||||
<p>This week, 39 pull requests were merged (28 in code and 11 in docs) and 21 issues were closed (19 in code and 2 in docs). Excluding merges, 21 authors made 1,033 additions and 332 deletions. See details for <a href="https://github.com/symfony/symfony/pulse">code</a> and <a href="https://github.com/symfony/symfony-docs/pulse">docs</a>.</p>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/5.4">5.4 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/5cbdb870baa9b41cc261f5085e53366cb8b0375e">5cbdb87</a>: [HttpClient] various cleanups after recent changes</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/520e31b5c3cc0ff66bd4b130eb8c1bfa45f7b66d">520e31b</a>: [PropertyInfo] consider write property visibility to decide whether a property is writable</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/294a39f6bc7a4ffe95e0a13ba4f7fb5b575c9471">294a39f</a>: [Translation] fix empty keys array in PUT, DELETE requests causing Lokalise API error</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/ec691c8f41867d90503e49d135b8c8015f8c4bc8">ec691c8</a>: [PropertyInfo] fix write visibility for Asymmetric Visibility and Virtual Properties</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/1b7d8b311784db5dab11d26a466e277adcffb09c">1b7d8b3</a>: [Dotenv] read runtime config from composer.json in debug dotenv command</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/9eea677637666ea9a9d4befe6ad5e06aad81dd7f">9eea677</a>: [HttpClient] close gracefull when the server closes the connection abruptly</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/4677f32c73035b965306e3ebd95a54f2c527c210">4677f32</a>: [HttpClient] fix checking for private IPs before connecting</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/7a2d66a7614b0323197cbc2e2b51c7c19ca79865">7a2d66a</a>: [HttpClient] fix streaming and redirecting with NoPrivateNetworkHttpClient</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/6.4">6.4 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/76df9834cf6c21d4f4e2b46f1573e8b66da0f3a6">76df983</a>: [DoctrineBridge] fix Connection::createSchemaManager() for Doctrine DBAL v2</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/9c6b5d55a6a0381586e51516abd7cd0673c900f6">9c6b5d5</a>: [HttpClient] more consistency cleanups</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/a59ff05190f1aa714888930c7c69cdc8ac78ab63">a59ff05</a>: [Messenger] fix Envelope::all() conditional return docblock</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/7.2">7.2 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/e128d76b3dfd5147fe1263a807e8ce83733094d7">e128d76</a>: [HttpClient] fix amphp/http-client 5 support</li>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/59ceb58fe4bf9f4c76b7eb9f3a4092aa66e89f21">59ceb58</a>: [Form] allow integer for the calendar option of DateType</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="https://github.com/symfony/symfony/commits/7.3">7.3 changelog</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/commit/baf98b91dbf5a9fcff942f2a0cf416814b77b04e">baf98b9</a>: [VarDumper] add caster for AddressInfo objects</li>
|
||||
</ul>
|
||||
|
||||
<h2>Newest issues and pull requests</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/58985">Use rfc9211 in HttpCache</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/58991">Rename security-csrf TokenStorageInterface for better DX</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/58996">[RFC] Support asymmetric visibility in VarDumper</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/59005">[Validator] Validate data according to environments</a></li>
|
||||
<li><a href="https://github.com/symfony/symfony/issues/59027">Add IPv6 support to NativeHttpClient</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Symfony CLI</h2>
|
||||
|
||||
<p><a href="https://github.com/symfony-cli/symfony-cli">Symfony CLI</a> is a must-have tool when developing
|
||||
Symfony applications on your local machine. It includes the
|
||||
<a href="https://symfony.com/doc/current/setup/symfony_server.html">Symfony Local Server</a>,
|
||||
the best way to run local Symfony applications. This week Symfony CLI released
|
||||
its new <a href="https://github.com/symfony-cli/symfony-cli/releases/tag/v5.10.5">5.10.5</a>,
|
||||
version with the following changes:</p>
|
||||
|
||||
<ul>
|
||||
<li>Update fixtures (@fabpot)</li>
|
||||
<li>Update check-requirements.php script to v2.0.3 (@fabpot)</li>
|
||||
<li>Fix path on local envs (@fabpot)</li>
|
||||
<li>Add a warning about the listening IP change in 5.10.3 (@tucksaun)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Symfony Jobs</h2>
|
||||
|
||||
<p>These are some of the most recent Symfony job offers:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Symfony Developer</strong> at Tactiplan<br>
|
||||
Full-time - €4,500 – €8,000 / month<br>
|
||||
Full remote<br>
|
||||
<a href="https://symfony.com/jobs/6f38654">View details</a></li>
|
||||
<li><strong>Backend Symfony Developer</strong> at 2beGROUP<br>
|
||||
Full-time - €48,000 – €72,000 / year<br>
|
||||
Remote + part-time onsite (Leiderdorp, Netherlands)<br>
|
||||
<a href="https://symfony.com/jobs/073ba48">View details</a></li>
|
||||
<li><strong>Symfony Developer</strong> at ProcurePro<br>
|
||||
Full-time - A$120,000 – A$150,000 / year<br>
|
||||
Full remote<br>
|
||||
<a href="https://symfony.com/jobs/335822f">View details</a></li>
|
||||
</ul>
|
||||
|
||||
<p>You can <a href="https://symfony.com/jobs">publish a Symfony job offer for free</a> on symfony.com.</p>
|
||||
|
||||
<h2>SymfonyCasts Updates</h2>
|
||||
|
||||
<p><a href="https://symfonycasts.com/">SymfonyCasts</a> is the official way to learn Symfony.
|
||||
Select a track for a guided path through 100+ video tutorial courses about
|
||||
Symfony, PHP and JavaScript.</p>
|
||||
|
||||
<p>This week, SymfonyCasts published the following updates:</p>
|
||||
|
||||
<ul>
|
||||
<li>(Video) <a href="https://symfonycasts.com/screencast/symfony7-doctrine/installing-doctrine">Symfony 7 - Doctrine, Symfony 7 & the Database: Installing Doctrine</a></li>
|
||||
<li>(Video) <a href="https://symfonycasts.com/screencast/symfony7-doctrine/database-setup">Symfony 7 - Doctrine, Symfony 7 & the Database: Database Setup & Docker</a></li>
|
||||
<li>(Video) <a href="https://symfonycasts.com/screencast/symfony7-doctrine/entity">Symfony 7 - Doctrine, Symfony 7 & the Database: Starship Entity</a></li>
|
||||
<li>(Video) <a href="https://symfonycasts.com/screencast/symfony7-doctrine/migrations">Symfony 7 - Doctrine, Symfony 7 & the Database: Migrations</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>They talked about us</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://symfonystation.mobileatom.net/Symfony-Station-Communique-29-November-2024">Symfony Station Communiqué - 29 November 2024</a></li>
|
||||
<li><a href="https://dev.to/etienneleba/another-way-to-structure-your-symfony-project-llo">Another Way to Structure your Symfony Project</a></li>
|
||||
<li><a href="https://dev.to/sirzarganwar/automate-symfonycomponenteventdispatchereventsubscriberinterfacegetsubscribedevents-with-160">Automate Symfony EventSubscriberInterface::getSubscribedEvents()</a></li>
|
||||
<li><a href="https://medium.com/@ahmedbhs/doctrine-for-dummies-50-recommendations-to-optimize-orm-and-turbocharge-your-app-4a7e7091544b">Doctrine for Dummies: 50 Recommendations to Optimize ORM and Turbocharge Your App</a></li>
|
||||
<li><a href="https://sylius.com/blog/month-of-sylius-october-2024/">Month of Sylius: October</a></li>
|
||||
<li><a href="https://medium.com/norsys-octogone/pattern-builder-exemple-dimpl%C3%A9mentation-avec-symfony-235617f5077b">Pattern Builder : exemple d’implémentation avec Symfony</a></li>
|
||||
<li><a href="https://medium.com/@SeniorJun/redis-%D0%BF%D0%B8%D1%88%D0%B5%D0%BC-url-%D1%81%D0%BE%D0%BA%D1%80%D0%B0%D1%89%D0%B0%D1%82%D0%B5%D0%BB%D1%8C-5994fc88d129">Redis: пишем URL-сокращатель</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Call to Action</h2>
|
||||
|
||||
<ul>
|
||||
<li>Follow Symfony <a href="https://x.com/symfony">on X</a>, <a href="https://mastodon.social/@symfony">on Mastodon</a>, <a href="https://bsky.app/profile/symfony.bsky.social">on Bluesky</a> and <a href="https://www.threads.net/@symfony">on Threads</a> and share this article.</li>
|
||||
<li><a href="https://feeds.feedburner.com/symfony/blog">Subscribe to the Symfony blog RSS</a> and never miss a Symfony story again.</li>
|
||||
</ul>
|
||||
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/a-week-of-symfony-935-25-november-1-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
|
||||
<pubDate>Sun, 01 Dec 2024 09:54:00 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/a-week-of-symfony-935-25-november-1-december-2024?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Symfony 7.2 curated new features]]></title>
|
||||
<link>https://symfony.com/blog/symfony-7-2-curated-new-features?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>Symfony 7.2.0 has been released. As for any other Symfony release, our backward compatibility promise applies and this means that you should be able to upgrade easily to 7.2 without changing anything in your code.
|
||||
|
||||
During the last couple of months, we've…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p>Symfony 7.2.0 has been released. As for any other Symfony release, our backward compatibility promise applies and this means that you should be able to upgrade easily to 7.2 without changing anything in your code.</p>
|
||||
|
||||
<p>During the last couple of months, we've blogged about the great 7.2 new features. I highly recommend you to read these articles about Symfony 7.2 as they contain the major changes for this new version:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-week-wordcount-and-yaml-constraints">Week, WordCount and Yaml Constraints</a>: Symfony 7.2 introduces three new constraints: one to validate week numbers, another to check word count, and a third to validate YAML syntax.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-silent-verbosity">Silent Verbosity</a>: Symfony 7.2 introduces a new silent verbosity to supress all output, including errors.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-expression-language-improvements">Expression Language Improvements</a>: Symfony 7.2 improves the ExpressionLanguage component with new bitwise and logical operators, easier registration of custom providers and support for comments.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-asmessage-attribute">AsMessage Attribute</a>: Symfony 7.2 introduces a new AsMessage attribute, allowing you to configure the transport(s) directly within the message class</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-named-serializers">Named Serializers</a>: Symfony 7.2 allows you to configure multiple serializer instances with different default contexts, name converters, and sets of normalizers and encoders.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-translations-linter">Translations Linter</a>: Symfony 7.2 includes a new lint:translations command to check the validity of your translation contents.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-whennot-attribute">WhenNot Attribute</a>: Symfony 7.2 introduces the WhenNot attribute to exclude a service from certain environments.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-lazy-choice-loader">Lazy Choice Loader</a>: Symfony 7.2 introduces a new lazy choice loader to improve performance of choice fields with lots of options.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-string-component-improvements">String Component Improvements</a>: Symfony 7.2 improves the String component with a new kebab-case method, new truncation modes and a Spanish inflector.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-compound-constraint-improvements">Compound Constraint Improvements</a>: In Symfony 7.2, Compound constraints are easier to test and can define the validation groups and payload via the constructor.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-mailer-and-notifier-integrations">Mailer and Notifier Integrations</a>: Symfony 7.2 adds some new integrations to the Mailer and Notifier components, adding to the tens of integrations already available.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-improved-translation-extractor">Improved Translation Extractor</a>: Symfony 7.2 improves the translation extractor command, allowing customization of prefixes, modification of update behavior, and sorting of content.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-desktop-notifications">Desktop Notifications</a>: Symfony 7.2 allows to send notifications directly to your local desktop using the new desktop channel in the Notifier component.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-template-dx-improvements">Template DX Improvements</a>: In Symfony 7.2, you can set HTTP headers in static pages and render specific Twig blocks using attributes.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-non-empty-container-parameters">Non-Empty Container Parameters</a>: Symfony 7.2 introduces a new utility to require that some parameters exist and have non-empty values.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-keepalive-messenger-transports">Keepalive Messenger Transports</a>: Symfony 7.2 introduces the keepalive feature for Messenger transports, preventing timeouts when processing messages.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-mime-improvements">Mime Improvements</a>: In Symfony 7.2, the Mime component adds support for custom encoders and Unicode email addresses.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-console-finished-indicator">Console Finished Indicator</a>: Symfony 7.2 allows customizing the indicator displayed when a Console command completes.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-constraint-improvements">Constraint Improvements</a>: Symfony 7.2 adds a validation mode for BIC constraint, an errorPath for Unique constraint, format options for Ulid constraint, and context support for When constraint.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-simpler-trusted-proxies-configuration">Simpler Trusted Proxies Configuration</a>: Symfony 7.2 simplifies trusted proxy configuration with a private subnet shortcut and new environment variables.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-simpler-single-file-symfony-applications">Simpler Single-File Symfony Applications</a>: In Symfony 7.2, single-file applications are now simpler and require less configuration.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-new-command-options">New Command Options</a>: Symfony 7.2 introduces new command options to lint container env vars, format messenger stats output, and filter assets during debugging.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-redesigned-typeinfo-component">Redesigned TypeInfo Component</a>: Symfony 7.2 redesigns the TypeInfo component and makes it stable.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-serializer-improvements">Serializer Improvements</a>: Symfony 7.2 enhances the Serializer with support for DateTime subclasses, a new SnakeCaseToCamelCase name converter, updated UUID constants, and optional Webhook integration.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-stateless-csrf">Stateless CSRF</a>: Symfony 7.2 introduces stateless CSRF protection, enabling secure token validation without relying on server-side sessions.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-deprecations">Deprecations</a>: Symfony 7.2 deprecates several features, including session config options, empty user identifiers, and the !tagged tag.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-optional-secret">Optional Secret</a>: Symfony 7.2 simplifies application setup by making the secret optional, enhancing security and developer experience.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-1">Misc. Improvements (Part 1)</a>: Symfony 7.2 introduces features like custom retry delays for Messenger, improved null-coalesce support in expressions, custom attributes for user login passports, and enhanced VarDumper support for PHP 8.4 property hooks.</li>
|
||||
<li><a href="https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2">Misc. Improvements (Part 2)</a>: Symfony 7.2 adds password strength estimation, simpler RequestStack testing, nullable boolean configuration, improved IP anonymization, and Security Profiler upgrades.</li>
|
||||
</ul>
|
||||
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfony-7-2-curated-new-features?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
|
||||
<pubDate>Fri, 29 Nov 2024 09:52:00 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfony-7-2-curated-new-features?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Symfony 7.2.0 released]]></title>
|
||||
<link>https://symfony.com/blog/symfony-7-2-0-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>Symfony 7.2.0 has just been released.
|
||||
Check the Living on the Edge
|
||||
category on this blog to learn about the main features of this new stable release;
|
||||
or check the release announcement of BETA1
|
||||
to get the list of all new features.
|
||||
Here is the list of the most…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p><a href="https://github.com/symfony/symfony/pull/59032" class="reference external" rel="external noopener noreferrer" target="_blank">Symfony 7.2.0</a> has just been released.</p>
|
||||
<p>Check the <a href="https://symfony.com/blog/category/living-on-the-edge/7.2" class="reference external">Living on the Edge</a>
|
||||
category on this blog to learn about the main features of this new stable release;
|
||||
or check the <a href="https://symfony.com/blog/symfony-7-2-0-beta1-released" class="reference external">release announcement of BETA1</a>
|
||||
to get the list of <em>all</em> new features.</p>
|
||||
<p>Here is the list of the most important changes since 7.2.0-RC1:</p>
|
||||
<ul>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59023" class="reference external" rel="external noopener noreferrer" target="_blank">#59023</a> [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59014" class="reference external" rel="external noopener noreferrer" target="_blank">#59014</a> [Form] Allow integer for the calendar option of DateType (@alexandre-daubois)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59013" class="reference external" rel="external noopener noreferrer" target="_blank">#59013</a> [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58562" class="reference external" rel="external noopener noreferrer" target="_blank">#58562</a> [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59007" class="reference external" rel="external noopener noreferrer" target="_blank">#59007</a> [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58963" class="reference external" rel="external noopener noreferrer" target="_blank">#58963</a> [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58983" class="reference external" rel="external noopener noreferrer" target="_blank">#58983</a> [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58956" class="reference external" rel="external noopener noreferrer" target="_blank">#58956</a> [DoctrineBridge] Fix Connection::createSchemaManager() for Doctrine DBAL v2 (@neodevcode)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58959" class="reference external" rel="external noopener noreferrer" target="_blank">#58959</a> [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58964" class="reference external" rel="external noopener noreferrer" target="_blank">#58964</a> [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58950" class="reference external" rel="external noopener noreferrer" target="_blank">#58950</a> [FrameworkBundle] Revert " Deprecate making cache.app adapter taggable" (@keulinho)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58952" class="reference external" rel="external noopener noreferrer" target="_blank">#58952</a> [Cache] silence warnings issued by Redis Sentinel on connection issues (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58953" class="reference external" rel="external noopener noreferrer" target="_blank">#58953</a> [HttpClient] Fix computing stats for PUSH with Amp (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58943" class="reference external" rel="external noopener noreferrer" target="_blank">#58943</a> [FrameworkBundle] Revert " Don't auto-register form/csrf when the corresponding components are not installed" (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58937" class="reference external" rel="external noopener noreferrer" target="_blank">#58937</a> [FrameworkBundle] Don't auto-register form/csrf when the corresponding components are not installed (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58859" class="reference external" rel="external noopener noreferrer" target="_blank">#58859</a> [AssetMapper] ignore missing directory in isVendor() (@alexislefebvre)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58917" class="reference external" rel="external noopener noreferrer" target="_blank">#58917</a> [OptionsResolver] Allow Union/Intersection Types in Resolved Closures (@zanbaldwin)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58822" class="reference external" rel="external noopener noreferrer" target="_blank">#58822</a> [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58865" class="reference external" rel="external noopener noreferrer" target="_blank">#58865</a> Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58921" class="reference external" rel="external noopener noreferrer" target="_blank">#58921</a> [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58908" class="reference external" rel="external noopener noreferrer" target="_blank">#58908</a> [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58938" class="reference external" rel="external noopener noreferrer" target="_blank">#58938</a> [Cache] make RelayProxyTrait compatible with relay extension 0.9.0 (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58924" class="reference external" rel="external noopener noreferrer" target="_blank">#58924</a> [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58915" class="reference external" rel="external noopener noreferrer" target="_blank">#58915</a> [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58919" class="reference external" rel="external noopener noreferrer" target="_blank">#58919</a> [WebProfilerBundle] Twig deprecations (@mazodude)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58914" class="reference external" rel="external noopener noreferrer" target="_blank">#58914</a> [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58888" class="reference external" rel="external noopener noreferrer" target="_blank">#58888</a> [Mailer][Notifier] Sweego is backing their bridges, thanks to them! (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58885" class="reference external" rel="external noopener noreferrer" target="_blank">#58885</a> [PropertyInfo][Serializer][TypeInfo][Validator] TypeInfo 7.1 compatibility (@mtarld)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58870" class="reference external" rel="external noopener noreferrer" target="_blank">#58870</a> [Serializer][Validator] prevent failures around not existing TypeInfo classes (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58872" class="reference external" rel="external noopener noreferrer" target="_blank">#58872</a> [PropertyInfo][Serializer][Validator] TypeInfo 7.2 compatibility (@mtarld)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58875" class="reference external" rel="external noopener noreferrer" target="_blank">#58875</a> [HttpClient] Removed body size limit (Carl Julian Sauter)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58866" class="reference external" rel="external noopener noreferrer" target="_blank">#58866</a> [Validator] fix compatibility with PHP < 8.2.4 (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58862" class="reference external" rel="external noopener noreferrer" target="_blank">#58862</a> [Notifier] Fix GoIpTransport (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58860" class="reference external" rel="external noopener noreferrer" target="_blank">#58860</a> [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58834" class="reference external" rel="external noopener noreferrer" target="_blank">#58834</a> [FrameworkBundle] ensure validator.translation_domain parameter is always set (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58836" class="reference external" rel="external noopener noreferrer" target="_blank">#58836</a> Work around parse_url() bug (bis) (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58818" class="reference external" rel="external noopener noreferrer" target="_blank">#58818</a> [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58828" class="reference external" rel="external noopener noreferrer" target="_blank">#58828</a> [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58842" class="reference external" rel="external noopener noreferrer" target="_blank">#58842</a> [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58850" class="reference external" rel="external noopener noreferrer" target="_blank">#58850</a> [HttpClient] fix PHP 7.2 compatibility (@xabbuh)</li>
|
||||
</ul>
|
||||
<p>Want to upgrade to this new release? Because Symfony protects
|
||||
backwards-compatibility very closely, this should be quite easy. Use
|
||||
<a href="https://insight.symfony.com/" class="reference external">SymfonyInsight upgrade reports</a>
|
||||
to detect the code you will need to change in your project and
|
||||
<a href="https://symfony.com/doc/current/cookbook/upgrade/index.html" class="reference external">read our upgrade</a>
|
||||
documentation to learn more.</p>
|
||||
<p>Want to be notified whenever a new Symfony release is published? Or when a
|
||||
version is not maintained anymore? Or only when a security issue is fixed?
|
||||
Consider <a href="https://symfony.com/account/notifications" class="reference external">subscribing to the Symfony Roadmap Notifications</a>.</p>
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfony-7-2-0-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
|
||||
<pubDate>Fri, 29 Nov 2024 09:46:04 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfony-7-2-0-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Symfony 5.4.49 released]]></title>
|
||||
<link>https://symfony.com/blog/symfony-5-4-49-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>Symfony 5.4.49 has just been released.
|
||||
Here is the list of the most important changes since 5.4.48:
|
||||
|
||||
bug #59023 [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (@nicolas-grekas)
|
||||
|
||||
WARNING: 5.4.49 is the last version for the Symfony…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p><a href="https://github.com/symfony/symfony/pull/59031" class="reference external" rel="external noopener noreferrer" target="_blank">Symfony 5.4.49</a> has just been released.
|
||||
Here is the list of the most important changes since 5.4.48:</p>
|
||||
<ul>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59023" class="reference external" rel="external noopener noreferrer" target="_blank">#59023</a> [HttpClient] Fix streaming and redirecting with NoPrivateNetworkHttpClient (@nicolas-grekas)</li>
|
||||
</ul>
|
||||
<p>WARNING: 5.4.49 is the last version for the Symfony 5.4 branch. If some
|
||||
of your projects are still using this version, consider upgrading as soon as
|
||||
possible. However, if you can't upgrade soon, note that we still provide
|
||||
security issue releases according to our release policy.</p>
|
||||
<p>Want to upgrade to this new release? Because Symfony protects
|
||||
backwards-compatibility very closely, this should be quite easy. Use
|
||||
<a href="https://insight.symfony.com/" class="reference external">SymfonyInsight upgrade reports</a>
|
||||
to detect the code you will need to change in your project and
|
||||
<a href="https://symfony.com/doc/current/cookbook/upgrade/index.html" class="reference external">read our upgrade</a>
|
||||
documentation to learn more.</p>
|
||||
<p>Want to be notified whenever a new Symfony release is published? Or when a
|
||||
version is not maintained anymore? Or only when a security issue is fixed?
|
||||
Consider <a href="https://symfony.com/account/notifications" class="reference external">subscribing to the Symfony Roadmap Notifications</a>.</p>
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfony-5-4-49-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
|
||||
<pubDate>Fri, 29 Nov 2024 09:39:54 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfony-5-4-49-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[SymfonyCon Vienna 2024: See you next week!]]></title>
|
||||
<link>https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>
|
||||
|
||||
|
||||
SymfonyCon Vienna is just around the corner! 🎉 Next week, we’ll come together for an exciting event featuring brand-new talks, inspiring speakers, and everything you need to make the most of this gathering with the Symfony and PHP community.
|
||||
|
||||
💡Pro…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p><a class="block text-center" href="https://live.symfony.com/2024-vienna-con" title="Sfconvienna2024 Blog">
|
||||
<img src="https://symfony.com/uploads/assets/blog/sfconvienna2024-blog.png" alt="Sfconvienna2024 Blog">
|
||||
</a>
|
||||
SymfonyCon Vienna is just around the corner! 🎉 Next week, we’ll come together for an exciting event featuring brand-new talks, inspiring speakers, and everything you need to make the most of this gathering with the Symfony and PHP community.</p>
|
||||
|
||||
<p>💡Pro tip: Use the business meeting feature in your SymfonyLive profile to schedule meetings with sponsors ahead of time!</p>
|
||||
|
||||
<p>Not registered yet? Don’t miss out—there’s still time to <a href="https://live.symfony.com/2024-vienna-con/registration/">grab your tickets</a> for:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>December 3-4</strong>: Workshop Days – Choose from a variety of 1-day training sessions. Spots are filling fast!</li>
|
||||
<li><strong>December 5-6</strong>: Conference Days – Dive into 3 parallel tracks plus an unconference track, all in English.</li>
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>🆕<strong>What's new in the schedule</strong></p>
|
||||
|
||||
<ul>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/schedule/symfony-forms-practical-use-cases">"Symfony Forms - Practical Use Cases!"</a></strong> by <a href="https://connect.symfony.com/profile/alexandresalome">Alexandre Salomé</a>, Director of Engineering, Platform.sh on Thursday, December 5 from 11:10</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/schedule/designing-for-change-extensibility-points-in-practice">"Designing for Change: Extensibility points in practice"</a></strong> by <a href="https://connect.symfony.com/profile/awojs">Adam Wójs</a>, Director of Engineering, Ibexa on Thursday, December 5 from 17:50</li>
|
||||
<li><strong>"Core team Q/A"</strong> on Friday, December 6 from 16:20</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>🔎 Explore the great lineup of talks</strong></p>
|
||||
|
||||
<p>In just a few days, you'll meet our inspiring experts speakers as <a href="https://connect.symfony.com/profile/fabpot">Fabien Potencier</a>, <a href="https://connect.symfony.com/profile/haruatari">Viktor Pikaev</a>, <a href="https://connect.symfony.com/profile/alcaeus">Andreas Braun</a>, <a href="https://connect.symfony.com/profile/michellesanver">Michelle Sanver</a>, , <a href="https://connect.symfony.com/profile/mirtes">Ondřej Mirtes</a>, <a href="https://connect.symfony.com/profile/derrabus">Alexander M. Turek</a>, <a href="https://connect.symfony.com/profile/dbu">David Buchmann</a>, <a href="https://connect.symfony.com/profile/marie.minassyan">Marie Minasyan</a>, <a href="https://connect.symfony.com/profile/naderman">Nils Adermann</a>, <a href="https://connect.symfony.com/profile/dazs">Anne-Julia Seitz</a>, <a href="https://connect.symfony.com/profile/thibaultmilan">Thibault Milan</a>, <a href="https://connect.symfony.com/profile/romainruaud">Romain Ruaud</a>, <a href="https://connect.symfony.com/profile/jrfnl">Juliette Reinders Folmer</a>, <a href="https://connect.symfony.com/profile/daveliddament">Dave Liddament</a>, <a href="https://connect.symfony.com/profile/akrabat">Rob Allen</a>, <a href="https://connect.symfony.com/profile/tucksaun">Tugdual Saunier</a>, <a href="https://connect.symfony.com/profile/simonandre">Simon André</a>, <a href="https://connect.symfony.com/profile/mtarld">Mathias Arlaud</a>, <a href="https://connect.symfony.com/profile/neirda24">Adrien Roches</a>, <a href="https://connect.symfony.com/profile/hubert_lenoir">Hubert Lenoir</a>, <a href="https://connect.symfony.com/profile/soyuka">Antoine Bluchet</a>, <a href="https://connect.symfony.com/profile/alexander-schranz">Alexander Schranz</a>, <a href="https://connect.symfony.com/profile/alexandresalome">Alexandre Salomé</a>, <a href="https://connect.symfony.com/profile/dunglas">Kévin Dunglas</a>, <a href="https://connect.symfony.com/profile/nicolas-grekas">Nicolas Grekas</a>, <a href="https://connect.symfony.com/profile/sebastianplagemann">Sebastian Plagemann</a>, <a href="https://connect.symfony.com/profile/shochdoerfer">Stephan Hochdörfer</a>, <a href="https://connect.symfony.com/profile/raphael-geffroy">Raphaël Geffroy</a>, <a href="https://connect.symfony.com/profile/xosofox">Peter Dietrich</a>, <a href="https://connect.symfony.com/profile/celine-deis">Céline Deis</a>, <a href="https://connect.symfony.com/profile/wjohannes">Johannes Wachter</a>, <a href="https://connect.symfony.com/profile/mathdns">Matheo Daninos</a>, <a href="https://live.symfony.com/2024-vienna-con/schedule/symfony-authentication-what-s-next">Robin Chalas</a>, <a href="https://connect.symfony.com/profile/dragoonis">Paul Dragoonis</a>, <a href="https://connect.symfony.com/profile/akalipetis">Antonis Kalipetis</a>, <a href="https://connect.symfony.com/profile/gmoigneu">Guillaume Moigneu</a>, <a href="https://connect.symfony.com/profile/flovntp">Florent Huck</a>, <a href="https://connect.symfony.com/profile/guguss">Augustin Delaporte</a>, <a href="https://connect.symfony.com/profile/cvanderwatt">Celeste Van Der Watt</a>, <a href="https://connect.symfony.com/profile/gregqualls">Greg Qualls</a>, <a href="https://connect.symfony.com/profile/thomasdiluccio">Thomas di Luccio</a>, <a href="https://connect.symfony.com/profile/nigelk">Nigel Kersten</a>, <a href="https://connect.symfony.com/profile/moritz-schuh">Moritz Schuh</a>, <a href="https://connect.symfony.com/profile/segge">Sebastian Seggewiß</a>, <a href="https://connect.symfony.com/profile/haylee">Haylee Millar</a> and, <a href="https://connect.symfony.com/profile/kemiojogbede">Kemi Elizabeth Ojogbede</a>.</p>
|
||||
|
||||
<p>Read the detailed content of talks <a href="https://live.symfony.com/2024-vienna-con/schedule">here</a>.</p>
|
||||
|
||||
<p><strong>🧑💻Unlock new skills and level up your expertise with our workshops!</strong></p>
|
||||
|
||||
<p>Held on December 3-4, 2024, these workshops are crafted for developers eager to dive deep into Symfony, PHP, and modern coding practices. Here’s what you can look forward to:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/custom-chatbots-with-gpt-and-symfony-2">Custom Chatbots with GPT & Symfony</a></strong> by Christopher Hertel: Build smarter chatbots and leverage AI with Symfony. <em>SOLD OUT</em></li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/refactor-your-code-refactor-yourself">Refactor Your Code - Refactor Yourself</a></strong> by Peter Dietrich: Improve your codebase—and your developer mindset. <em>SOLD OUT</em></li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/mastering-oop-and-design-patterns-2">Mastering OOP & Design Patterns</a></strong> by Alexandre Salomé: Hone your skills in object-oriented programming and design patterns. <em>SOLD OUT</em></li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/get-started-with-sulu-a-developer-friendly-cms-powered-by-symfony-s-full-stack-framework">Getting Started with Sulu</a></strong> by Thomas Schedler: Discover Sulu, a powerful CMS for Symfony, and learn to build flexible, full-stack applications.</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/a-practical-introduction-to-symfony-ux">A Practical Introduction to Symfony UX</a></strong> by Simon André: Dive into Symfony UX for an enhanced, seamless frontend experience.</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/getting-the-most-out-of-phpstan">Getting the Most Out of PHPStan</a></strong> by Ondřej Mirtes: Unlock PHPStan’s full potential to write cleaner, more reliable code.</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/ddd-and-symfony-in-practice-2">DDD and Symfony in Practice</a></strong> by Stefan Koopmanschap: Put Domain-Driven Design into action with Symfony.</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop#introduction-to-sylius-2-0">Introduction to Sylius 2.0</a></strong> by Łukasz Chruściel: Explore the latest features in Sylius 2.0 for ecommerce development.</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop#test-driven-development-the-right-way-2">Test-Driven Development the Right Way</a></strong> by Diego Aguiar: Master TDD for a more robust, error-free codebase.</li>
|
||||
<li><strong><a href="https://live.symfony.com/2024-vienna-con/workshop/symfony-7-the-fast-track-2">Symfony 7: The Fast Track</a></strong> by Nicolas Grekas (2-day intensive): Jumpstart your journey with Symfony 7 and grasp its latest innovations.</li>
|
||||
</ul>
|
||||
|
||||
<p>Whether you're new to Symfony or looking to master advanced techniques, there’s something <a href="https://live.symfony.com/2024-vienna-con/workshop">here</a> for everyone. Don't miss the chance to learn directly from Symfony experts and apply your skills to real-world projects. <a href="https://live.symfony.com/2024-vienna-con/registration">Secure your spot now</a> and get ready to accelerate your Symfony journey!</p>
|
||||
|
||||
<p><strong>🎟️ Select the ticket of your choice</strong></p>
|
||||
|
||||
<p>Register by clicking on <strong><a href="https://live.symfony.com/2024-vienna-con/registration">Buy ticket</a></strong> and choose your ticket:</p>
|
||||
|
||||
<ul>
|
||||
<li>"Workshops only", December 3-4</li>
|
||||
<li>"Conference only", December 5-6</li>
|
||||
<li>Combo ticket "Conference + Workshops" to live a full Symfony week experience!</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>🫵 Participate in the Unconference track</strong></p>
|
||||
|
||||
<p>The Unconference track is a participant-driven format where attendees shape the content and discussions in real-time. <strong>Have a topic you're passionate about?</strong> Claim your slot by emailing us at <code>events@symfony.com</code> and set the stage for an unforgettable experience.</p>
|
||||
|
||||
<p>Each unconference talk lasts 20 minutes with a screen and projector available on both days.</p>
|
||||
|
||||
<p><strong>🧳 Plan your participation</strong></p>
|
||||
|
||||
<ul>
|
||||
<li><p>Use the <a href="https://live.symfony.com/2024-vienna-con/schedule">schedule</a> to organize your visit.</p></li>
|
||||
<li><p>Read our <a href="https://live.symfony.com/2024-vienna-con/venue">attendee guide</a> for venue, accommodation, and transportation details.</p></li>
|
||||
<li><p>Use the business meeting feature in your <a href="https://live.symfony.com/account">Symfony Live profile</a> to schedule meetings with sponsors ahead of time!</p></li>
|
||||
</ul>
|
||||
|
||||
<p><strong>🎉 Plan to attend the community evening on Thursday, December 5</strong></p>
|
||||
|
||||
<p>Join us for a "Night at the Museum" at one of Vienna's most iconic place: Naturhistorisches Museum Wien (20 minutes by public transport from the conference). From 7:30-10:30 pm. Drinks, music & access to parts of permanent exhibition included!</p>
|
||||
|
||||
<p><strong>💻 Save the date for the Symfony hackathon on Saturday, December 7</strong></p>
|
||||
|
||||
<p>Everyone is welcome to join the hackday! Whether you're an experienced contributor or new to the community, your participation is highly valued as it brings a fresh perspective! More details are available <a href="https://live.symfony.com/2024-vienna-con/hackday">here</a>. Address: Stockwerk, Pater-Schwartz-Gasse 11A, 1150 Wien - <a href="https://maps.app.goo.gl/6qo2cvyej95VyM3U7">Map</a></p>
|
||||
|
||||
<p><strong>💡 Follow the <a href="https://symfony.com/blog/category/conferences">"conferences"</a> blog posts to stay updated!</strong></p>
|
||||
|
||||
<p>We can't wait to meet you in person to learn and share the latest about Symfony. Join us and be part of the <a href="https://twitter.com/symfony">@symfony</a> community! 🫶</p>
|
||||
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
|
||||
<pubDate>Thu, 28 Nov 2024 15:45:00 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfonycon-vienna-2024-see-you-next-week?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[New in Symfony 7.2: Misc. Improvements (Part 2)]]></title>
|
||||
<link>https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>
|
||||
Access to the Estimated Password Strength
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Contributed by
|
||||
Yannick
|
||||
in
|
||||
#54881…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<div class="section">
|
||||
<h2 id="access-to-the-estimated-password-strength"><a class="headerlink" href="#access-to-the-estimated-password-strength" title="Permalink to this headline">Access to the Estimated Password Strength</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://connect.symfony.com/profile/nannick">
|
||||
<img src="https://connect.symfony.com/profile/nannick.picture" alt="Yannick">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/nannick">Yannick</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/54881">#54881</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <a href="https://symfony.com/doc/current/reference/constraints/PasswordStrength.html" class="reference external">PasswordStrength constraint</a> validates that the given password has reached
|
||||
a minimum strength configured in the constraint. In Symfony 7.2, we've changed
|
||||
the visibility of the <code translate="no" class="notranslate">estimateStrength()</code> validator method from private to public.</p>
|
||||
<p>This allows you to access the estimated password strength and display it, for
|
||||
example, in the interface, so users can better understand the quality of their
|
||||
passwords.</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 id="simpler-requeststack-unit-testing"><a class="headerlink" href="#simpler-requeststack-unit-testing" title="Permalink to this headline">Simpler RequestStack Unit Testing</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://connect.symfony.com/profile/alexander-schranz">
|
||||
<img src="https://connect.symfony.com/profile/alexander-schranz.picture" alt="Alexander Schranz">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/alexander-schranz">Alexander Schranz</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/57909">#57909</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>When using the <a href="https://symfony.com/doc/current/service_container/request.html" class="reference external">RequestStack</a> in unit tests, you previously needed code like
|
||||
this to configure the requests:</p>
|
||||
<div translate="no" data-loc="3" class="notranslate codeblock codeblock-length-sm codeblock-php">
|
||||
<div class="codeblock-scroll">
|
||||
|
||||
<pre class="codeblock-code"><code><span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>requestStack</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">RequestStack</span>();
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>requestStack</span>-><span class="hljs-title invoke__">push</span>(Request::<span class="hljs-title invoke__">create</span>(<span class="hljs-string">'/'</span>));
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>someCustomClass</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">MyCustomClass</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>requestStack</span>);</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<p>In Symfony 7.2, we've simplified this by adding a constructor to <code translate="no" class="notranslate">RequestStack</code>
|
||||
that accepts an array of requests:</p>
|
||||
<div translate="no" data-loc="3" class="notranslate codeblock codeblock-length-sm codeblock-php">
|
||||
<div class="codeblock-scroll">
|
||||
|
||||
<pre class="codeblock-code"><code><span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>someCustomClass</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">MyCustomClass</span>(<span class="hljs-keyword">new</span> <span class="hljs-title invoke__">RequestStack</span>([
|
||||
Request::<span class="hljs-title invoke__">create</span>(<span class="hljs-string">'/'</span>),
|
||||
]));</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 id="default-action-in-the-html-sanitizer"><a class="headerlink" href="#default-action-in-the-html-sanitizer" title="Permalink to this headline">Default Action in the HTML Sanitizer</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://connect.symfony.com/profile/seldaek">
|
||||
<img src="https://connect.symfony.com/profile/seldaek.picture" alt="Jordi Boggiano">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/seldaek">Jordi Boggiano</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/57399">#57399</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>In Symfony 7.2, we've added a new <code translate="no" class="notranslate">defaultAction()</code> method in the <a href="https://symfony.com/html-sanitizer" class="reference external">HtmlSanitizer component</a>.
|
||||
This method sets the default action for elements that are not explicitly allowed
|
||||
or blocked:</p>
|
||||
<div translate="no" data-loc="8" class="notranslate codeblock codeblock-length-sm codeblock-php">
|
||||
<div class="codeblock-scroll">
|
||||
|
||||
<pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HtmlSanitizer</span>\<span class="hljs-title">HtmlSanitizer</span>;
|
||||
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HtmlSanitizer</span>\<span class="hljs-title">HtmlSanitizerAction</span>;
|
||||
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>config</span> = (<span class="hljs-keyword">new</span> <span class="hljs-title invoke__">HtmlSanitizerConfig</span>())
|
||||
-><span class="hljs-title invoke__">defaultAction</span>(HtmlSanitizerAction::<span class="hljs-variable constant_">Block</span>)
|
||||
-><span class="hljs-title invoke__">allowElement</span>(<span class="hljs-string">'p'</span>);
|
||||
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>sanitizer</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">HtmlSanitizer</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>config</span>);</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<p><code translate="no" class="notranslate">HtmlSanitizerAction</code> is a PHP enum with three cases: <code translate="no" class="notranslate">Drop</code> (removes the element
|
||||
and its children); <code translate="no" class="notranslate">Block</code> (removes the element but keeps its children); and <code translate="no" class="notranslate">Allow</code>
|
||||
(keeps the element).</p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 id="allow-using-defaultnull-on-boolean-nodes"><a class="headerlink" href="#allow-using-defaultnull-on-boolean-nodes" title="Permalink to this headline">Allow Using <code translate="no" class="notranslate">defaultNull()</code> on Boolean Nodes</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://connect.symfony.com/profile/alexandre-daubois">
|
||||
<img src="https://connect.symfony.com/profile/alexandre-daubois.picture" alt="Alexandre Daubois">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/alexandre-daubois">Alexandre Daubois</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/58490">#58490</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>The current <code translate="no" class="notranslate">defaultNull()</code> of <code translate="no" class="notranslate">BooleanNode</code>, used when
|
||||
<a href="https://symfony.com/doc/current/components/config/definition.html" class="reference external">defining and processing configuration values</a>, casts null values to <code translate="no" class="notranslate">true</code>.
|
||||
In Symfony 7.2, we've updated this method so you can define nullable boolean
|
||||
values properly:</p>
|
||||
<div translate="no" data-loc="1" class="notranslate codeblock codeblock-length-sm codeblock-php">
|
||||
<div class="codeblock-scroll">
|
||||
|
||||
<pre class="codeblock-code"><code>-><span class="hljs-title invoke__">booleanNode</span>(<span class="hljs-string">'enabled'</span>)-><span class="hljs-title invoke__">defaultNull</span>()-><span class="hljs-title invoke__">end</span>()</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 id="better-ip-address-anonymization"><a class="headerlink" href="#better-ip-address-anonymization" title="Permalink to this headline">Better IP Address Anonymization</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://connect.symfony.com/profile/alexandre-daubois">
|
||||
<img src="https://connect.symfony.com/profile/alexandre-daubois.picture" alt="Alexandre Daubois">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/alexandre-daubois">Alexandre Daubois</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/58038">#58038</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code translate="no" class="notranslate">IpUtils</code> class includes an <code translate="no" class="notranslate">anonymize()</code> method to obscure part of the IP
|
||||
address for user privacy. In Symfony 7.2, we've added two new arguments to this
|
||||
method so you can specify how many bytes to anonymize:</p>
|
||||
<div translate="no" data-loc="13" class="notranslate codeblock codeblock-length-md codeblock-php">
|
||||
<div class="codeblock-scroll">
|
||||
|
||||
<pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpFoundation</span>\<span class="hljs-title">IpUtils</span>;
|
||||
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>ipv4</span> = <span class="hljs-string">'123.234.235.236'</span>;
|
||||
<span class="hljs-comment">// for IPv4 addresses, you can hide 0 to 4 bytes</span>
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>anonymousIpv4</span> = IpUtils::<span class="hljs-title invoke__">anonymize</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>ipv4</span>, <span class="hljs-number">3</span>);
|
||||
<span class="hljs-comment">// $anonymousIpv4 = '123.0.0.0'</span>
|
||||
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>ipv6</span> = <span class="hljs-string">'2a01:198:603:10:396e:4789:8e99:890f'</span>;
|
||||
<span class="hljs-comment">// for IPv6 addresses, you can hide 0 to 16 bytes</span>
|
||||
<span class="hljs-comment">// (you must define the second argument (bytes to anonymize in IPv4 addresses)</span>
|
||||
<span class="hljs-comment">// even when you are only anonymizing IPv6 addresses)</span>
|
||||
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>anonymousIpv6</span> = IpUtils::<span class="hljs-title invoke__">anonymize</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>ipv6</span>, <span class="hljs-number">3</span>, <span class="hljs-number">10</span>);
|
||||
<span class="hljs-comment">// $anonymousIpv6 = '2a01:198:603::'</span></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 id="string-configuration-node"><a class="headerlink" href="#string-configuration-node" title="Permalink to this headline">String Configuration Node</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://github.com/raffaelecarelle">
|
||||
<img src="https://github.com/raffaelecarelle.png" alt="Raffaele Carelle">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://github.com/raffaelecarelle">Raffaele Carelle</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/58428">#58428</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>When defining a <a href="https://symfony.com/doc/current/components/config/definition.html" class="reference external">configuration tree</a>, you can use many node types for
|
||||
configuration values (boolean, integers, floats, enums, arrays, etc.). However,
|
||||
you couldn't define string values directly; they were specified as <code translate="no" class="notranslate">scalar</code> nodes.</p>
|
||||
<p>In Symfony 7.2, we've added a string node type and a <code translate="no" class="notranslate">stringNode()</code> method,
|
||||
allowing you to define configuration values as strings explicitly:</p>
|
||||
<div translate="no" data-loc="11" class="notranslate codeblock codeblock-length-md codeblock-php">
|
||||
<div class="codeblock-scroll">
|
||||
|
||||
<pre class="codeblock-code"><code><span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>rootNode</span>
|
||||
-><span class="hljs-title invoke__">children</span>()
|
||||
<span class="hljs-comment">// ...</span>
|
||||
-><span class="hljs-title invoke__">stringNode</span>(<span class="hljs-string">'username'</span>)
|
||||
-><span class="hljs-title invoke__">defaultValue</span>(<span class="hljs-string">'root'</span>)
|
||||
-><span class="hljs-title invoke__">end</span>()
|
||||
-><span class="hljs-title invoke__">stringNode</span>(<span class="hljs-string">'password'</span>)
|
||||
-><span class="hljs-title invoke__">defaultValue</span>(<span class="hljs-string">'root'</span>)
|
||||
-><span class="hljs-title invoke__">end</span>()
|
||||
-><span class="hljs-title invoke__">end</span>()
|
||||
;</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2 id="security-profiler-improvements"><a class="headerlink" href="#security-profiler-improvements" title="Permalink to this headline">Security Profiler Improvements</a></h2>
|
||||
<div class="blog-post-contributor-info">
|
||||
<div class="blog-post-contributor-avatar">
|
||||
<a target="_blank" href="https://github.com/MatTheCat">
|
||||
<img src="https://github.com/MatTheCat.png" alt="Mathieu">
|
||||
</a>
|
||||
</div>
|
||||
<div class="blog-post-contributor-contents">
|
||||
<span>Contributed by</span>
|
||||
<a target="_blank" class="blog-post-contributor-name" href="https://github.com/MatTheCat">Mathieu</a>
|
||||
<span class="blog-post-contributor-prs"> in
|
||||
<a target="_blank" href="https://github.com/symfony/symfony/pull/57525">#57525</a>
|
||||
, <a target="_blank" href="https://github.com/symfony/symfony/pull/57369">#57369</a>
|
||||
and <a target="_blank" href="https://github.com/symfony/symfony/pull/57692">#57692</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>In Symfony 7.2, the security panel of the <a href="https://symfony.com/doc/current/profiler.html" class="reference external">Symfony Profiler</a> has been improved
|
||||
with several new features. First, the <strong>authenticators</strong> tab has been updated.
|
||||
Previously, authenticators that didn't support the request were not shown:</p>
|
||||
<div class="figure">
|
||||
<img alt="Symfony security profiler panel with no authenticator shown" class="align-center" src="https://symfony.com/uploads/assets/blog/338546073-37f63661-ad21-4945-b05a-396f4781b88f.png">
|
||||
</div>
|
||||
<p>Now, to make debugging easier, you can see all the application's authenticators.
|
||||
If an authenticator doesn't support the request, it will be labeled as "not supported":</p>
|
||||
<div class="figure">
|
||||
<img alt="Symfony 7.2 security profiler panel with all authenticators shown" class="align-center" src="https://symfony.com/uploads/assets/blog/338546604-ca7241c8-92f1-47f7-bdea-96fce6daf910.png">
|
||||
</div>
|
||||
<p>When using a stateful firewall, the <strong>token</strong> tab of de-authenticated users now
|
||||
includes a link to the request that contained the previously authenticated user:</p>
|
||||
<div class="figure">
|
||||
<img alt="Symfony 7.2 security profiler panel with a link to previous authenticated user" class="align-center" src="https://symfony.com/uploads/assets/blog/347023482-a279ba0f-3634-40ce-8d81-d2009729485b.png">
|
||||
</div>
|
||||
<p>The <strong>authenticators</strong> tab has also been redesigned to display information more
|
||||
clearly. It now also shows whether an authenticator is lazy and includes any exception
|
||||
passed to the <code translate="no" class="notranslate">onAuthenticationFailure()</code> method:</p>
|
||||
<div class="figure">
|
||||
<img alt="Symfony 7.2 security profiler panel with more and redesigned information in the authenticators tab" class="align-center" src="https://symfony.com/uploads/assets/blog/348481614-8d59845d-9629-470a-89ec-49db97b99ccb.png">
|
||||
</div>
|
||||
<hr>
|
||||
<p>This is the final blog post in the <a href="https://symfony.com/blog/category/living-on-the-edge/7.2" class="reference external">New in Symfony 7.2</a> series. We hope you
|
||||
enjoyed it and discovered some of the great new features introduced in Symfony 7.2.
|
||||
Check out <a href="https://symfony.com/doc/current/setup/upgrade_minor.html" class="reference external">the Symfony minor version upgrade guide</a> to learn how to upgrade to 7.2
|
||||
from other 7.x versions. Meanwhile, we've already started working on <a href="https://symfony.com/releases/7.3" class="reference external">Symfony 7.3</a>,
|
||||
which will be released at the end of May 2025.</p>
|
||||
</div>
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
|
||||
<pubDate>Thu, 28 Nov 2024 08:40:00 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/new-in-symfony-7-2-misc-improvements-part-2?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Symfony 7.1.9 released]]></title>
|
||||
<link>https://symfony.com/blog/symfony-7-1-9-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>Symfony 7.1.9 has just been released.
|
||||
Here is the list of the most important changes since 7.1.8:
|
||||
|
||||
bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
|
||||
bug #58562 [HttpClient] Close gracefull when the server closes…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p><a href="https://github.com/symfony/symfony/pull/59019" class="reference external" rel="external noopener noreferrer" target="_blank">Symfony 7.1.9</a> has just been released.
|
||||
Here is the list of the most important changes since 7.1.8:</p>
|
||||
<ul>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59013" class="reference external" rel="external noopener noreferrer" target="_blank">#59013</a> [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58562" class="reference external" rel="external noopener noreferrer" target="_blank">#58562</a> [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59007" class="reference external" rel="external noopener noreferrer" target="_blank">#59007</a> [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58963" class="reference external" rel="external noopener noreferrer" target="_blank">#58963</a> [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58983" class="reference external" rel="external noopener noreferrer" target="_blank">#58983</a> [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58956" class="reference external" rel="external noopener noreferrer" target="_blank">#58956</a> [DoctrineBridge] Fix Connection::createSchemaManager() for Doctrine DBAL v2 (@neodevcode)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58959" class="reference external" rel="external noopener noreferrer" target="_blank">#58959</a> [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58964" class="reference external" rel="external noopener noreferrer" target="_blank">#58964</a> [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58952" class="reference external" rel="external noopener noreferrer" target="_blank">#58952</a> [Cache] silence warnings issued by Redis Sentinel on connection issues (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58859" class="reference external" rel="external noopener noreferrer" target="_blank">#58859</a> [AssetMapper] ignore missing directory in isVendor() (@alexislefebvre)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58917" class="reference external" rel="external noopener noreferrer" target="_blank">#58917</a> [OptionsResolver] Allow Union/Intersection Types in Resolved Closures (@zanbaldwin)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58822" class="reference external" rel="external noopener noreferrer" target="_blank">#58822</a> [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58865" class="reference external" rel="external noopener noreferrer" target="_blank">#58865</a> Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58921" class="reference external" rel="external noopener noreferrer" target="_blank">#58921</a> [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58908" class="reference external" rel="external noopener noreferrer" target="_blank">#58908</a> [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58938" class="reference external" rel="external noopener noreferrer" target="_blank">#58938</a> [Cache] make RelayProxyTrait compatible with relay extension 0.9.0 (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58924" class="reference external" rel="external noopener noreferrer" target="_blank">#58924</a> [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58915" class="reference external" rel="external noopener noreferrer" target="_blank">#58915</a> [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58919" class="reference external" rel="external noopener noreferrer" target="_blank">#58919</a> [WebProfilerBundle] Twig deprecations (@mazodude)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58914" class="reference external" rel="external noopener noreferrer" target="_blank">#58914</a> [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58870" class="reference external" rel="external noopener noreferrer" target="_blank">#58870</a> [Serializer][Validator] prevent failures around not existing TypeInfo classes (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58872" class="reference external" rel="external noopener noreferrer" target="_blank">#58872</a> [PropertyInfo][Serializer][Validator] TypeInfo 7.2 compatibility (@mtarld)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58875" class="reference external" rel="external noopener noreferrer" target="_blank">#58875</a> [HttpClient] Removed body size limit (Carl Julian Sauter)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58866" class="reference external" rel="external noopener noreferrer" target="_blank">#58866</a> [Validator] fix compatibility with PHP < 8.2.4 (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58862" class="reference external" rel="external noopener noreferrer" target="_blank">#58862</a> [Notifier] Fix GoIpTransport (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58860" class="reference external" rel="external noopener noreferrer" target="_blank">#58860</a> [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58836" class="reference external" rel="external noopener noreferrer" target="_blank">#58836</a> Work around parse_url() bug (bis) (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58818" class="reference external" rel="external noopener noreferrer" target="_blank">#58818</a> [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58828" class="reference external" rel="external noopener noreferrer" target="_blank">#58828</a> [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58842" class="reference external" rel="external noopener noreferrer" target="_blank">#58842</a> [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58850" class="reference external" rel="external noopener noreferrer" target="_blank">#58850</a> [HttpClient] fix PHP 7.2 compatibility (@xabbuh)</li>
|
||||
</ul>
|
||||
<p>Want to upgrade to this new release? Because Symfony protects
|
||||
backwards-compatibility very closely, this should be quite easy. Use
|
||||
<a href="https://insight.symfony.com/" class="reference external">SymfonyInsight upgrade reports</a>
|
||||
to detect the code you will need to change in your project and
|
||||
<a href="https://symfony.com/doc/current/cookbook/upgrade/index.html" class="reference external">read our upgrade</a>
|
||||
documentation to learn more.</p>
|
||||
<p>Want to be notified whenever a new Symfony release is published? Or when a
|
||||
version is not maintained anymore? Or only when a security issue is fixed?
|
||||
Consider <a href="https://symfony.com/account/notifications" class="reference external">subscribing to the Symfony Roadmap Notifications</a>.</p>
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfony-7-1-9-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
|
||||
<pubDate>Wed, 27 Nov 2024 14:02:38 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfony-7-1-9-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Symfony 6.4.16 released]]></title>
|
||||
<link>https://symfony.com/blog/symfony-6-4-16-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>Symfony 6.4.16 has just been released.
|
||||
Here is the list of the most important changes since 6.4.15:
|
||||
|
||||
bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
|
||||
bug #58562 [HttpClient] Close gracefull when the server closes…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p><a href="https://github.com/symfony/symfony/pull/59017" class="reference external" rel="external noopener noreferrer" target="_blank">Symfony 6.4.16</a> has just been released.
|
||||
Here is the list of the most important changes since 6.4.15:</p>
|
||||
<ul>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59013" class="reference external" rel="external noopener noreferrer" target="_blank">#59013</a> [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58562" class="reference external" rel="external noopener noreferrer" target="_blank">#58562</a> [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59007" class="reference external" rel="external noopener noreferrer" target="_blank">#59007</a> [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58963" class="reference external" rel="external noopener noreferrer" target="_blank">#58963</a> [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58983" class="reference external" rel="external noopener noreferrer" target="_blank">#58983</a> [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58956" class="reference external" rel="external noopener noreferrer" target="_blank">#58956</a> [DoctrineBridge] Fix Connection::createSchemaManager() for Doctrine DBAL v2 (@neodevcode)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58959" class="reference external" rel="external noopener noreferrer" target="_blank">#58959</a> [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58964" class="reference external" rel="external noopener noreferrer" target="_blank">#58964</a> [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58952" class="reference external" rel="external noopener noreferrer" target="_blank">#58952</a> [Cache] silence warnings issued by Redis Sentinel on connection issues (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58859" class="reference external" rel="external noopener noreferrer" target="_blank">#58859</a> [AssetMapper] ignore missing directory in isVendor() (@alexislefebvre)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58917" class="reference external" rel="external noopener noreferrer" target="_blank">#58917</a> [OptionsResolver] Allow Union/Intersection Types in Resolved Closures (@zanbaldwin)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58822" class="reference external" rel="external noopener noreferrer" target="_blank">#58822</a> [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58865" class="reference external" rel="external noopener noreferrer" target="_blank">#58865</a> Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58921" class="reference external" rel="external noopener noreferrer" target="_blank">#58921</a> [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58908" class="reference external" rel="external noopener noreferrer" target="_blank">#58908</a> [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58938" class="reference external" rel="external noopener noreferrer" target="_blank">#58938</a> [Cache] make RelayProxyTrait compatible with relay extension 0.9.0 (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58924" class="reference external" rel="external noopener noreferrer" target="_blank">#58924</a> [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58915" class="reference external" rel="external noopener noreferrer" target="_blank">#58915</a> [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58919" class="reference external" rel="external noopener noreferrer" target="_blank">#58919</a> [WebProfilerBundle] Twig deprecations (@mazodude)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58914" class="reference external" rel="external noopener noreferrer" target="_blank">#58914</a> [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58875" class="reference external" rel="external noopener noreferrer" target="_blank">#58875</a> [HttpClient] Removed body size limit (Carl Julian Sauter)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58862" class="reference external" rel="external noopener noreferrer" target="_blank">#58862</a> [Notifier] Fix GoIpTransport (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58860" class="reference external" rel="external noopener noreferrer" target="_blank">#58860</a> [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58836" class="reference external" rel="external noopener noreferrer" target="_blank">#58836</a> Work around parse_url() bug (bis) (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58818" class="reference external" rel="external noopener noreferrer" target="_blank">#58818</a> [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58828" class="reference external" rel="external noopener noreferrer" target="_blank">#58828</a> [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58842" class="reference external" rel="external noopener noreferrer" target="_blank">#58842</a> [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58850" class="reference external" rel="external noopener noreferrer" target="_blank">#58850</a> [HttpClient] fix PHP 7.2 compatibility (@xabbuh)</li>
|
||||
</ul>
|
||||
<p>Want to upgrade to this new release? Because Symfony protects
|
||||
backwards-compatibility very closely, this should be quite easy. Use
|
||||
<a href="https://insight.symfony.com/" class="reference external">SymfonyInsight upgrade reports</a>
|
||||
to detect the code you will need to change in your project and
|
||||
<a href="https://symfony.com/doc/current/cookbook/upgrade/index.html" class="reference external">read our upgrade</a>
|
||||
documentation to learn more.</p>
|
||||
<p>Want to be notified whenever a new Symfony release is published? Or when a
|
||||
version is not maintained anymore? Or only when a security issue is fixed?
|
||||
Consider <a href="https://symfony.com/account/notifications" class="reference external">subscribing to the Symfony Roadmap Notifications</a>.</p>
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfony-6-4-16-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
|
||||
<pubDate>Wed, 27 Nov 2024 13:54:27 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfony-6-4-16-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Symfony 5.4.48 released]]></title>
|
||||
<link>https://symfony.com/blog/symfony-5-4-48-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</link>
|
||||
<description>Symfony 5.4.48 has just been released.
|
||||
Here is the list of the most important changes since 5.4.47:
|
||||
|
||||
bug #59013 [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)
|
||||
bug #58562 [HttpClient] Close gracefull when the server closes…</description>
|
||||
<content:encoded><![CDATA[
|
||||
<p><a href="https://github.com/symfony/symfony/pull/59016" class="reference external" rel="external noopener noreferrer" target="_blank">Symfony 5.4.48</a> has just been released.
|
||||
Here is the list of the most important changes since 5.4.47:</p>
|
||||
<ul>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59013" class="reference external" rel="external noopener noreferrer" target="_blank">#59013</a> [HttpClient] Fix checking for private IPs before connecting (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58562" class="reference external" rel="external noopener noreferrer" target="_blank">#58562</a> [HttpClient] Close gracefull when the server closes the connection abruptly (@discordier)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/59007" class="reference external" rel="external noopener noreferrer" target="_blank">#59007</a> [Dotenv] read runtime config from composer.json in debug dotenv command (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58963" class="reference external" rel="external noopener noreferrer" target="_blank">#58963</a> [PropertyInfo] Fix write visibility for Asymmetric Visibility and Virtual Properties (@xabbuh, @pan93412)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58983" class="reference external" rel="external noopener noreferrer" target="_blank">#58983</a> [Translation] [Bridge][Lokalise] Fix empty keys array in PUT, DELETE requests causing Lokalise API error (@DominicLuidold)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58959" class="reference external" rel="external noopener noreferrer" target="_blank">#58959</a> [PropertyInfo] consider write property visibility to decide whether a property is writable (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58964" class="reference external" rel="external noopener noreferrer" target="_blank">#58964</a> [TwigBridge] do not add child nodes to EmptyNode instances (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58822" class="reference external" rel="external noopener noreferrer" target="_blank">#58822</a> [DependencyInjection] Fix checking for interfaces in ContainerBuilder::getReflectionClass() (@donquixote)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58865" class="reference external" rel="external noopener noreferrer" target="_blank">#58865</a> Dynamically fix compatibility with doctrine/data-fixtures v2 (@greg0ire)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58921" class="reference external" rel="external noopener noreferrer" target="_blank">#58921</a> [HttpKernel] Ensure HttpCache::getTraceKey() does not throw exception (@lyrixx)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58908" class="reference external" rel="external noopener noreferrer" target="_blank">#58908</a> [DoctrineBridge] don't call EntityManager::initializeObject() with scalar values (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58924" class="reference external" rel="external noopener noreferrer" target="_blank">#58924</a> [HttpClient] Fix empty hosts in option "resolve" (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58915" class="reference external" rel="external noopener noreferrer" target="_blank">#58915</a> [HttpClient] Fix option "resolve" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58919" class="reference external" rel="external noopener noreferrer" target="_blank">#58919</a> [WebProfilerBundle] Twig deprecations (@mazodude)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58914" class="reference external" rel="external noopener noreferrer" target="_blank">#58914</a> [HttpClient] Fix option "bindto" with IPv6 addresses (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58875" class="reference external" rel="external noopener noreferrer" target="_blank">#58875</a> [HttpClient] Removed body size limit (Carl Julian Sauter)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58860" class="reference external" rel="external noopener noreferrer" target="_blank">#58860</a> [HttpClient] Fix catching some invalid Location headers (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58836" class="reference external" rel="external noopener noreferrer" target="_blank">#58836</a> Work around parse_url() bug (bis) (@nicolas-grekas)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58818" class="reference external" rel="external noopener noreferrer" target="_blank">#58818</a> [Messenger] silence PHP warnings issued by Redis::connect() (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58828" class="reference external" rel="external noopener noreferrer" target="_blank">#58828</a> [PhpUnitBridge] fix dumping tests to skip with data providers (@xabbuh)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58842" class="reference external" rel="external noopener noreferrer" target="_blank">#58842</a> [Routing] Fix: lost priority when defining hosts in configuration (@BeBlood)</li>
|
||||
<li>bug <a href="https://github.com/symfony/symfony/pull/58850" class="reference external" rel="external noopener noreferrer" target="_blank">#58850</a> [HttpClient] fix PHP 7.2 compatibility (@xabbuh)</li>
|
||||
</ul>
|
||||
<p>Want to upgrade to this new release? Because Symfony protects
|
||||
backwards-compatibility very closely, this should be quite easy. Use
|
||||
<a href="https://insight.symfony.com/" class="reference external">SymfonyInsight upgrade reports</a>
|
||||
to detect the code you will need to change in your project and
|
||||
<a href="https://symfony.com/doc/current/cookbook/upgrade/index.html" class="reference external">read our upgrade</a>
|
||||
documentation to learn more.</p>
|
||||
<p>Want to be notified whenever a new Symfony release is published? Or when a
|
||||
version is not maintained anymore? Or only when a security issue is fixed?
|
||||
Consider <a href="https://symfony.com/account/notifications" class="reference external">subscribing to the Symfony Roadmap Notifications</a>.</p>
|
||||
<hr style="margin-bottom: 5px" />
|
||||
<div style="font-size: 90%">
|
||||
<a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
|
||||
</div>
|
||||
]]></content:encoded>
|
||||
<guid isPermaLink="false">https://symfony.com/blog/symfony-5-4-48-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed</guid>
|
||||
<dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
|
||||
<pubDate>Wed, 27 Nov 2024 13:48:58 +0100</pubDate>
|
||||
<comments>https://symfony.com/blog/symfony-5-4-48-released?utm_source=Symfony%20Blog%20Feed&utm_medium=feed#comments-list</comments>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
56
demo/tests/SmokeTest.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversNothing;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
|
||||
|
||||
#[CoversNothing]
|
||||
final class SmokeTest extends WebTestCase
|
||||
{
|
||||
use InteractsWithLiveComponents;
|
||||
|
||||
public function testIndex(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextSame('h1', 'Welcome to the LLM Chain Demo');
|
||||
self::assertSelectorCount(5, '.card');
|
||||
}
|
||||
|
||||
#[DataProvider('provideChats')]
|
||||
public function testChats(string $path, string $expectedHeadline): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', $path);
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextSame('h4', $expectedHeadline);
|
||||
self::assertSelectorCount(1, '#chat-submit');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array{string, string}>
|
||||
*/
|
||||
public static function provideChats(): iterable
|
||||
{
|
||||
yield 'Blog' => ['/blog', 'Retrieval Augmented Generation based on the Symfony blog'];
|
||||
yield 'YouTube' => ['/youtube', 'Chat about a YouTube Video'];
|
||||
yield 'Wikipedia' => ['/wikipedia', 'Wikipedia Research'];
|
||||
}
|
||||
}
|
||||
36
demo/tests/YouTube/TranscriptFetcherTest.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\YouTube;
|
||||
|
||||
use App\YouTube\TranscriptFetcher;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
|
||||
#[CoversClass(TranscriptFetcher::class), ]
|
||||
final class TranscriptFetcherTest extends TestCase
|
||||
{
|
||||
public function testFetchTranscript(): void
|
||||
{
|
||||
$videoResponse = MockResponse::fromFile(__DIR__.'/fixtures/video.html');
|
||||
$transcriptResponse = MockResponse::fromFile(__DIR__.'/fixtures/transcript.xml');
|
||||
$mockClient = new MockHttpClient([$videoResponse, $transcriptResponse]);
|
||||
|
||||
$fetcher = new TranscriptFetcher($mockClient);
|
||||
$transcript = $fetcher->fetchTranscript('6uXW-ulpj0s');
|
||||
|
||||
self::assertStringContainsString('symphony is a PHP framework', $transcript);
|
||||
}
|
||||
}
|
||||