Skip to content

Commit 68822da

Browse files
committed
feat: initial setup of demo app
0 parents  commit 68822da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+10795
-0
lines changed

.env

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# In all environments, the following files are loaded if they exist,
2+
# the latter taking precedence over the former:
3+
#
4+
# * .env contains default values for the environment variables needed by the app
5+
# * .env.local uncommitted file with local overrides
6+
# * .env.$APP_ENV committed environment-specific defaults
7+
# * .env.$APP_ENV.local uncommitted environment-specific overrides
8+
#
9+
# Real environment variables win over .env files.
10+
#
11+
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
12+
# https://symfony.com/doc/current/configuration/secrets.html
13+
#
14+
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
15+
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
16+
17+
###> symfony/framework-bundle ###
18+
APP_ENV=dev
19+
APP_SECRET=ccb9dca72dce53c683eaaf775bfdb253
20+
###< symfony/framework-bundle ###
21+
22+
CHROMADB_HOST=chromadb
23+
OPENAI_API_KEY=sk-...

.env.test

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# define your env variables for the test env here
2+
KERNEL_CLASS='App\Kernel'
3+
APP_SECRET='$ecretf0rt3st'
4+
SYMFONY_DEPRECATIONS_HELPER=999999
5+
PANTHER_APP_ENV=panther
6+
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
7+
OPENAI_API_KEY=sk-proj-testing1234

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
###> symfony/framework-bundle ###
2+
/.env.local
3+
/.env.local.php
4+
/.env.*.local
5+
/config/secrets/dev/dev.decrypt.private.php
6+
/config/secrets/prod/prod.decrypt.private.php
7+
/public/bundles/
8+
/var/
9+
/vendor/
10+
###< symfony/framework-bundle ###
11+
###> symfony/asset-mapper ###
12+
/public/assets/
13+
/assets/vendor/
14+
###< symfony/asset-mapper ###
15+
16+
###> php-cs-fixer/shim ###
17+
/.php-cs-fixer.php
18+
/.php-cs-fixer.cache
19+
###< php-cs-fixer/shim ###
20+
21+
###> phpstan/phpstan ###
22+
phpstan.neon
23+
###< phpstan/phpstan ###
24+
25+
###> phpunit/phpunit ###
26+
.phpunit.cache
27+
###< phpunit/phpunit ###
28+
29+
chromadb

.php-cs-fixer.dist.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
$finder = (new PhpCsFixer\Finder())
4+
->in(__DIR__)
5+
->exclude('var')
6+
->exclude('config/secrets')
7+
;
8+
9+
return (new PhpCsFixer\Config())
10+
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
11+
->setRules([
12+
'@Symfony' => true,
13+
])
14+
->setFinder($finder)
15+
;

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2024 Christopher Hertel
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# LLM Chain - Symfony Demo Chatbot Application
2+
3+
Simple Symfony demo application on top of [LLM Chain](https://github.com/php-llm/llm-chain) and its [integration bundle](https://github.com/php-llm/llm-chain-bundle).
4+
5+
## Examples
6+
![demo.png](demo.png)
7+
8+
## Requirements
9+
10+
What you need to run this demo:
11+
12+
* Internet Connection
13+
* Terminal & Browser
14+
* [Git](https://git-scm.com/) & [GitHub Account](https://github.com)
15+
* [Docker](https://www.docker.com/) with [Docker Compose Plugin](https://docs.docker.com/compose/)
16+
* Your Favorite IDE or Editor
17+
18+
## Technology
19+
20+
This small demo sits on top of following technologies:
21+
22+
* [PHP >= 8.4](https://www.php.net/releases/8.4/en.php)
23+
* [Symfony 7.2 incl. Twig, Asset Mapper & UX](https://symfony.com/)
24+
* [Bootstrap 5](https://getbootstrap.com/docs/5.0/getting-started/introduction/)
25+
* [OpenAI's GPT & Embeddings](https://platform.openai.com/docs/overview)
26+
* [ChromaDB Vector Store](https://www.trychroma.com/)
27+
* [FrankenPHP](https://frankenphp.dev/)
28+
29+
## Setup
30+
31+
The setup is split into three parts, the Symfony application, the OpenAI configuration, and initializing the Chroma DB.
32+
33+
### 1. Symfony App
34+
35+
Checkout the repository, start the docker environment and install dependencies:
36+
```shell
37+
git clone [email protected]:php-llm/symfony-demo.git
38+
cd symfony-demo
39+
docker compose up -d
40+
docker compose run composer install --no-scripts
41+
```
42+
43+
Now you should be able to open https://localhost/ in your browser,
44+
and the chatbot UI should be available for you to start chatting.
45+
46+
### 2. OpenAI Configuration
47+
48+
For using GPT and embedding models from OpenAI, you need to configure an OpenAI API key as environment variable.
49+
This requires you to have an OpenAI account, create a valid API key and set it as `OPENAI_API_KEY` in `.env.local` file.
50+
51+
Verify the success of this step by running the following command:
52+
```shell
53+
docker compose exec app bin/console debug:dotenv
54+
```
55+
56+
You should be able to see the `OPENAI_API_KEY` in the list of environment variables.
57+
58+
### 3. Chroma DB Initialization
59+
60+
The Chroma DB is a vector store that is used to store embeddings of the chatbot's context.
61+
62+
To initialize the Chroma DB, you need to run the following command:
63+
```shell
64+
docker compose exec app bin/console app:blog:embed -vv
65+
```
66+
67+
Now you should be able to run the test command and get some results:
68+
```shell
69+
docker compose exec app bin/console app:chroma:test
70+
```
71+
72+
**Don't forget to set up the project in your favorite IDE or editor.**
73+
74+
## Functionality
75+
76+
* The chatbot application is a simple and small Symfony 7.2 application.
77+
* The UI is coupled to a Twig LiveComponent, that integrates different `Chat` implementations on top of the user's session.
78+
* You can reset the chat context by hitting the `Reset` button in the top right corner.
79+
* You find three different usage scenarios in the upper navbar.

assets/app.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import './bootstrap.js';
2+
import 'bootstrap/dist/css/bootstrap.min.css';
3+
import './styles/app.css';

assets/bootstrap.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { startStimulusApp } from '@symfony/stimulus-bundle';
2+
3+
const app = startStimulusApp();
4+
// register any custom, 3rd party controllers here
5+
// app.register('some_controller_name', SomeImportedController);

assets/controllers.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"controllers": {
3+
"@symfony/ux-live-component": {
4+
"live": {
5+
"enabled": true,
6+
"fetch": "eager",
7+
"autoimport": {
8+
"@symfony/ux-live-component/dist/live.min.css": true
9+
}
10+
}
11+
},
12+
"@symfony/ux-turbo": {
13+
"turbo-core": {
14+
"enabled": true,
15+
"fetch": "eager"
16+
},
17+
"mercure-turbo-stream": {
18+
"enabled": false,
19+
"fetch": "eager"
20+
}
21+
},
22+
"@symfony/ux-typed": {
23+
"typed": {
24+
"enabled": true,
25+
"fetch": "eager"
26+
}
27+
}
28+
},
29+
"entrypoints": []
30+
}

assets/controllers/chat_controller.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import { getComponent } from '@symfony/ux-live-component';
3+
4+
export default class extends Controller {
5+
async initialize() {
6+
this.component = await getComponent(this.element);
7+
this.scrollToBottom();
8+
9+
const input = document.getElementById('chat-message');
10+
input.addEventListener('keypress', (event) => {
11+
if (event.key === 'Enter') {
12+
this.submitMessage();
13+
}
14+
});
15+
input.focus();
16+
17+
const resetButton = document.getElementById('chat-reset');
18+
resetButton.addEventListener('click', (event) => {
19+
this.component.action('reset');
20+
});
21+
22+
const submitButton = document.getElementById('chat-submit');
23+
submitButton.addEventListener('click', (event) => {
24+
this.submitMessage();
25+
});
26+
27+
this.component.on('loading.state:started', (e,r) => {
28+
if (r.actions.includes('reset')) {
29+
return;
30+
}
31+
document.getElementById('welcome')?.remove();
32+
document.getElementById('loading-message').removeAttribute('class');
33+
this.scrollToBottom();
34+
});
35+
36+
this.component.on('loading.state:finished', () => {
37+
document.getElementById('loading-message').setAttribute('class', 'd-none');
38+
});
39+
40+
this.component.on('render:finished', () => {
41+
this.scrollToBottom();
42+
});
43+
};
44+
45+
submitMessage() {
46+
const input = document.getElementById('chat-message');
47+
const message = input.value;
48+
document
49+
.getElementById('loading-message')
50+
.getElementsByClassName('user-message')[0].innerHTML = message;
51+
this.component.action('submit', { message });
52+
input.value = '';
53+
}
54+
55+
scrollToBottom() {
56+
const chatBody = document.getElementById('chat-body');
57+
chatBody.scrollTop = chatBody.scrollHeight;
58+
}
59+
}

0 commit comments

Comments
 (0)