interface web
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
{"text": " Je fais quoi ? Gant de boxe ou club de golf ? Vas-y club de golf 32 d\u00e9g\u00e2ts pour l'attaque charg\u00e9e ? Ah ouais Pourquoi tu n'as pas pris les gants de boxe ? Parce que \u00e7a, \u00e7a fait 32 d\u00e9g\u00e2ts quand c'est charg\u00e9 C'est excellent Je pensais que c'\u00e9tait les gants de boxe Apr\u00e8s les gants de boxe ils t'aiment vite En vrai vas-y \u00e7a donne envie de les jouer C'est quoi tu veux pour le coup ? Ouais non", "segments": [{"id": 0, "seek": 0, "start": 0.0, "end": 2.72, "text": " Je fais quoi ?", "tokens": [50365, 2588, 12153, 11714, 2506, 50501], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 1, "seek": 0, "start": 2.72, "end": 5.4, "text": " Gant de boxe ou club de golf ?", "tokens": [50501, 460, 394, 368, 2424, 68, 2820, 6482, 368, 12880, 2506, 50635], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 2, "seek": 0, "start": 5.4, "end": 6.24, "text": " Vas-y club de golf", "tokens": [50635, 23299, 12, 88, 6482, 368, 12880, 50677], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 3, "seek": 0, "start": 6.24, "end": 7.5200000000000005, "text": " 32 d\u00e9g\u00e2ts pour l'attaque charg\u00e9e ?", "tokens": [50677, 8858, 2795, 70, 3201, 1373, 2016, 287, 6, 18405, 1077, 1290, 70, 3856, 2506, 50741], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 4, "seek": 0, "start": 7.5200000000000005, "end": 7.9, "text": " Ah ouais", "tokens": [50741, 2438, 30570, 50760], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 5, "seek": 0, "start": 7.9, "end": 12.120000000000001, "text": " Pourquoi tu n'as pas pris les gants de boxe ?", "tokens": [50760, 30333, 2604, 297, 6, 296, 1736, 16163, 1512, 290, 1719, 368, 2424, 68, 2506, 50971], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 6, "seek": 0, "start": 12.120000000000001, "end": 14.200000000000001, "text": " Parce que \u00e7a, \u00e7a fait 32 d\u00e9g\u00e2ts quand c'est charg\u00e9", "tokens": [50971, 20429, 631, 2788, 11, 2788, 3887, 8858, 2795, 70, 3201, 1373, 6932, 269, 6, 377, 1290, 70, 526, 51075], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 7, "seek": 0, "start": 14.200000000000001, "end": 16.86, "text": " C'est excellent", "tokens": [51075, 383, 6, 377, 7103, 51208], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 8, "seek": 0, "start": 16.86, "end": 17.94, "text": " Je pensais que c'\u00e9tait les gants de boxe", "tokens": [51208, 2588, 6099, 1527, 631, 269, 6, 9743, 1512, 290, 1719, 368, 2424, 68, 51262], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 9, "seek": 0, "start": 17.94, "end": 20.56, "text": " Apr\u00e8s les gants de boxe ils t'aiment vite", "tokens": [51262, 29265, 1512, 290, 1719, 368, 2424, 68, 9047, 256, 6, 64, 2328, 24462, 51393], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 10, "seek": 0, "start": 20.56, "end": 21.78, "text": " En vrai vas-y \u00e7a donne envie de les jouer", "tokens": [51393, 2193, 17815, 11481, 12, 88, 2788, 21837, 24149, 368, 1512, 30823, 51454], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 11, "seek": 0, "start": 21.78, "end": 24.0, "text": " C'est quoi tu veux pour le coup ?", "tokens": [51454, 383, 6, 377, 11714, 2604, 16389, 2016, 476, 8682, 2506, 51565], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}, {"id": 12, "seek": 0, "start": 25.28, "end": 25.96, "text": " Ouais non", "tokens": [51629, 25475, 2107, 51663], "temperature": 0.0, "avg_logprob": -0.25769146068676096, "compression_ratio": 1.6570247933884297, "no_speech_prob": 0.4927598834037781}], "language": "fr"}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
1
|
||||||
|
00:00:00,000 --> 00:00:02,720
|
||||||
|
Je fais quoi ?
|
||||||
|
|
||||||
|
2
|
||||||
|
00:00:02,720 --> 00:00:05,400
|
||||||
|
Gant de boxe ou club de golf ?
|
||||||
|
|
||||||
|
3
|
||||||
|
00:00:05,400 --> 00:00:06,240
|
||||||
|
Vas-y club de golf
|
||||||
|
|
||||||
|
4
|
||||||
|
00:00:06,240 --> 00:00:07,520
|
||||||
|
32 dégâts pour l'attaque chargée ?
|
||||||
|
|
||||||
|
5
|
||||||
|
00:00:07,520 --> 00:00:07,900
|
||||||
|
Ah ouais
|
||||||
|
|
||||||
|
6
|
||||||
|
00:00:07,900 --> 00:00:12,120
|
||||||
|
Pourquoi tu n'as pas pris les gants de boxe ?
|
||||||
|
|
||||||
|
7
|
||||||
|
00:00:12,120 --> 00:00:14,200
|
||||||
|
Parce que ça, ça fait 32 dégâts quand c'est chargé
|
||||||
|
|
||||||
|
8
|
||||||
|
00:00:14,200 --> 00:00:16,860
|
||||||
|
C'est excellent
|
||||||
|
|
||||||
|
9
|
||||||
|
00:00:16,860 --> 00:00:17,940
|
||||||
|
Je pensais que c'était les gants de boxe
|
||||||
|
|
||||||
|
10
|
||||||
|
00:00:17,940 --> 00:00:20,560
|
||||||
|
Après les gants de boxe ils t'aiment vite
|
||||||
|
|
||||||
|
11
|
||||||
|
00:00:20,560 --> 00:00:21,780
|
||||||
|
En vrai vas-y ça donne envie de les jouer
|
||||||
|
|
||||||
|
12
|
||||||
|
00:00:21,780 --> 00:00:24,000
|
||||||
|
C'est quoi tu veux pour le coup ?
|
||||||
|
|
||||||
|
13
|
||||||
|
00:00:25,280 --> 00:00:25,960
|
||||||
|
Ouais non
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
start end text
|
||||||
|
0 2720 Je fais quoi ?
|
||||||
|
2720 5400 Gant de boxe ou club de golf ?
|
||||||
|
5400 6240 Vas-y club de golf
|
||||||
|
6240 7520 32 dégâts pour l'attaque chargée ?
|
||||||
|
7520 7900 Ah ouais
|
||||||
|
7900 12120 Pourquoi tu n'as pas pris les gants de boxe ?
|
||||||
|
12120 14200 Parce que ça, ça fait 32 dégâts quand c'est chargé
|
||||||
|
14200 16860 C'est excellent
|
||||||
|
16860 17940 Je pensais que c'était les gants de boxe
|
||||||
|
17940 20560 Après les gants de boxe ils t'aiment vite
|
||||||
|
20560 21780 En vrai vas-y ça donne envie de les jouer
|
||||||
|
21780 24000 C'est quoi tu veux pour le coup ?
|
||||||
|
25280 25960 Ouais non
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
Je fais quoi ?
|
||||||
|
Gant de boxe ou club de golf ?
|
||||||
|
Vas-y club de golf
|
||||||
|
32 dégâts pour l'attaque chargée ?
|
||||||
|
Ah ouais
|
||||||
|
Pourquoi tu n'as pas pris les gants de boxe ?
|
||||||
|
Parce que ça, ça fait 32 dégâts quand c'est chargé
|
||||||
|
C'est excellent
|
||||||
|
Je pensais que c'était les gants de boxe
|
||||||
|
Après les gants de boxe ils t'aiment vite
|
||||||
|
En vrai vas-y ça donne envie de les jouer
|
||||||
|
C'est quoi tu veux pour le coup ?
|
||||||
|
Ouais non
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
WEBVTT
|
||||||
|
|
||||||
|
00:00.000 --> 00:02.720
|
||||||
|
Je fais quoi ?
|
||||||
|
|
||||||
|
00:02.720 --> 00:05.400
|
||||||
|
Gant de boxe ou club de golf ?
|
||||||
|
|
||||||
|
00:05.400 --> 00:06.240
|
||||||
|
Vas-y club de golf
|
||||||
|
|
||||||
|
00:06.240 --> 00:07.520
|
||||||
|
32 dégâts pour l'attaque chargée ?
|
||||||
|
|
||||||
|
00:07.520 --> 00:07.900
|
||||||
|
Ah ouais
|
||||||
|
|
||||||
|
00:07.900 --> 00:12.120
|
||||||
|
Pourquoi tu n'as pas pris les gants de boxe ?
|
||||||
|
|
||||||
|
00:12.120 --> 00:14.200
|
||||||
|
Parce que ça, ça fait 32 dégâts quand c'est chargé
|
||||||
|
|
||||||
|
00:14.200 --> 00:16.860
|
||||||
|
C'est excellent
|
||||||
|
|
||||||
|
00:16.860 --> 00:17.940
|
||||||
|
Je pensais que c'était les gants de boxe
|
||||||
|
|
||||||
|
00:17.940 --> 00:20.560
|
||||||
|
Après les gants de boxe ils t'aiment vite
|
||||||
|
|
||||||
|
00:20.560 --> 00:21.780
|
||||||
|
En vrai vas-y ça donne envie de les jouer
|
||||||
|
|
||||||
|
00:21.780 --> 00:24.000
|
||||||
|
C'est quoi tu veux pour le coup ?
|
||||||
|
|
||||||
|
00:25.280 --> 00:25.960
|
||||||
|
Ouais non
|
||||||
|
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
# 🤖 TwitchBot Controller - Interface Web
|
||||||
|
|
||||||
|
Une interface web moderne en dark mode pour contrôler votre bot Twitch intelligent.
|
||||||
|
|
||||||
|
## 🚀 Fonctionnalités
|
||||||
|
|
||||||
|
### 📊 Tableau de Bord
|
||||||
|
- **Statut en temps réel** : Nombre de flux actifs, enregistrements, connexions chat
|
||||||
|
- **Mises à jour automatiques** : Interface qui se met à jour toutes les 5-10 secondes
|
||||||
|
- **Indicateur de connexion** : Statut de connexion au serveur en temps réel
|
||||||
|
|
||||||
|
### 🎥 Gestion des Flux
|
||||||
|
- **Ajout de flux** : Ajouter des canaux Twitch à surveiller
|
||||||
|
- **Contrôle audio** : Choisir d'enregistrer ou non l'audio du stream
|
||||||
|
- **Surveillance chat** : Connexion automatique au chat de chaque flux
|
||||||
|
- **Gestion dynamique** : Ajouter/supprimer des flux sans redémarrer
|
||||||
|
|
||||||
|
### 💬 Gestion des Messages
|
||||||
|
- **Prochain message** : Voir le message généré par l'IA qui sera envoyé
|
||||||
|
- **Envoi manuel** : Envoyer le prochain message ou un message personnalisé
|
||||||
|
- **Historique** : Voir les derniers messages envoyés avec horodatage
|
||||||
|
- **Actions rapides** : Boutons pour envoyer rapidement la dernière génération
|
||||||
|
|
||||||
|
### 🎯 Configuration des Prompts IA
|
||||||
|
- **Édition en direct** : Modifier les prompts de génération IA directement
|
||||||
|
- **Ajout/suppression** : Gérer dynamiquement la liste des prompts
|
||||||
|
- **Sauvegarde** : Sauvegarder les modifications en temps réel
|
||||||
|
- **Interface intuitive** : Textareas redimensionnables pour chaque prompt
|
||||||
|
|
||||||
|
### 🎙️ Sous-titres et Transcription
|
||||||
|
- **Dernier texte détecté** : Affichage du dernier sous-titre généré
|
||||||
|
- **Historique complet** : Liste des 10 derniers sous-titres avec timestamps
|
||||||
|
- **Génération automatique** : Bouton pour générer une réponse IA à partir du texte
|
||||||
|
- **Mise à jour temps réel** : Nouveaux sous-titres affichés automatiquement
|
||||||
|
|
||||||
|
## 🛠️ Installation
|
||||||
|
|
||||||
|
### 1. Installer les dépendances
|
||||||
|
```bash
|
||||||
|
pip install -r requirements_web.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Démarrer l'interface web
|
||||||
|
```bash
|
||||||
|
python start_web_interface.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou avec des options personnalisées :
|
||||||
|
```bash
|
||||||
|
python start_web_interface.py --host 0.0.0.0 --port 8080 --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Accéder à l'interface
|
||||||
|
Ouvrez votre navigateur et allez à : `http://localhost:5000`
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
### Fichiers de configuration
|
||||||
|
L'interface web utilise les mêmes fichiers de configuration que votre bot :
|
||||||
|
|
||||||
|
- `config/config.json` : Configuration principale
|
||||||
|
- `config/user.json` : Comptes Twitch pour l'envoi de messages
|
||||||
|
|
||||||
|
### Configuration automatique
|
||||||
|
Au premier démarrage, des fichiers de configuration par défaut sont créés. Modifiez-les avec vos paramètres :
|
||||||
|
|
||||||
|
```json
|
||||||
|
// config/config.json
|
||||||
|
{
|
||||||
|
"twitchname": "votre_channel",
|
||||||
|
"language": "fr",
|
||||||
|
"list_prompt": [
|
||||||
|
"Réponds en 8 mots max avec humour : ",
|
||||||
|
"Réagis comme un viewer twitch en 6 mots : "
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// config/user.json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"tw_acc_pseudo": "votre_pseudo",
|
||||||
|
"tw_acc_token": "oauth:votre_token",
|
||||||
|
"charactere": "😊"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Interface Dark Mode
|
||||||
|
|
||||||
|
L'interface utilise un thème dark moderne avec :
|
||||||
|
- **Couleurs adaptées** : Palette sombre pour réduire la fatigue oculaire
|
||||||
|
- **Animations fluides** : Transitions et effets visuels
|
||||||
|
- **Design responsive** : Compatible mobile et desktop
|
||||||
|
- **Icons FontAwesome** : Interface moderne et intuitive
|
||||||
|
|
||||||
|
### Couleurs principales
|
||||||
|
- Background principal : `#1a1a1a`
|
||||||
|
- Background secondaire : `#2d2d2d`
|
||||||
|
- Accent : `#007bff`
|
||||||
|
- Succès : `#28a745`
|
||||||
|
- Avertissement : `#ffc107`
|
||||||
|
|
||||||
|
## 📱 Utilisation
|
||||||
|
|
||||||
|
### Onglet Flux
|
||||||
|
1. **Ajouter un flux** : Cliquez sur "Ajouter Flux"
|
||||||
|
2. **Nom du canal** : Entrez le nom du canal Twitch
|
||||||
|
3. **Options audio** : Cochez pour enregistrer l'audio
|
||||||
|
4. **Gestion** : Utilisez les boutons pause/play et supprimer
|
||||||
|
|
||||||
|
### Onglet Messages
|
||||||
|
1. **Prochain message** : Voir le message généré par l'IA
|
||||||
|
2. **Envoyer** : Cliquer pour envoyer le message dans le chat
|
||||||
|
3. **Message personnalisé** : Taper et envoyer vos propres messages
|
||||||
|
4. **Historique** : Consulter les messages récents envoyés
|
||||||
|
|
||||||
|
### Onglet Prompts IA
|
||||||
|
1. **Modifier** : Cliquer dans les textareas pour éditer
|
||||||
|
2. **Ajouter** : Bouton "Ajouter Prompt" pour de nouveaux prompts
|
||||||
|
3. **Supprimer** : Bouton ❌ sur chaque prompt
|
||||||
|
4. **Sauvegarder** : Bouton "Sauvegarder" pour appliquer les changements
|
||||||
|
|
||||||
|
### Onglet Sous-titres
|
||||||
|
1. **Texte actuel** : Voir le dernier texte détecté du stream
|
||||||
|
2. **Générer réponse** : Cliquer pour lancer la génération IA
|
||||||
|
3. **Historique** : Consulter les derniers sous-titres générés
|
||||||
|
|
||||||
|
## 🔄 Actions Rapides
|
||||||
|
|
||||||
|
### Sidebar - Actions Rapides
|
||||||
|
- **Générer Réponse** : Lance la génération IA sur le dernier sous-titre
|
||||||
|
- **Envoyer Dernière Génération** : Envoie immédiatement la dernière réponse générée
|
||||||
|
- **Actualiser** : Recharge toutes les données de l'interface
|
||||||
|
|
||||||
|
## 🔌 API REST
|
||||||
|
|
||||||
|
L'interface expose une API REST pour l'intégration :
|
||||||
|
|
||||||
|
### Endpoints principaux
|
||||||
|
- `GET /api/status` : Statut général du bot
|
||||||
|
- `GET /api/flux` : Liste des flux actifs
|
||||||
|
- `POST /api/flux` : Ajouter un nouveau flux
|
||||||
|
- `DELETE /api/flux/<id>` : Supprimer un flux
|
||||||
|
- `GET/POST /api/config/prompts` : Gérer les prompts
|
||||||
|
- `POST /api/send-message` : Envoyer un message
|
||||||
|
- `POST /api/generate-response` : Lancer une génération IA
|
||||||
|
|
||||||
|
### WebSocket
|
||||||
|
- Connexion temps réel sur le même port
|
||||||
|
- Événements : `status_update`, `new_subtitle`, `new_generation`
|
||||||
|
|
||||||
|
## 🚨 Dépannage
|
||||||
|
|
||||||
|
### Port déjà utilisé
|
||||||
|
```bash
|
||||||
|
python start_web_interface.py --port 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problèmes de dépendances
|
||||||
|
```bash
|
||||||
|
pip install --upgrade -r requirements_web.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fichiers de configuration manquants
|
||||||
|
Les fichiers sont créés automatiquement au premier démarrage. Vérifiez le dossier `config/`.
|
||||||
|
|
||||||
|
### Connexion WebSocket
|
||||||
|
Si les mises à jour temps réel ne fonctionnent pas, vérifiez que le port n'est pas bloqué par un firewall.
|
||||||
|
|
||||||
|
## 🔐 Sécurité
|
||||||
|
|
||||||
|
- **Accès local** : Par défaut, accessible uniquement en local
|
||||||
|
- **Configuration tokens** : Stockage sécurisé des tokens dans les fichiers config
|
||||||
|
- **Validation** : Validation des entrées côté serveur
|
||||||
|
- **CORS** : Configuration CORS pour les connexions WebSocket
|
||||||
|
|
||||||
|
## 🆕 Nouveautés vs Version Console
|
||||||
|
|
||||||
|
### Avantages de l'interface web
|
||||||
|
- ✅ **Interface graphique moderne** vs commandes clavier
|
||||||
|
- ✅ **Gestion multi-flux** vs flux unique
|
||||||
|
- ✅ **Modification prompts en direct** vs redémarrage requis
|
||||||
|
- ✅ **Historique visuel** vs logs console
|
||||||
|
- ✅ **Contrôle à distance** vs accès local uniquement
|
||||||
|
- ✅ **Statut temps réel** vs statut sur demande
|
||||||
|
|
||||||
|
### Compatibilité
|
||||||
|
L'interface web peut fonctionner :
|
||||||
|
- **Indépendamment** : Comme contrôleur principal
|
||||||
|
- **En parallèle** : Avec votre script console existant
|
||||||
|
- **Migration** : Remplacement progressif de l'interface console
|
||||||
|
|
||||||
|
## 📈 Performance
|
||||||
|
|
||||||
|
- **Mises à jour optimisées** : Seulement les données modifiées
|
||||||
|
- **WebSocket efficace** : Communication bidirectionnelle légère
|
||||||
|
- **Cache intelligent** : Réduction des appels API
|
||||||
|
- **Interface responsive** : Adaptation automatique à la taille d'écran
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🎉 Profitez de votre nouvelle interface web pour contrôler votre bot Twitch !**
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"language":"fr",
|
|
||||||
"tw_acc_pseudo": "foufure13",
|
|
||||||
"tw_acc_token": "oauth:nmpw9bypx9emht7hn5z3vlgbwitaz5",
|
|
||||||
"bad_answer":["suis un assistant","Je ne comprends pas.","pas un humain","je suis désolé"],
|
|
||||||
"list_prompt":["Je vais te donner plusieur a respecter pour répondre a mon texte, 1er règles tu doit répondre en moins de 15mots et tu répondre seulement avec la réponse pas d'affiramtion pour dire que tu comprend, 2eme regle tu doit répondre a ce texte de mainière a réagir avec une blague ou une explication : Voici le texte : ",
|
|
||||||
"réagis avec au text donnée a la fin avec seulement ta réponse au texte en moins de 15mots et soit créatif : ",
|
|
||||||
"Répond en 10 mots avec un charatère gentils comme si tu fesait une remarque en regardant la tv : ",
|
|
||||||
"Réponds en 1 phrase à ce texte avec un peu de sarcasme, comme si tu étais un personnage un peu exaspéré mais sympathique.",
|
|
||||||
"Réponds de manière décontractée et courte, avec une pointe d'humour.",
|
|
||||||
"Réponds de manière concise avec un ton un peu moqueur mais gentil.",
|
|
||||||
"Réponds de façon simple et courte, mais avec une touche d’humour pince-sans-rire.",
|
|
||||||
"Réponds en 1 phrase de manière enthousiaste mais décalée, comme si tu étais un peu trop excité.",
|
|
||||||
"Réponds en 1 phrase avec un peu d'ironie, comme si tu ne prenais pas la situation trop au sérieux.",
|
|
||||||
"Réponds en 1 phrase de façon très simple et directe, mais avec un petit côté humoristique.",
|
|
||||||
"Réponds en 1 phrase comme si tu étais un coach un peu excentrique, motivant mais drôle.",
|
|
||||||
"Réponds en 1 phrase avec une touche de légèreté et de bienveillance, mais en ajoutant un brin d’humour.",
|
|
||||||
"Réponds en 1 phrase de manière décalée, mais avec un fond de vérité sous le ton léger.",
|
|
||||||
|
|
||||||
"Réponds avec humour en 1 phrase, sans trop te prendre au sérieux.",
|
|
||||||
"Réponds avec un peu de sarcasme, mais reste concis.",
|
|
||||||
"Sois direct, avec une touche d'humour, mais ne dépasse pas 15 mots.",
|
|
||||||
"Réponds en mode décalé, mais garde la réponse courte et punchy.",
|
|
||||||
"Réponds avec un ton léger et drôle, en 1 phrase seulement.",
|
|
||||||
"Réponds de manière concise, avec un brin d’ironie.",
|
|
||||||
"Réponds avec une touche d’humour rapide et percutante.",
|
|
||||||
"Réponds de manière brève mais amusante, sans fioritures.",
|
|
||||||
"Réponds avec un peu de moquerie gentille, mais court.",
|
|
||||||
"Sois concis et drôle, avec une touche de légèreté."
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
+24
-36
@@ -1,9 +1,22 @@
|
|||||||
{
|
{
|
||||||
|
"twitchname": "demo_streamer",
|
||||||
|
"recordtime": 60,
|
||||||
|
"threads": 1,
|
||||||
"language": "fr",
|
"language": "fr",
|
||||||
"tw_acc_pseudo": "foufure13",
|
"max_flux_lifetime": 3600,
|
||||||
"tw_acc_token": "oauth:nmpw9bypx9emht7hn5z3vlgbwitaz5",
|
"active_flux": [
|
||||||
"bad_answer":["suis un assistant","Je ne comprends pas.","pas un humain","je suis désolé"],
|
{
|
||||||
"list_prompt":[ "Réponds en 8 mots max avec humour : ",
|
"id": 1,
|
||||||
|
"name": "demo_streamer",
|
||||||
|
"twitchname": "demo_streamer",
|
||||||
|
"quantity": 1,
|
||||||
|
"lifetime": 3600,
|
||||||
|
"created_at": "2025-07-20T01:40:18.601523",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"list_prompt": [
|
||||||
|
"Réponds en 8 mots max avec humour : ",
|
||||||
"Réagis comme un viewer twitch en 6 mots : ",
|
"Réagis comme un viewer twitch en 6 mots : ",
|
||||||
"Commentaire de live en 7 mots drôle : ",
|
"Commentaire de live en 7 mots drôle : ",
|
||||||
"Réponse sarcastique en 5 mots : ",
|
"Réponse sarcastique en 5 mots : ",
|
||||||
@@ -12,37 +25,12 @@
|
|||||||
"Réponse ironique en 6 mots : ",
|
"Réponse ironique en 6 mots : ",
|
||||||
"Réponse punchy en 7 mots : ",
|
"Réponse punchy en 7 mots : ",
|
||||||
"Réaction wtf en 5 mots : ",
|
"Réaction wtf en 5 mots : ",
|
||||||
"Commentaire troll en 7 mots : ",
|
"Commentaire troll en 7 mots : "
|
||||||
"Réponse cringe en 6 mots : ",
|
],
|
||||||
"Blague meta en 8 mots : ",
|
"bad_answer": [
|
||||||
"Commentaire rage en 6 mots : ",
|
"suis un assistant",
|
||||||
"Je vais te donner plusieur a respecter pour répondre a mon texte, 1er règles tu doit répondre en moins de 15mots et tu répondre seulement avec la réponse pas d'affiramtion pour dire que tu comprend, 2eme regle tu doit répondre a ce texte de mainière a réagir avec une blague ou une explication : Voici le texte : ",
|
"Je ne comprends pas.",
|
||||||
"réagis avec au text donnée a la fin avec seulement ta réponse au texte en moins de 15mots et soit créatif : ",
|
"pas un humain",
|
||||||
"Répond en 10 mots avec un charatère gentils comme si tu fesait une remarque en regardant la tv : ",
|
"je suis désolé"
|
||||||
"Réponds en 1 phrase à ce texte avec un peu de sarcasme, comme si tu étais un personnage un peu exaspéré mais sympathique.",
|
|
||||||
"Réponds de manière décontractée et courte, avec une pointe d'humour.",
|
|
||||||
"Réponds de manière concise avec un ton un peu moqueur mais gentil.",
|
|
||||||
"Réponds de façon simple et courte, mais avec une touche d’humour pince-sans-rire.",
|
|
||||||
"Réponds en 1 phrase de manière enthousiaste mais décalée, comme si tu étais un peu trop excité.",
|
|
||||||
"Réponds en 1 phrase avec un peu d'ironie, comme si tu ne prenais pas la situation trop au sérieux.",
|
|
||||||
"Réponds en 1 phrase de façon très simple et directe, mais avec un petit côté humoristique.",
|
|
||||||
"Réponds en 1 phrase comme si tu étais un coach un peu excentrique, motivant mais drôle.",
|
|
||||||
"Réponds en 1 phrase avec une touche de légèreté et de bienveillance, mais en ajoutant un brin d’humour.",
|
|
||||||
"Réponds en 1 phrase de manière décalée, mais avec un fond de vérité sous le ton léger.",
|
|
||||||
|
|
||||||
"Réponds avec humour en 1 phrase, sans trop te prendre au sérieux.",
|
|
||||||
"Réponds avec un peu de sarcasme, mais reste concis.",
|
|
||||||
"Sois direct, avec une touche d'humour, mais ne dépasse pas 15 mots.",
|
|
||||||
"Réponds en mode décalé, mais garde la réponse courte et punchy.",
|
|
||||||
"Réponds avec un ton léger et drôle, en 1 phrase seulement.",
|
|
||||||
"Réponds de manière concise, avec un brin d’ironie.",
|
|
||||||
"Réponds avec une touche d’humour rapide et percutante.",
|
|
||||||
"Réponds de manière brève mais amusante, sans fioritures.",
|
|
||||||
"Réponds avec un peu de moquerie gentille, mais court.",
|
|
||||||
"Sois concis et drôle, avec une touche de légèreté."
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"name": "ocelothfoufure test",
|
||||||
|
"twitch_channel": "ocelothfoufure",
|
||||||
|
"language": "fr",
|
||||||
|
"record_time": 60,
|
||||||
|
"max_lifetime": 3600,
|
||||||
|
"auto_generate": true,
|
||||||
|
"flux_only": false,
|
||||||
|
"custom_prompts": [],
|
||||||
|
"character": "🤖",
|
||||||
|
"active": true,
|
||||||
|
"created_at": "2025-07-20T00:57:26.877680"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+6
-13
@@ -1,19 +1,12 @@
|
|||||||
[
|
[
|
||||||
|
|
||||||
{
|
{
|
||||||
"tw_acc_pseudo": "SnowLunaSoft",
|
"tw_acc_pseudo": "demo_bot_1",
|
||||||
"tw_acc_token": "oauth:l348b8e7g7srjnc8trnxjqe2i2boq2",
|
"tw_acc_token": "oauth:demo_token_1",
|
||||||
"charactere": "Kappa"
|
"charactere": "😊"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tw_acc_pseudo": "exoticnaturees",
|
"tw_acc_pseudo": "demo_bot_2",
|
||||||
"tw_acc_token": "oauth:68mshyeuzbm8duwg1aaqha686iwrnf",
|
"tw_acc_token": "oauth:demo_token_2",
|
||||||
"charactere": "Kappa"
|
"charactere": "🤖"
|
||||||
},
|
|
||||||
{
|
|
||||||
"tw_acc_pseudo": "ForFunIlluminaty",
|
|
||||||
"tw_acc_token": "oauth:f0wrqe8njoayk5za8g0r40200hi817",
|
|
||||||
"charactere": "Kappa"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Executable
+224
@@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script de démonstration pour tester l'interface web du TwitchBot Controller
|
||||||
|
Génère des données de test pour montrer les fonctionnalités
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
import random
|
||||||
|
|
||||||
|
def create_demo_data():
|
||||||
|
"""Créer des données de démonstration"""
|
||||||
|
|
||||||
|
# Créer les dossiers nécessaires
|
||||||
|
os.makedirs('working_bot/storage', exist_ok=True)
|
||||||
|
os.makedirs('config', exist_ok=True)
|
||||||
|
|
||||||
|
# Données de démonstration pour les sous-titres
|
||||||
|
demo_subtitles = {
|
||||||
|
"14h30m15s": "Salut les viewers ! Comment ça va aujourd'hui ?",
|
||||||
|
"14h31m22s": "On va jouer à ce nouveau jeu, j'ai hâte de voir ce que ça donne",
|
||||||
|
"14h32m45s": "Oh non, je suis mort déjà ! C'est plus dur que je pensais",
|
||||||
|
"14h33m12s": "Merci pour le follow @nouveau_viewer !",
|
||||||
|
"14h34m30s": "Cette partie est vraiment intense, regardez ça !",
|
||||||
|
"14h35m18s": "Je pense qu'on devrait essayer une autre stratégie",
|
||||||
|
"14h36m05s": "Excellent, on progresse enfin dans ce niveau",
|
||||||
|
"14h37m22s": "N'hésitez pas à poser vos questions dans le chat"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Données de démonstration pour les générations IA
|
||||||
|
demo_generations = {
|
||||||
|
"14h30m20s": "Salut ! Ça va super bien merci ! 😊",
|
||||||
|
"14h31m28s": "Ce jeu a l'air génial, bonne chance !",
|
||||||
|
"14h32m50s": "Ça arrive aux meilleurs, tu vas y arriver !",
|
||||||
|
"14h33m15s": "Bienvenue dans la communauté ! 🎉",
|
||||||
|
"14h34m35s": "Waouh cette action était épique ! 🔥",
|
||||||
|
"14h35m25s": "Bonne idée, change de tactique !",
|
||||||
|
"14h36m10s": "Bravo ! Tu maîtrises de mieux en mieux",
|
||||||
|
"14h37m28s": "Toujours là pour aider ! 💪"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sauvegarder les données de démonstration
|
||||||
|
with open('working_bot/storage/subtitle_data.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(demo_subtitles, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
with open('working_bot/storage/IA_generator.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(demo_generations, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print("✅ Données de démonstration créées")
|
||||||
|
|
||||||
|
def create_demo_config():
|
||||||
|
"""Créer une configuration de démonstration"""
|
||||||
|
|
||||||
|
demo_config = {
|
||||||
|
"twitchname": "demo_streamer",
|
||||||
|
"recordtime": 60,
|
||||||
|
"threads": 1,
|
||||||
|
"language": "fr",
|
||||||
|
"max_flux_lifetime": 3600,
|
||||||
|
"active_flux": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "demo_streamer",
|
||||||
|
"twitchname": "demo_streamer",
|
||||||
|
"quantity": 1,
|
||||||
|
"lifetime": 3600,
|
||||||
|
"created_at": datetime.now().isoformat(),
|
||||||
|
"active": True
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"list_prompt": [
|
||||||
|
"Réponds en 8 mots max avec humour : ",
|
||||||
|
"Réagis comme un viewer twitch en 6 mots : ",
|
||||||
|
"Commentaire de live en 7 mots drôle : ",
|
||||||
|
"Réponse sarcastique en 5 mots : ",
|
||||||
|
"Blague rapide en 6 mots : ",
|
||||||
|
"Commentaire décalé en 7 mots : ",
|
||||||
|
"Réponse ironique en 6 mots : ",
|
||||||
|
"Réponse punchy en 7 mots : ",
|
||||||
|
"Réaction wtf en 5 mots : ",
|
||||||
|
"Commentaire troll en 7 mots : "
|
||||||
|
],
|
||||||
|
"bad_answer": [
|
||||||
|
"suis un assistant",
|
||||||
|
"Je ne comprends pas.",
|
||||||
|
"pas un humain",
|
||||||
|
"je suis désolé"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
demo_user = [
|
||||||
|
{
|
||||||
|
"tw_acc_pseudo": "demo_bot_1",
|
||||||
|
"tw_acc_token": "oauth:demo_token_1",
|
||||||
|
"charactere": "😊"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tw_acc_pseudo": "demo_bot_2",
|
||||||
|
"tw_acc_token": "oauth:demo_token_2",
|
||||||
|
"charactere": "🤖"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Sauvegarder les configurations
|
||||||
|
with open('config/config.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(demo_config, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
with open('config/user.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(demo_user, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print("✅ Configuration de démonstration créée")
|
||||||
|
|
||||||
|
def simulate_live_updates():
|
||||||
|
"""Simuler des mises à jour en temps réel"""
|
||||||
|
|
||||||
|
def update_data():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Simuler un nouveau sous-titre
|
||||||
|
current_time = datetime.now().strftime("%Hh%Mm%Ss")
|
||||||
|
|
||||||
|
demo_messages = [
|
||||||
|
"Cette partie devient vraiment intéressante !",
|
||||||
|
"Merci pour tous vos messages dans le chat !",
|
||||||
|
"Je vais essayer cette nouvelle technique",
|
||||||
|
"Waouh, je ne m'attendais pas à ça !",
|
||||||
|
"Vous avez vu cette action incroyable ?",
|
||||||
|
"On approche du boss final !",
|
||||||
|
"N'oubliez pas de follow si vous aimez le contenu",
|
||||||
|
"Cette musique de fond est parfaite"
|
||||||
|
]
|
||||||
|
|
||||||
|
demo_responses = [
|
||||||
|
"C'est parti pour l'action ! 🎮",
|
||||||
|
"Toujours un plaisir de vous lire ! 💬",
|
||||||
|
"Bonne stratégie, ça va marcher ! 👍",
|
||||||
|
"Plot twist inattendu ! 😮",
|
||||||
|
"Action de malade ! 🔥",
|
||||||
|
"Le boss va trembler ! 💪",
|
||||||
|
"Contenu de qualité garanti ! ⭐",
|
||||||
|
"Parfait pour l'ambiance ! 🎵"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Charger les données existantes
|
||||||
|
try:
|
||||||
|
with open('working_bot/storage/subtitle_data.json', 'r', encoding='utf-8') as f:
|
||||||
|
subtitles = json.load(f)
|
||||||
|
with open('working_bot/storage/IA_generator.json', 'r', encoding='utf-8') as f:
|
||||||
|
generations = json.load(f)
|
||||||
|
except:
|
||||||
|
subtitles = {}
|
||||||
|
generations = {}
|
||||||
|
|
||||||
|
# Ajouter de nouvelles données
|
||||||
|
new_message = random.choice(demo_messages)
|
||||||
|
new_response = random.choice(demo_responses)
|
||||||
|
|
||||||
|
subtitles[current_time] = new_message
|
||||||
|
generations[current_time] = new_response
|
||||||
|
|
||||||
|
# Limiter à 20 entrées maximum
|
||||||
|
if len(subtitles) > 20:
|
||||||
|
oldest_key = min(subtitles.keys())
|
||||||
|
del subtitles[oldest_key]
|
||||||
|
del generations[oldest_key]
|
||||||
|
|
||||||
|
# Sauvegarder
|
||||||
|
with open('working_bot/storage/subtitle_data.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(subtitles, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
with open('working_bot/storage/IA_generator.json', 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(generations, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"🔄 Mise à jour simulée: {current_time}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur lors de la simulation: {e}")
|
||||||
|
|
||||||
|
# Attendre entre 30 secondes et 2 minutes
|
||||||
|
time.sleep(random.randint(30, 120))
|
||||||
|
|
||||||
|
# Démarrer la simulation dans un thread
|
||||||
|
update_thread = threading.Thread(target=update_data, daemon=True)
|
||||||
|
update_thread.start()
|
||||||
|
|
||||||
|
print("🎭 Simulation des mises à jour en temps réel démarrée")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("🎬 TwitchBot Controller - Mode Démonstration")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Créer les données et configuration de démonstration
|
||||||
|
create_demo_data()
|
||||||
|
create_demo_config()
|
||||||
|
|
||||||
|
# Démarrer la simulation des mises à jour
|
||||||
|
simulate_live_updates()
|
||||||
|
|
||||||
|
print("\n🚀 Démarrage de l'interface web en mode démonstration...")
|
||||||
|
print("📍 Les données de test sont générées automatiquement")
|
||||||
|
print("🔄 De nouvelles données apparaîtront toutes les 30s-2min")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Importer et démarrer l'interface web
|
||||||
|
try:
|
||||||
|
from web_interface import app, socketio
|
||||||
|
|
||||||
|
socketio.run(
|
||||||
|
app,
|
||||||
|
host='0.0.0.0',
|
||||||
|
port=5000,
|
||||||
|
debug=False
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n🛑 Arrêt de la démonstration...")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur: {e}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -529,9 +529,6 @@ class TwitchChatBot:
|
|||||||
self.chat_thread.daemon = True
|
self.chat_thread.daemon = True
|
||||||
self.chat_thread.start()
|
self.chat_thread.start()
|
||||||
|
|
||||||
self.chat_thread = threading.Thread(target=self.main_loop)
|
|
||||||
self.chat_thread.start() # Démarre dans un thread séparé
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################## ChatMessage FIN ########################
|
################## ChatMessage FIN ########################
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
torch>=2
|
|
||||||
torchaudio>=2
|
|
||||||
faster-whisper==1.0.0
|
|
||||||
transformers
|
|
||||||
pandas
|
|
||||||
setuptools>=65
|
|
||||||
nltk
|
|
||||||
customtkinter
|
|
||||||
requests
|
|
||||||
streamlink
|
|
||||||
fake_useragent
|
|
||||||
rich
|
|
||||||
pydub
|
|
||||||
ffmpeg
|
|
||||||
whisperx
|
|
||||||
pytmi
|
|
||||||
keyboard
|
|
||||||
pynput
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Flask==2.3.2
|
||||||
|
Flask-SocketIO==5.3.4
|
||||||
|
python-socketio==5.8.0
|
||||||
|
python-engineio==4.7.1
|
||||||
|
eventlet==0.33.3
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🤖 TwitchBot Controller - Démarrage Interface Web"
|
||||||
|
echo "================================================"
|
||||||
|
|
||||||
|
# Vérifier que Python est installé
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo "❌ Python3 n'est pas installé"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Vérifier les dépendances et les installer si nécessaire
|
||||||
|
if ! python3 -c "import flask, flask_socketio" 2>/dev/null; then
|
||||||
|
echo "📦 Installation des dépendances Flask..."
|
||||||
|
pip3 install -r requirements_web.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Démarrer l'interface web
|
||||||
|
echo "🚀 Démarrage de l'interface web..."
|
||||||
|
python3 start_web_interface.py "$@"
|
||||||
Executable
+126
@@ -0,0 +1,126 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script de démarrage pour l'interface web du TwitchBot Controller
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def check_dependencies():
|
||||||
|
"""Vérifier que les dépendances sont installées"""
|
||||||
|
try:
|
||||||
|
import flask
|
||||||
|
import flask_socketio
|
||||||
|
print("✓ Dépendances Flask installées")
|
||||||
|
return True
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"✗ Dépendances manquantes: {e}")
|
||||||
|
print("Installez les dépendances avec: pip install -r requirements_web.txt")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_directories():
|
||||||
|
"""Créer les dossiers nécessaires s'ils n'existent pas"""
|
||||||
|
directories = [
|
||||||
|
'working_bot/storage',
|
||||||
|
'config',
|
||||||
|
'templates',
|
||||||
|
'static/css',
|
||||||
|
'static/js'
|
||||||
|
]
|
||||||
|
|
||||||
|
for directory in directories:
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
os.makedirs(directory)
|
||||||
|
print(f"✓ Dossier créé: {directory}")
|
||||||
|
|
||||||
|
def check_config_files():
|
||||||
|
"""Vérifier que les fichiers de configuration existent"""
|
||||||
|
config_files = {
|
||||||
|
'config/config.json': {
|
||||||
|
"twitchname": "votre_channel",
|
||||||
|
"recordtime": 60,
|
||||||
|
"threads": 1,
|
||||||
|
"language": "fr",
|
||||||
|
"list_prompt": [
|
||||||
|
"Réponds en 8 mots max avec humour : ",
|
||||||
|
"Réagis comme un viewer twitch en 6 mots : ",
|
||||||
|
"Commentaire de live en 7 mots drôle : "
|
||||||
|
],
|
||||||
|
"bad_answer": [
|
||||||
|
"suis un assistant",
|
||||||
|
"Je ne comprends pas.",
|
||||||
|
"pas un humain"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'config/user.json': [
|
||||||
|
{
|
||||||
|
"tw_acc_pseudo": "votre_pseudo",
|
||||||
|
"tw_acc_token": "oauth:votre_token",
|
||||||
|
"charactere": "😊"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
import json
|
||||||
|
for file_path, default_content in config_files.items():
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(default_content, f, indent=4, ensure_ascii=False)
|
||||||
|
print(f"✓ Fichier de configuration créé: {file_path}")
|
||||||
|
print(f"⚠️ Pensez à modifier {file_path} avec vos propres paramètres")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Démarrer l\'interface web du TwitchBot Controller')
|
||||||
|
parser.add_argument('--host', default='0.0.0.0', help='Adresse IP d\'écoute (défaut: 0.0.0.0)')
|
||||||
|
parser.add_argument('--port', type=int, default=5000, help='Port d\'écoute (défaut: 5000)')
|
||||||
|
parser.add_argument('--debug', action='store_true', help='Mode debug')
|
||||||
|
parser.add_argument('--no-check', action='store_true', help='Ignorer la vérification des dépendances')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print("🤖 TwitchBot Controller - Interface Web")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Vérifications préliminaires
|
||||||
|
if not args.no_check:
|
||||||
|
if not check_dependencies():
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
create_directories()
|
||||||
|
check_config_files()
|
||||||
|
|
||||||
|
# Changer vers le répertoire de travail si nécessaire
|
||||||
|
if not os.path.exists('working_bot'):
|
||||||
|
os.makedirs('working_bot')
|
||||||
|
|
||||||
|
print("\n🚀 Démarrage de l'interface web...")
|
||||||
|
print(f"📍 Adresse: http://{args.host}:{args.port}")
|
||||||
|
print("🔧 Utilisez Ctrl+C pour arrêter le serveur")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Importer et démarrer l'application Flask
|
||||||
|
try:
|
||||||
|
from web_interface import app, socketio
|
||||||
|
|
||||||
|
# Configurer l'application
|
||||||
|
app.config['DEBUG'] = args.debug
|
||||||
|
|
||||||
|
# Démarrer le serveur
|
||||||
|
socketio.run(
|
||||||
|
app,
|
||||||
|
host=args.host,
|
||||||
|
port=args.port,
|
||||||
|
debug=args.debug,
|
||||||
|
use_reloader=False # Éviter les problèmes avec les threads
|
||||||
|
)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n🛑 Arrêt de l'interface web...")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur lors du démarrage: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
/* Variables CSS pour le thème dark */
|
||||||
|
:root {
|
||||||
|
--primary-bg: #1a1a1a;
|
||||||
|
--secondary-bg: #2d2d2d;
|
||||||
|
--tertiary-bg: #404040;
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #cccccc;
|
||||||
|
--accent-color: #007bff;
|
||||||
|
--success-color: #28a745;
|
||||||
|
--warning-color: #ffc107;
|
||||||
|
--danger-color: #dc3545;
|
||||||
|
--info-color: #17a2b8;
|
||||||
|
--border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body et éléments de base */
|
||||||
|
body {
|
||||||
|
background-color: var(--primary-bg) !important;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
.navbar-dark {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-secondary {
|
||||||
|
background-color: var(--secondary-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background-color: var(--tertiary-bg) !important;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Items */
|
||||||
|
.status-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.5em 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #007bff, #6610f2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background: linear-gradient(135deg, #17a2b8, #6f42c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background: linear-gradient(135deg, #ffc107, #fd7e14);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: linear-gradient(135deg, #dc3545, #e83e8c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
.nav-tabs {
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover {
|
||||||
|
background-color: var(--tertiary-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
.form-control {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alerts */
|
||||||
|
.alert {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: linear-gradient(135deg, rgba(23, 162, 184, 0.2), rgba(23, 162, 184, 0.1));
|
||||||
|
color: #17a2b8;
|
||||||
|
border-left: 4px solid #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 193, 7, 0.2), rgba(255, 193, 7, 0.1));
|
||||||
|
color: #ffc107;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: linear-gradient(135deg, rgba(40, 167, 69, 0.2), rgba(40, 167, 69, 0.1));
|
||||||
|
color: #28a745;
|
||||||
|
border-left: 4px solid #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flux Item */
|
||||||
|
.flux-item {
|
||||||
|
background-color: var(--tertiary-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-status {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-status.active {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-status.inactive {
|
||||||
|
background-color: var(--danger-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message History */
|
||||||
|
.message-history {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item {
|
||||||
|
background-color: var(--tertiary-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-left: 3px solid var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subtitle History */
|
||||||
|
.subtitle-history {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle-item {
|
||||||
|
background-color: var(--tertiary-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-left: 3px solid var(--info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prompt Item */
|
||||||
|
.prompt-item {
|
||||||
|
background-color: var(--tertiary-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-item .btn-danger {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar personnalisée */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--secondary-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container-fluid {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading spinner */
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid rgba(255,255,255,.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: #fff;
|
||||||
|
animation: spin 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast notifications */
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-header {
|
||||||
|
background-color: var(--tertiary-bg);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats cards */
|
||||||
|
.stats-card {
|
||||||
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flux controls */
|
||||||
|
.flux-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-details {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading states */
|
||||||
|
.loading {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin: -10px 0 0 -10px;
|
||||||
|
border: 2px solid var(--accent-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: transparent;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
@@ -0,0 +1,662 @@
|
|||||||
|
// Configuration et variables globales
|
||||||
|
const API_BASE = '';
|
||||||
|
let socket;
|
||||||
|
let currentPrompts = [];
|
||||||
|
let currentStatus = {};
|
||||||
|
|
||||||
|
// Initialisation de l'application
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initializeSocketIO();
|
||||||
|
loadInitialData();
|
||||||
|
setupEventListeners();
|
||||||
|
startPeriodicUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialisation de Socket.IO pour les mises à jour en temps réel
|
||||||
|
function initializeSocketIO() {
|
||||||
|
socket = io();
|
||||||
|
|
||||||
|
socket.on('connect', function() {
|
||||||
|
console.log('Connecté au serveur');
|
||||||
|
updateConnectionStatus(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
console.log('Déconnecté du serveur');
|
||||||
|
updateConnectionStatus(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('status_update', function(data) {
|
||||||
|
updateDashboard(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('new_subtitle', function(data) {
|
||||||
|
addNewSubtitle(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('new_generation', function(data) {
|
||||||
|
updateNextMessage(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour du statut de connexion
|
||||||
|
function updateConnectionStatus(connected) {
|
||||||
|
const statusElement = document.getElementById('connection-status');
|
||||||
|
const iconElement = statusElement.previousElementSibling;
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
statusElement.textContent = 'Connecté';
|
||||||
|
iconElement.className = 'fas fa-circle text-success pulse';
|
||||||
|
} else {
|
||||||
|
statusElement.textContent = 'Déconnecté';
|
||||||
|
iconElement.className = 'fas fa-circle text-danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chargement des données initiales
|
||||||
|
async function loadInitialData() {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
loadFluxList(),
|
||||||
|
loadPrompts(),
|
||||||
|
loadStatus(),
|
||||||
|
loadSubtitles(),
|
||||||
|
loadGenerations()
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des données:', error);
|
||||||
|
showToast('Erreur lors du chargement des données', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration des écouteurs d'événements
|
||||||
|
function setupEventListeners() {
|
||||||
|
// Modal d'ajout de flux
|
||||||
|
const addFluxModal = document.getElementById('addFluxModal');
|
||||||
|
addFluxModal.addEventListener('hidden.bs.modal', function() {
|
||||||
|
document.getElementById('channel-name').value = '';
|
||||||
|
document.getElementById('record-audio').checked = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mises à jour périodiques
|
||||||
|
function startPeriodicUpdates() {
|
||||||
|
// Mise à jour des données toutes les 10 secondes
|
||||||
|
setInterval(async () => {
|
||||||
|
await loadStatus();
|
||||||
|
await loadSubtitles();
|
||||||
|
await loadGenerations();
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DES FLUX ===
|
||||||
|
|
||||||
|
// Chargement de la liste des flux
|
||||||
|
async function loadFluxList() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/flux`);
|
||||||
|
const flux = await response.json();
|
||||||
|
renderFluxList(flux);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des flux:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendu de la liste des flux
|
||||||
|
function renderFluxList(fluxList) {
|
||||||
|
const container = document.getElementById('flux-list');
|
||||||
|
|
||||||
|
if (fluxList.length === 0) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="text-center text-muted py-4">
|
||||||
|
<i class="fas fa-stream fa-3x mb-3"></i>
|
||||||
|
<p>Aucun flux configuré</p>
|
||||||
|
<p class="small">Cliquez sur "Ajouter Flux" pour commencer</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = fluxList.map(flux => `
|
||||||
|
<div class="flux-item fade-in" data-flux-id="${flux.id}">
|
||||||
|
<div class="flux-controls">
|
||||||
|
<div class="flux-info">
|
||||||
|
<div class="flux-name">
|
||||||
|
<span class="flux-status ${flux.active ? 'active' : 'inactive'}"></span>
|
||||||
|
${flux.name}
|
||||||
|
<span class="badge bg-${flux.status === 'active' ? 'success' : flux.status === 'error' ? 'danger' : 'warning'} ms-2">${flux.status}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flux-details">
|
||||||
|
${flux.record_audio ?
|
||||||
|
'<i class="fas fa-microphone text-success me-1"></i>Audio' :
|
||||||
|
'<i class="fas fa-microphone-slash text-muted me-1"></i>Pas d\'audio'
|
||||||
|
}
|
||||||
|
<i class="fas fa-comments text-info ms-2 me-1"></i>Chat
|
||||||
|
<span class="text-muted ms-2">${new Date(flux.created_at).toLocaleString()}</span>
|
||||||
|
${flux.error ? `<span class="text-danger ms-2">Erreur: ${flux.error}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flux-actions">
|
||||||
|
<button class="btn btn-sm btn-outline-warning" onclick="toggleFlux(${flux.id})" ${flux.status === 'error' ? 'disabled' : ''}>
|
||||||
|
<i class="fas fa-${flux.active ? 'pause' : 'play'}"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" onclick="removeFlux(${flux.id})">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour activer/désactiver un flux
|
||||||
|
async function toggleFlux(fluxId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/flux/${fluxId}/toggle`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast(`Flux ${result.active ? 'activé' : 'désactivé'}`, 'success');
|
||||||
|
await loadFluxList();
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors du changement de statut', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors du changement de statut du flux', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajout d'un nouveau flux
|
||||||
|
async function addFlux() {
|
||||||
|
const channelName = document.getElementById('channel-name').value.trim();
|
||||||
|
const recordAudio = document.getElementById('record-audio').checked;
|
||||||
|
|
||||||
|
if (!channelName) {
|
||||||
|
showToast('Veuillez entrer un nom de canal', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/flux`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
channel_name: channelName,
|
||||||
|
record_audio: recordAudio
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast(`Flux ${channelName} ajouté avec succès`, 'success');
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById('addFluxModal')).hide();
|
||||||
|
await loadFluxList();
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors de l\'ajout du flux', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de l\'ajout du flux', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppression d'un flux
|
||||||
|
async function removeFlux(fluxId) {
|
||||||
|
if (!confirm('Êtes-vous sûr de vouloir supprimer ce flux ?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/flux/${fluxId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Flux supprimé avec succès', 'success');
|
||||||
|
await loadFluxList();
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors de la suppression', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de la suppression du flux', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DES PROMPTS ===
|
||||||
|
|
||||||
|
// Chargement des prompts
|
||||||
|
async function loadPrompts() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/config/prompts`);
|
||||||
|
currentPrompts = await response.json();
|
||||||
|
renderPrompts();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des prompts:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendu des prompts
|
||||||
|
function renderPrompts() {
|
||||||
|
const container = document.getElementById('prompts-list');
|
||||||
|
|
||||||
|
container.innerHTML = currentPrompts.map((prompt, index) => `
|
||||||
|
<div class="prompt-item fade-in">
|
||||||
|
<button class="btn btn-danger btn-sm" onclick="removePrompt(${index})">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small text-muted">Prompt ${index + 1}</label>
|
||||||
|
</div>
|
||||||
|
<textarea class="form-control" rows="2" data-prompt-index="${index}">${prompt}</textarea>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajout d'un prompt
|
||||||
|
function addPrompt() {
|
||||||
|
currentPrompts.push('Nouveau prompt : ');
|
||||||
|
renderPrompts();
|
||||||
|
|
||||||
|
// Focus sur le nouveau prompt
|
||||||
|
setTimeout(() => {
|
||||||
|
const newPromptTextarea = document.querySelector(`textarea[data-prompt-index="${currentPrompts.length - 1}"]`);
|
||||||
|
if (newPromptTextarea) {
|
||||||
|
newPromptTextarea.focus();
|
||||||
|
newPromptTextarea.select();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppression d'un prompt
|
||||||
|
function removePrompt(index) {
|
||||||
|
if (confirm('Êtes-vous sûr de vouloir supprimer ce prompt ?')) {
|
||||||
|
currentPrompts.splice(index, 1);
|
||||||
|
renderPrompts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarde des prompts
|
||||||
|
async function savePrompts() {
|
||||||
|
// Récupérer les valeurs des textareas
|
||||||
|
const textareas = document.querySelectorAll('textarea[data-prompt-index]');
|
||||||
|
const updatedPrompts = Array.from(textareas).map(textarea => textarea.value.trim());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/config/prompts`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
prompts: updatedPrompts
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
currentPrompts = updatedPrompts;
|
||||||
|
showToast('Prompts sauvegardés avec succès', 'success');
|
||||||
|
} else {
|
||||||
|
showToast('Erreur lors de la sauvegarde', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de la sauvegarde des prompts', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DES MESSAGES ===
|
||||||
|
|
||||||
|
// Envoi d'un message personnalisé
|
||||||
|
async function sendCustomMessage() {
|
||||||
|
const message = document.getElementById('custom-message').value.trim();
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
showToast('Veuillez entrer un message', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/send-message`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
channel: 'default'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Message envoyé avec succès', 'success');
|
||||||
|
document.getElementById('custom-message').value = '';
|
||||||
|
addToRecentMessages(message);
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors de l\'envoi', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de l\'envoi du message', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envoi du prochain message
|
||||||
|
async function sendNextMessage() {
|
||||||
|
const nextMessageElement = document.getElementById('next-message');
|
||||||
|
const message = nextMessageElement.textContent.trim();
|
||||||
|
|
||||||
|
if (message === 'Aucun message en attente') {
|
||||||
|
showToast('Aucun message à envoyer', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/send-message`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: message,
|
||||||
|
channel: 'default'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Message envoyé avec succès', 'success');
|
||||||
|
addToRecentMessages(message);
|
||||||
|
nextMessageElement.textContent = 'Aucun message en attente';
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors de l\'envoi', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de l\'envoi du message', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envoi de la dernière génération
|
||||||
|
async function sendLastGeneration() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/generations`);
|
||||||
|
const generations = await response.json();
|
||||||
|
|
||||||
|
if (Object.keys(generations).length === 0) {
|
||||||
|
showToast('Aucune génération disponible', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedKeys = Object.keys(generations).sort();
|
||||||
|
const lastGeneration = generations[sortedKeys[sortedKeys.length - 1]];
|
||||||
|
|
||||||
|
const sendResponse = await fetch(`${API_BASE}/api/send-message`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: lastGeneration,
|
||||||
|
channel: 'default'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await sendResponse.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Dernière génération envoyée', 'success');
|
||||||
|
addToRecentMessages(lastGeneration);
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors de l\'envoi', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de l\'envoi de la génération', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajout d'un message aux messages récents
|
||||||
|
function addToRecentMessages(message) {
|
||||||
|
const container = document.getElementById('recent-messages');
|
||||||
|
const messageElement = document.createElement('div');
|
||||||
|
messageElement.className = 'message-item fade-in';
|
||||||
|
messageElement.innerHTML = `
|
||||||
|
<div class="message-content">${message}</div>
|
||||||
|
<div class="message-time">${new Date().toLocaleTimeString()}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.insertBefore(messageElement, container.firstChild);
|
||||||
|
|
||||||
|
// Limiter à 10 messages récents
|
||||||
|
const messages = container.children;
|
||||||
|
if (messages.length > 10) {
|
||||||
|
container.removeChild(messages[messages.length - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DES SOUS-TITRES ===
|
||||||
|
|
||||||
|
// Chargement des sous-titres
|
||||||
|
async function loadSubtitles() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/subtitles`);
|
||||||
|
const subtitles = await response.json();
|
||||||
|
renderSubtitles(subtitles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des sous-titres:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendu des sous-titres
|
||||||
|
function renderSubtitles(subtitles) {
|
||||||
|
const historyContainer = document.getElementById('subtitles-history');
|
||||||
|
const lastSubtitleElement = document.getElementById('last-subtitle');
|
||||||
|
|
||||||
|
if (Object.keys(subtitles).length === 0) {
|
||||||
|
lastSubtitleElement.textContent = 'Aucun texte détecté pour le moment';
|
||||||
|
historyContainer.innerHTML = '<p class="text-muted text-center">Aucun sous-titre disponible</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedKeys = Object.keys(subtitles).sort();
|
||||||
|
const lastKey = sortedKeys[sortedKeys.length - 1];
|
||||||
|
|
||||||
|
// Mettre à jour le dernier sous-titre
|
||||||
|
lastSubtitleElement.textContent = subtitles[lastKey];
|
||||||
|
|
||||||
|
// Mettre à jour l'historique
|
||||||
|
historyContainer.innerHTML = sortedKeys.reverse().slice(0, 10).map(key => `
|
||||||
|
<div class="subtitle-item fade-in">
|
||||||
|
<div class="subtitle-content">${subtitles[key]}</div>
|
||||||
|
<div class="subtitle-time text-muted small">${key}</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Génération d'une réponse à partir du sous-titre
|
||||||
|
async function generateFromSubtitle() {
|
||||||
|
const lastSubtitleElement = document.getElementById('last-subtitle');
|
||||||
|
const text = lastSubtitleElement.textContent.trim();
|
||||||
|
|
||||||
|
if (text === 'Aucun texte détecté pour le moment') {
|
||||||
|
showToast('Aucun sous-titre disponible pour la génération', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/generate-response`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
text: text
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Génération de réponse lancée', 'success');
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Erreur lors de la génération', 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur:', error);
|
||||||
|
showToast('Erreur lors de la génération de réponse', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DES GÉNÉRATIONS ===
|
||||||
|
|
||||||
|
// Chargement des générations
|
||||||
|
async function loadGenerations() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/generations`);
|
||||||
|
const generations = await response.json();
|
||||||
|
|
||||||
|
if (Object.keys(generations).length > 0) {
|
||||||
|
const sortedKeys = Object.keys(generations).sort();
|
||||||
|
const lastGeneration = generations[sortedKeys[sortedKeys.length - 1]];
|
||||||
|
updateNextMessage(lastGeneration);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des générations:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour du prochain message
|
||||||
|
function updateNextMessage(message) {
|
||||||
|
const nextMessageElement = document.getElementById('next-message');
|
||||||
|
nextMessageElement.textContent = message || 'Aucun message en attente';
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GESTION DU STATUT ===
|
||||||
|
|
||||||
|
// Chargement du statut
|
||||||
|
async function loadStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/api/status`);
|
||||||
|
currentStatus = await response.json();
|
||||||
|
updateDashboard(currentStatus);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement du statut:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mise à jour du tableau de bord
|
||||||
|
function updateDashboard(status) {
|
||||||
|
document.getElementById('flux-count').textContent = status.flux_count || 0;
|
||||||
|
document.getElementById('recording-count').textContent = status.active_recordings || 0;
|
||||||
|
document.getElementById('chat-count').textContent = status.chat_connections || 0;
|
||||||
|
|
||||||
|
if (status.last_subtitle) {
|
||||||
|
document.getElementById('last-subtitle').textContent = status.last_subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.next_message) {
|
||||||
|
updateNextMessage(status.next_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ACTIONS RAPIDES ===
|
||||||
|
|
||||||
|
// Génération d'une réponse
|
||||||
|
async function generateResponse() {
|
||||||
|
const lastSubtitleElement = document.getElementById('last-subtitle');
|
||||||
|
const text = lastSubtitleElement.textContent.trim();
|
||||||
|
|
||||||
|
if (text === 'Aucun texte détecté pour le moment') {
|
||||||
|
showToast('Aucun sous-titre disponible pour la génération', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await generateFromSubtitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualisation des données
|
||||||
|
async function refreshData() {
|
||||||
|
showToast('Actualisation en cours...', 'info');
|
||||||
|
await loadInitialData();
|
||||||
|
showToast('Données actualisées', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// === UTILITAIRES ===
|
||||||
|
|
||||||
|
// Affichage des notifications toast
|
||||||
|
function showToast(message, type = 'info') {
|
||||||
|
// Créer le conteneur de toast s'il n'existe pas
|
||||||
|
let container = document.querySelector('.toast-container');
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
container.className = 'toast-container';
|
||||||
|
document.body.appendChild(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastId = 'toast-' + Date.now();
|
||||||
|
const toastColors = {
|
||||||
|
success: 'text-bg-success',
|
||||||
|
error: 'text-bg-danger',
|
||||||
|
warning: 'text-bg-warning',
|
||||||
|
info: 'text-bg-info'
|
||||||
|
};
|
||||||
|
|
||||||
|
const toastElement = document.createElement('div');
|
||||||
|
toastElement.id = toastId;
|
||||||
|
toastElement.className = `toast ${toastColors[type] || 'text-bg-info'}`;
|
||||||
|
toastElement.setAttribute('role', 'alert');
|
||||||
|
toastElement.innerHTML = `
|
||||||
|
<div class="toast-header">
|
||||||
|
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : type === 'warning' ? 'exclamation-triangle' : 'info-circle'} me-2"></i>
|
||||||
|
<strong class="me-auto">TwitchBot</strong>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
${message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(toastElement);
|
||||||
|
|
||||||
|
const toast = new bootstrap.Toast(toastElement, {
|
||||||
|
autohide: true,
|
||||||
|
delay: type === 'error' ? 5000 : 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.show();
|
||||||
|
|
||||||
|
// Supprimer l'élément après fermeture
|
||||||
|
toastElement.addEventListener('hidden.bs.toast', () => {
|
||||||
|
container.removeChild(toastElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion des erreurs globales
|
||||||
|
window.addEventListener('error', function(e) {
|
||||||
|
console.error('Erreur JavaScript:', e.error);
|
||||||
|
showToast('Une erreur inattendue s\'est produite', 'error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestion des erreurs de promesses non capturées
|
||||||
|
window.addEventListener('unhandledrejection', function(e) {
|
||||||
|
console.error('Promesse rejetée:', e.reason);
|
||||||
|
showToast('Erreur de communication avec le serveur', 'error');
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"00:41:14": "Oui, cest possible. Le coût est de 50€.",
|
||||||
|
"00:41:55": "Ah, cest vrai ! Qui contrôle ici ? Moi, bien sûr !Daccord, daccord. Pas la peine de tagiter.Je vais entrer dans une pièce... si jen ai envie.Tu ne ten sors pas. Pas question.On verra bien comment tu te débrouilles.Cette version conserve lessence du texte original tout en le rendant plus clair, plus drôle et plus fluide. Elle utilise des expressions familières et un ton léger pour créer un effet comique.",
|
||||||
|
"00:42:15": "```S.T. 501 : La série qui vous fait rire sans trop deffort```Ce sous-titre capture lessence de votre demande en étant court, amusant et légèrement ironique. Il met en avant le concept de série tout en faisant référence à son titre original de manière ludique.Si vous souhaitez que je modifie ou ajoute quelque chose, nhésitez pas à me le faire savoir !",
|
||||||
|
"00:42:36": "```🎭 Le spectacle se poursuit !🤡 Notre équipe de comédiens en herbe sentraîne dur pour vous faire rire.💃 Les danseuses sont prêtes à déclencher la folie.🎭 Et notre metteur en scène ? Il est... toujours perdu dans ses notes ! 😂#PocanAttendsAuCoeur #ComédieEnHerbe #RiresAssurés```Ce texte utilise des emojis pour ajouter une touche visuelle et amusante. Il mentionne brièvement léquipe, le travail dentraînement, et fait une petite blague sur le metteur en scène. Lhumour est léger et adapté à un public familial.",
|
||||||
|
"00:43:17": "So be concise and funny, with a touch of lightness. Oh yeah! Hes too much, isnt he? Hes so strong! What should I do? Have a healthy frame? No Put them togetherCette version conserve lessence du texte original tout en ajoutant un peu dhumour et de légèreté. Elle rend également le sens plus clair pour les lecteurs anglophones.",
|
||||||
|
"00:43:58": "Alors, mes amis, préparez vos foulards et vos chaussures de course ! On va faire du sport, mais pas nimporte quel sport... Non, non, on va courir, sauter, grimper et peut-être même voler (avec un peu dimagination) !Cest le grand jour où nous allons découvrir les joies des courses haies combinées. Allez, qui est prêt à se transformer en athlète olympique ?Attention, cependant : pas dapplaudissements sil vous plaît. Nous sommes encore dans lignorance totale sur ce que nous allons faire exactement. Cest comme si on vous disait : Demain, on va faire quelque chose de génial, mais je ne sais pas quoi !Bref, restez calmes, buvez votre café, et surtout, gardez vos muscles flexibles. Qui sait, peut-être que demain, on va courir autour du monde ?N",
|
||||||
|
"00:44:39": "Alors voilà, les gagnants de la semaine ! Le premier a remporté 30 wesh (ou weshes ?), cest pas clair pour tout le monde. Et savez-vous ce qui se passe quand on regarde votre live ? Oui, il y a un avertissement. Comme si on avait besoin dêtre prévenu quon allait voir quelque chose de marrant ! Enfin, au moins, ça nous prépare à limpact culturel que vous allez avoir sur notre planète.Ce texte est plus court, drôle et léger que le passage original, tout en conservant lidée principale. Il utilise des expressions familières comme wesh et ajoute une touche dhumour en faisant référence à lavertissement sur le live.",
|
||||||
|
"00:45:00": "Alors là, cest vraiment la fin du monde ! Zen sest fait un petit saut dans linconnu, et la scène est restée aussi calme quun chat en apnée. Mais pas de panique, on va reboucler comme des kangourous sur une piste de danse. Après tout, quand Zen saute, il ne sarrête jamais jusquà ce quil atteigne les étoiles... ou au moins le plafond.Cette réponse joue sur lhumour et la légèreté demandées, en utilisant des comparaisons absurdes et des expressions amusantes pour rendre le texte plus engageant et mémorable.",
|
||||||
|
"00:45:41": "```pythonimport randomdef jouer_musique(): morse_code = [., .., ...] notes = [Do, Ré, Mi, Fa, Sol, La, Si] print(Lorchestre sest mis en place...) print(Le maître de musique crie :) print(Allez-y !) for _ in range(5): note = random.choice(notes) temps = random.choice(morse_code) print(f{temps} {note})jouer_musique()```Ce code crée une fonction `jouer_musique()` qui :1. Importe les modules nécessaires2. Définit des variables pour le morse code et les notes musicales3. Simule lorchestre sassemblant4. Joue cinq notes aléatoires en utilisant le morse codeLorsque vous exécutez ce code, il affichera une série de notes musicales jouées au rythme du morse code. Cest un exemple simple mais amusant de musique programmée !",
|
||||||
|
"00:46:22": "```pythonimport randomdef generate_catchy_tune(): notes = [Do, Ré, Mi, Fa, Sol, La, Si] rhythm = [1, 2, 3, 4, 5] tune = [] for _ in range(8): note = random.choice(notes) duration = random.choice(rhythm) tune.append(f{note} {duration}) return , .join(tune)print(generate_catchy_tune())```Ce code Python crée une mélodie aléatoire en utilisant des noms de notes musicales et des durées simples. Vous pouvez lexécuter pour obtenir différentes versions humoristiques de thèmes de générique !",
|
||||||
|
"00:46:43": "Hey there! Im here to bring some humor and lightness to our conversation. Lets dive deep (pun intended) into the world of absurdity.1. Ive got a doggone good joke: Why did the dog go to the vet? Because he was feeling ruff!2. Corail-cul and corail-lis? Sounds like a recipe for disaster... or porridge. Either way, its a real pain in the butt!3. The stone tablet where an artist tripped is inscribed with Salle Archives. Talk about a rocky start!4. And finally, if you ever need help finding the archives room, just follow the trail of broken dreams and shattered expectations. Its right next door to the I Give Up lounge!Hope this made you howl with laughter!",
|
||||||
|
"00:57:59": "Humour absolu ! Réponse concise et drôle.",
|
||||||
|
"00:59:00": "Ah, cest parti les archives ! Tu veux savoir si ton survêt fait son effet ? Je dirais plutôt que ça fait son petit plus... ou son petit moins ? En tout cas, cest ton meilleur vêtement et ça compte ! Allez, va ten, mais reviens vite avec des nouvelles de la chambre des archives, sinon on va avoir lœil qui nous sort !Cette réponse joue sur les mots pêche et bleu, ajoute une touche dhumour en utilisant des expressions familières, et maintient un ton léger tout au long du message. Elle répond à chaque point soulevé dans le texte initial de manière ludique et concise.",
|
||||||
|
"00:59:21": "```pythonimport randomdef intro_music(): jokes = [ Bonjour, bienvenue dans notre spectacle !, Préparez-vous à être amusés... ou pas., On va essayer de vous faire rire, mais pas trop fort. ] print(random.choice(jokes))intro_music()```Cette fonction Python choisit aléatoirement une blague dintroduction et limprime. Vous pouvez ajouter plus de blagues à la liste pour varier les choses.",
|
||||||
|
"01:07:57": "• Enfin, un prétexte pour manger des sardines !• Préparez vos masques de plongée... et votre dentiste !• On y va, mais pas trop vite, on ne veut pas déraper !• Cest parti pour une aventure sous-marine... ou simplement sous la table !Ces réponses jouent sur lidée dexploration abyssale tout en ajoutant une touche comique, souvent liée à des références culturelles (comme le film Le monde profond) ou à des situations quotidiennes (compter les sardines). Lhumour est ici basé sur lironie et le jeu de mots pour créer un effet amusant.",
|
||||||
|
"01:09:58": "```1:00Personnage 1 : Bonjour, comment ça va ?Personnage 2 :Ça va bien, merci. Et vous ?(Soupir)Je suis en train décrire des sous-titres pour un film sur les sous-titres.Personnage 1 :Ah, cest marrant !Vous voulez que je vous aide à trouver des expressions plus originales ?Personnage 2 :Oui, sil vous plaît. Je veux être le meilleur sous-titreur du monde !Personnage 1 :Daccord, mais attention, pas trop de légèreté !On ne veut pas quon nous prenne pour des amateurs !Personnage 2 :Promis, promis ! Je vais être aussi sérieux quun sous-titreur qui perd son emploi !```Ce sous-titre capture lessence du film tout en ajoutant une touche dhumour et de légèreté. Il montre les personnages discutant des sous-titres tout en faisant référence au fait quils sont en train de créer des sous-titres pour un film sur les sous-titres. Cela crée un effet de récit en abyme amusant et pertinent.",
|
||||||
|
"01:10:20": "```pythonimport randomdef musique_intro(): # Liste des éléments comiques à ajouter dans lintroduction elements_comiques = [ Un gamin qui joue du xylophone, Une souris en pyjama qui fait la danse du chat, Un robot qui parle en langage morse ] # Sélection aléatoire dun élément comique element_sélectionné = random.choice(elements_comiques) print(f\\n**{element_sélectionné}**) print( ~~~~) print( / \\\\ ) print((oo)_____(oo)) print( || |) print( || |) print( || |) print( ||----|)musique_intro()```Ce code crée une fonction `musique_intro()` qui sélectionne aléatoirement un élément comique parmi une liste donnée. Il affiche ensuite une animation simple représentant cet élément.Pour exécuter ce code, vous pouvez copier-coller le bloc de code Python dans votre environnement de développement (comme VSCode) et lexécuter. Cela créera une sortie visuelle amusante avec une introduction musicale légère.",
|
||||||
|
"01:11:00": "Ah, super ! On va faire un piquenique sur le glacier... avec des glaçons à portée de main ! Cest comme si on allait au parc, mais en plus froid et en moins de vêtements. Prêt pour une expérience qui va vous faire trembler ? (Pas trop fort, on ne veut pas que vous vous évanouissiez.)Cette réponse maintient lhumour tout en abordant brièvement le sujet du voyage dans une zone glaciaire. Elle utilise des expressions familières et des jeux de mots pour créer une atmosphère légère et amusante.",
|
||||||
|
"01:11:42": "Ah, mon ami, tu as les boules ! Pas de panique, cest juste un petit R qui ta pris la main. Allez, relâche-toi et fais-le en douceur. Si ça continue, on va avoir besoin dun autre R pour te sortir de ce pétrin !Cette réponse joue sur lidée du R comme un personnage qui tient la main, tout en gardant un ton léger et amical. Elle encourage le destinataire à rester calme et à sen sortir lui-même, tout en faisant une petite blague sur le besoin potentiel dune intervention supplémentaire.",
|
||||||
|
"01:16:12": "La musique est le langage des dieux.",
|
||||||
|
"01:17:52": "```Jai fait 38 minutes de plongée Zepbi et jen ai descendu un mètre plus bas. Jai vu des barracudas au lait long, des poissons ogres, des pteractyes, des requins à grande gueule... Et en haut, ma mère qui me regarde, un peu inquiète. Je lui ai dit : Maman, jai mangé un crabe. Elle était contente.```Cette version conserve lhumour et la légèreté tout en étant plus claire et concise.",
|
||||||
|
"01:18:33": "Vite, les gars ! Notre drone sous-marin a faim et il ne sarrêtera pas tant quil plongera dans leau. On va le pierrer avant quil ne nous dévore tous !Cette version :1. Simplifie le langage2. Retient lidée principale (le drone sous-marin)3. Conserve un ton léger et drôle4. Élimine les répétitions et les phrases inutilesNhésitez pas si vous voulez que je modifie autre chose !",
|
||||||
|
"01:19:15": "Tu es toujours sur la pointe des pieds quand tu... Ouais cest vrai, je focalise, jai un peu le vertige sous les talons chauds !Jai pas mes... mes yeux !Cette phrase semble être une improvisation comique, probablement dans un contexte théâtral ou scénique. Elle utilise des expressions familières et des jeux de mots pour créer un effet humoristique.Quelques observations :1. Lutilisation répétée de ouais donne un ton familier et spontané.2. Le jeu sur les mots comme pointe des pieds et vertige sous les talons chauds est typique du style comique.3. La phrase incomplète à la fin (Jai pas mes... mes yeux) laisse entendre que lacteur sest trompé dans son texte, ajoutant au comique.Ce type dhumour est souvent utilisé en comédie ou dans les situations où limprovisation est recherchée. Il nécessite un bon timing et une bonne maîtrise de la langue pour être efficace.",
|
||||||
|
"01:19:35": "Ah le thon ! Cest là bas un mur les gars Oui oui Ah la putain de tes mortCette phrase semble déjà être une réponse humoristique à une question sur lemplacement dun thon. Elle est courte, amusante et utilise un langage familier pour créer un effet comique. Si vous cherchez à ajouter quelque chose de plus, nous pourrions peut-être explorer dautres idées humoristiques liées au thon ou à la situation décrite.",
|
||||||
|
"01:19:56": "Ah, ces gars-là ! Ils sont comme des mouches : ils reviennent toujours. Mais pas de panique, voilà comment vous en débarrasser :1. Ignorez-les (comme si vous ne les aviez jamais vus).2. Si cest trop tard, faites semblant dêtre occupé (travaillez sur votre tan).3. Utilisez lastuce du je suis désolé(e), mais seulement deux fois maximum.4. En dernier recours, utilisez la méthode tourne-toi et pars.Et si rien ne fonctionne ? Eh bien, il y a toujours la solution ultime : la porte de sortie. Cest le meilleur moyen de dire adieu à vos soucis... et à vos responsabilités.N",
|
||||||
|
"01:20:17": "```pythondef snipe_tank(): return On voit le Snipe là ? Non ça marche pas si ?def tank_de_fou(): return Mais ils sont tank de fou ! Grave je vaisdef je_suis_bon(): return Je suis bon, vas-y toucheprint(f{snipe_tank()} {tank_de_fou()} {je_suis_bon()})```Ce code Python crée trois fonctions humoristiques qui représentent les différentes phrases du texte original. Lorsque vous exécutez ce code, il affichera la combinaison des trois phrases dans un seul bloc.Note : Ce nest quune interprétation ludique et ne doit pas être pris au sérieux. Le but est de capturer lessence humoristique et légère du texte original en utilisant un langage de programmation.",
|
||||||
|
"01:20:58": "```On a gagné deux nouveaux abonnés !Y1:0Cest pas mal, mais on peut faire mieux.```Cette version est plus courte, plus claire et moins confuse. Elle conserve lessence humoristique tout en étant plus facile à lire.",
|
||||||
|
"01:21:19": "Alors, soyez courts et amusants, avec une touche de légèreté. Cest parti ! Ils sont rapides dun ton, quoi ? Rouge. Cétait chaud, ça vers actuel.Cette version est plus courte et plus drôle, tout en conservant lessentiel du message original. Elle utilise des expressions familières comme Cest parti et Quoi ? pour donner un ton plus léger.",
|
||||||
|
"01:22:00": "Be concise and funny, with a touch of lightness. Theres no tuna investment here. But its in mode. What is this? Ha! Its amusing. Oh my... An Alvins cheat sheet, high-quality 3-star!Cette traduction conserve le sens original tout en ajoutant des éléments humoristiques pour rendre le texte plus léger et amusant. Elle utilise également des expressions idiomatiques comme high-quality 3-star! qui sont courantes dans les langues anglophones pour désigner quelque chose de très bon ou de haut niveau.Le terme Alvins cheat sheet fait référence à la série danimation Les Schtroumpfs (The Smurfs) où Alvin est un personnage récurrent. Lutilisation de ce terme ajoute une touche de jeu et de reconnaissance culturelle au texte.",
|
||||||
|
"01:22:21": "Alors on sest débarrassés dun peu de poids, maintenant il faut descendre et se régaler ! Tas amélioré tes équipements ? Pas de souci, ça coûte cher mais cest pour ta santé... ou ton estomac !Et puis jai acheté des hippocampes pour les tes-tes ? Ya même !Cette version conserve lessentiel du message tout en le rendant plus amusant et léger. Elle utilise des expressions familières et des jeux de mots pour créer un ton humoristique.",
|
||||||
|
"01:23:02": "### Analyse comiqueLe texte semble être une conversation informelle entre deux personnes discutant dun chargeur pour appareil photo Samsung. Voici comment on pourrait le reformuler de manière plus amusante :Alors, tu veux savoir pourquoi je prénds toujours mon chargeur Samsung ? Eh bien, cest simple : il est génial ! (Ou pas...)En tout cas, cest le seul qui fonctionne chez moi. Comme ma mère disait : Un bon chargeur, cest comme un bon mari : il faut quil tienne le coup !Et puis, vous savez ce qui est drôle ? Cest que ce chargeur, il nest ni trop grand ni trop petit. Il est juste... parfaitement moyen. Comme moi !Bref, si vous cherchez un chargeur qui ne fera jamais de vagues, qui sera toujours là pour vous, et qui ne vous posera jamais de problèmes... Alors, votre homme (ou votre chargeur) est trouvé !Cette interprétation ajoute une touche de légèreté et dhumour au texte original, tout en gardant lesprit général de la conversation.",
|
||||||
|
"01:23:23": "Tadji, lexpert en créations culinaires sous-marines ! Voici quelques suggestions pour votre menu délicieux :1. Crevettes grillées au poivre (mais sans oublier la sauce à la moutarde)2. Hippocampes rôtis avec une touche dhumour (cest-à-dire quils sont crus)3. Oursons panés et frits (pour un goût de mer et de surprise)4. Requin tartare (avec une pincée de sel et beaucoup de courage)Nhésitez pas à ajouter des étoiles de mer comme garniture pour un touché de classe !Bonne cuisine et bon appétit, Tadji !",
|
||||||
|
"01:24:04": "```1:00Personnage : Jérémy DiazAction :Sapprocher du microDialogue :Jérémy : (soupir) Ah, les sous-titres... Mon rêve depuis toujours.2:00Personnage : Jérémy DiazAction :Commencer à écrireDialogue :Jérémy : (en lisant son texte) Cest génial ! Je vais faire des sous-titres qui vont faire pleurer de rire !3:00Personnage : Jérémy DiazAction :Lisant son texteDialogue :Jérémy : (en lisant) Sois concis et drôle... Cest facile, je suis un pro !```Ce sous-titre parle du personnage Jérémy Diaz qui est chargé de créer des sous-titres pour le film. Il sapproche du micro et commence à écrire les sous-titres en se moquant légèrement du travail quil doit faire. Le dialogue montre son enthousiasme initial qui pourrait rapidement changer en fonction des défis rencontrés lors de la création des sous-titres.",
|
||||||
|
"01:24:25": "Ah, un Samsung ! Cest comme un ami qui vous fait un concert de serpents en pyjama. Chaud, daccord, mais attention à ne pas trop se laisser griser par le son !Cette réponse joue sur lhumour en utilisant des images visuelles (le concert de serpents) tout en abordant la question du volume musical. Elle reste légère et amusante tout en évitant de dévoiler des informations techniques spécifiques sur les appareils Samsung.",
|
||||||
|
"01:25:06": "Ah, vous voulez que je sois comme un bon vin blanc : rafraîchissant, amusant et qui fait oublier les soucis ! Sinon, cest vrai, on risque dêtre aussi intéressants quun vieux fromage... Alors, laissez-moi vous faire rire et vous montrer que jai encore du jus dans les veines !Cette réponse combine lhumour, la légèreté et une touche de jeu de mots pour répondre au ton demandé. Elle évoque lidée de rester frais et intéressant, tout en évitant les références trop sérieuses ou complexes.",
|
||||||
|
"01:25:27": "Ah, le jugeur de musique ! Cest comme avoir un critique gastronomique pour un plat de macaroni. Mais sérieusement, si vous voulez améliorer votre écoute musicale, essayez découter sans regarder la vidéo. Votre cerveau (et vos oreilles) vous remercieront. Et noubliez pas : la meilleure façon de protéger vos yeux est de ne pas les rouvrir après avoir vu mon dernier concert !Cette réponse joue sur lidée que le jugeur de musique peut être superflu, tout en ajoutant une touche humoristique concernant la protection de la santé oculaire. Elle reste courte et légère, tout en abordant le sujet de manière amusante.",
|
||||||
|
"01:25:48": "Be concise, witty, and light-hearted. Tell them. Dont tell them. No prevention. You should have done a #HashtagRaison. Green. Line their eyes. Prev. Subtitle Société Radio-CanadaCette version conserve la structure originale tout en utilisant des mots-clés américains comme witty et light-hearted. Elle maintient également les éléments spécifiques comme le hashtag, la couleur verte, et le sous-titre en français.",
|
||||||
|
"01:26:08": "Be brief, funny, and light-hearted. So there we have it, all the big weapons equipment and nothing to put in the game. No but were going to make money out of it anyway. Its just like usual. Oh yeah... Do you want a new game? Complete all the missions, oh!Cette traduction conserve le ton humoristique et légère du texte original tout en restant concise. Elle met laccent sur les points clés comme la frustration concernant léquipement, la décision de faire des profits malgré tout, et lidée dun nouveau jeu avec des missions à accomplir.",
|
||||||
|
"01:26:49": "```pythondef crevette_tigre(): return Une crevette ? La tigre ! Mais tu vois, je ne bâtissais pas des crevettes. Attends, prends les grandes poches.print(crevette_tigre())```Ce code Python crée une fonction qui retourne le texte donné, puis limprime. Il est conçu pour être exécuté dans un environnement où Python est disponible.",
|
||||||
|
"01:27:10": "So be funny and light-hearted. What do I do? Boxing gloves or golf clubs? Go for 32 damage for charged attack. Oh yeah! Why didnt you take the gloves? Because when its charged, its excellent! They recover fast. Actually, go ahead, it makes you want to play, you want to try it out. No way!Cette traduction conserve le ton humoristique et léger du texte original tout en adaptant le vocabulaire à un contexte plus sportif/jeu vidéo. Elle est également concise comme demandé."
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"00:40:56": "Tas vu je peux payer pour utiliser le taxi Beluga mes noms Jessaye ? Vas-y",
|
||||||
|
"00:41:35": "Oh cest vrai ! Contrôlé ou celui-là ? Cest moi qui le contrôle. Ouais ouais. Non tu peux même sortir. Non. On va voir je vais rentrer dans une pièce avec. si rentre avec pas.",
|
||||||
|
"00:42:13": "Sous-titrage ST 501",
|
||||||
|
"00:42:31": "On va faire des comptes du Pocan Attends au coeur",
|
||||||
|
"00:43:10": "Ah ben oui ! cest trop fiant non lui il est fort Quest-ce que je dois faire ? Tas un châssis sain Non Place-les",
|
||||||
|
"00:43:49": "Ok quest ce quon va faire ? Applaudissez pas on ne sait rien il existe différents niveaux de types courses les cours haies combinées et avec des compétences importantes.",
|
||||||
|
"00:44:26": "Ten vas moi putain Oh le premier a gagné 30 Wesh quand on va sur ton live il y un avertissement Ouais",
|
||||||
|
"00:44:45": "Ok il faut que japplaudisse. Zen sest sauté. poussé. La scène restée calme. Alors reboucler. Cest le cas de sauter tout suite. Putain.",
|
||||||
|
"00:45:24": "Cest quoi les missions un peu là ? Les humains ne sont pas dignes de confiance mais jai reçu liste chez villageois. Liste complète ah voilà !",
|
||||||
|
"00:45:40": "Musique",
|
||||||
|
"00:46:18": "Musique de générique",
|
||||||
|
"00:46:40": "Jarrive à voir une sienne du profondeur enlevé pourrais-tu men rapporter 5 ? Ah ouais jai merde des chiens saluer mais... Ok... Tu auras besoin de corail-cul et corail-lis pour faire porridge ah ouais... La tablette en pierre sur laquelle a chancelé un oeuvre est préinscrite... Dans la salle... Ok. Cest dans salle archives.",
|
||||||
|
"00:58:41": "on part de la chambre des archives cest parti tu parles quoi ça pêche ou blé jai mis mon plus beau survêt vous trouvez tons aussi qui est important",
|
||||||
|
"00:59:18": "Musique dintro",
|
||||||
|
"00:59:56": "On va dans les profondeurs",
|
||||||
|
"01:09:37": "Sous-titres par Jérémy Diaz",
|
||||||
|
"01:10:14": "Musique dintro",
|
||||||
|
"01:10:52": "On va aller dans une zone glaciaire apparemment.",
|
||||||
|
"01:11:30": "Quest-ce que je dois faire là ? Ça ne va pas bien. Dans le temps il y a R à faire.",
|
||||||
|
"01:12:07": "Musique",
|
||||||
|
"01:17:39": "On a fait 38 minutes de plongée Zepbi et je suis descendu dun mètre plus profond voilà on quoi du barracuda à balles lait long nez la pêche bizarre sait pas trop pourquoi poisson ogre pteractys requin grande gueule cest celui quon le ouais jen ai mangé crabe un peu tinquiète il est content oh ça ken maman",
|
||||||
|
"01:18:20": "tout aussi rapide dès quils sont de temps pourront pas échapper à ce piège des énormes secret pour la chasse aujourdhui le drone sous-marin mange dans leau attend je peux faire dautres trucs on va tester est parti loin jeu là",
|
||||||
|
"01:18:59": "Tes toujours sur la pointe des pieds quand tu... Ouais cest vrai Je focus je suis un peu... sous talons chaud Jai pas mes... mes yeux",
|
||||||
|
"01:19:16": "Oh le thon ! Cest Et là bas ya un mur les gars Oui oui Ah la putain de tes mort",
|
||||||
|
"01:19:55": "Cest un peu chiant ça. Il va me shooter puis quand je ly reprends. Ouais ouais ça cest bon Voilà. Je peux les chasser comme ?",
|
||||||
|
"01:20:14": "On voit le Snipe là Non ça marche pas si ? Oui cest drôle les gars Vas-y on va aller tenter Mais ils sont tank de fou grave je vais Je suis bon vas-y touche",
|
||||||
|
"01:20:54": "On a eu tas gagné deux followers Ah ouais ? Tas déjà Yann et... Et Clara non cest un ou qui sappellent Oui moi Cest vrai voilà et Ouais quoi ça truc là on est daccord",
|
||||||
|
"01:21:12": "Cest parti ! Ils sont rapides un ton quoi ? rouge Cétait chaud ça vers actuel.",
|
||||||
|
"01:21:51": "Il ny a même pas dinvestissement de tunas Mais il est en mode Cest quoi ? Ah cest marrant Oh est... Un Alvinos cheaté un 3 étoiles haute qualité",
|
||||||
|
"01:22:13": "Bon là du coup on a chassé un petit peu maintenant va descendre et se régaler hein ! Tas amélioré tes équipements ? Coudal pas de thunes ça coûte cher Mais dis-moi pourquoi tas Parce que jai acheté enclos tu mets tout dans hippocampes ya les Tes genoudé par la pâte gain Ya même",
|
||||||
|
"01:22:52": "Cest un chargeur normal pas typisé cest rouleau Samsung Pourquoi spécialement les ? Parce que apparaissent en commun Oui utilisé mais moi comme ma mère jai des",
|
||||||
|
"01:23:13": "On peut faire quoi Tadji ? prendre des crevettes et tout Ah mais non on En fait cest hippocampes pas Pour ta culture même les oursins ouais là Oui si possible a joué le jeu Un ptit un ptit... requin",
|
||||||
|
"01:23:50": "Sous-titres par Jérémy Diaz",
|
||||||
|
"01:24:08": "Pour toi cest un Samsung. Je vous fais petit concert les vipers. Chaud. Ça charge fort ou pas trop ? fort.",
|
||||||
|
"01:24:48": "Cest très important sinon vos yeux dedans ça va être des raisins secs. Et personne naime les secs parce quon ne voit rien avec. Pas vrai ? Dites-leur. On le dit nous. avec cest bien connu. Vous avez déjà essayé de prendre dates voir à travers Ben on rien.",
|
||||||
|
"01:25:07": "Si on a un jugeur de plus avec la musique... Ouais malheureusement ça... Ça paraît bien. Pensez à votre santé oculaire les petits chichis ! Toujours tenir des yeux assis",
|
||||||
|
"01:25:46": "Dites-leur. Ne les dites pas. faites pas de prévention. Vous auriez dû faire un hashtag Hashtag raison. vert. Linez des yeux. prev. Sous-titrage Société Radio-Canada",
|
||||||
|
"01:26:06": "Donc là en fait le gros équipement armes et tout tas rien à mettre dans jeu. Non mais on va faire masse de sous là. Cest pareil que dhabitude. Ah ouais. Eh ben... Tu veux un nouveau jeu ? fais toutes les putains missions oh !",
|
||||||
|
"01:26:44": "Oh une crevette ! la tigre La Mais tu vois je ne bâtissais pas des crevettes Attends prends les grandes poches",
|
||||||
|
"01:27:06": "Je fais quoi ? Gant de boxe ou club golf Vas-y 32 dégâts pour lattaque chargée Ah ouais Pourquoi tu nas pas pris les gants Parce que ça fait quand cest chargé Cest excellent pensais cétait Après ils taiment vite En vrai vas-y donne envie jouer veux le coup Ouais non"
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TwitchBot Controller - Interface de Contrôle</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-dark text-light">
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">
|
||||||
|
<i class="fas fa-robot me-2"></i>TwitchBot Controller
|
||||||
|
</a>
|
||||||
|
<div class="navbar-nav ms-auto">
|
||||||
|
<span class="navbar-text">
|
||||||
|
<i class="fas fa-circle text-success pulse"></i>
|
||||||
|
<span id="connection-status">Connecté</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-tachometer-alt me-2"></i>Tableau de Bord</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="status-item">
|
||||||
|
<span class="badge bg-info">Flux Actifs</span>
|
||||||
|
<span class="float-end" id="flux-count">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<span class="badge bg-warning">Enregistrements</span>
|
||||||
|
<span class="float-end" id="recording-count">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<span class="badge bg-success">Connexions Chat</span>
|
||||||
|
<span class="float-end" id="chat-count">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="card bg-secondary mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-bolt me-2"></i>Actions Rapides</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<button class="btn btn-success btn-sm w-100 mb-2" onclick="generateResponse()">
|
||||||
|
<i class="fas fa-magic me-2"></i>Générer Réponse
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary btn-sm w-100 mb-2" onclick="sendLastGeneration()">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>Envoyer Dernière Génération
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info btn-sm w-100" onclick="refreshData()">
|
||||||
|
<i class="fas fa-sync me-2"></i>Actualiser
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="col-md-9">
|
||||||
|
<!-- Tabs Navigation -->
|
||||||
|
<ul class="nav nav-tabs" id="mainTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="flux-tab" data-bs-toggle="tab" data-bs-target="#flux" type="button">
|
||||||
|
<i class="fas fa-stream me-2"></i>Flux
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="messages-tab" data-bs-toggle="tab" data-bs-target="#messages" type="button">
|
||||||
|
<i class="fas fa-comments me-2"></i>Messages
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="prompts-tab" data-bs-toggle="tab" data-bs-target="#prompts" type="button">
|
||||||
|
<i class="fas fa-cogs me-2"></i>Prompts IA
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="subtitles-tab" data-bs-toggle="tab" data-bs-target="#subtitles" type="button">
|
||||||
|
<i class="fas fa-closed-captioning me-2"></i>Sous-titres
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Tab Content -->
|
||||||
|
<div class="tab-content mt-3" id="mainTabContent">
|
||||||
|
<!-- Flux Tab -->
|
||||||
|
<div class="tab-pane fade show active" id="flux" role="tabpanel">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5><i class="fas fa-stream me-2"></i>Gestion des Flux</h5>
|
||||||
|
<button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#addFluxModal">
|
||||||
|
<i class="fas fa-plus me-2"></i>Ajouter Flux
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="flux-list">
|
||||||
|
<!-- Flux list will be populated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Messages Tab -->
|
||||||
|
<div class="tab-pane fade" id="messages" role="tabpanel">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-eye me-2"></i>Prochain Message</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info" id="next-message">
|
||||||
|
Aucun message en attente
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" onclick="sendNextMessage()">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>Envoyer ce Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-secondary mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-keyboard me-2"></i>Envoyer Message Personnalisé</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<textarea class="form-control" id="custom-message" rows="3" placeholder="Tapez votre message ici..."></textarea>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success" onclick="sendCustomMessage()">
|
||||||
|
<i class="fas fa-send me-2"></i>Envoyer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-history me-2"></i>Messages Récents</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="recent-messages" class="message-history">
|
||||||
|
<!-- Recent messages will be populated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Prompts Tab -->
|
||||||
|
<div class="tab-pane fade" id="prompts" role="tabpanel">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5><i class="fas fa-cogs me-2"></i>Configuration des Prompts IA</h5>
|
||||||
|
<button class="btn btn-success btn-sm" onclick="addPrompt()">
|
||||||
|
<i class="fas fa-plus me-2"></i>Ajouter Prompt
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="prompts-list">
|
||||||
|
<!-- Prompts will be populated here -->
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary mt-3" onclick="savePrompts()">
|
||||||
|
<i class="fas fa-save me-2"></i>Sauvegarder les Prompts
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subtitles Tab -->
|
||||||
|
<div class="tab-pane fade" id="subtitles" role="tabpanel">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-microphone me-2"></i>Dernier Texte Détecté</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-warning" id="last-subtitle">
|
||||||
|
Aucun texte détecté pour le moment
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success" onclick="generateFromSubtitle()">
|
||||||
|
<i class="fas fa-robot me-2"></i>Générer Réponse IA
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-secondary">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5><i class="fas fa-list me-2"></i>Historique des Sous-titres</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="subtitles-history" class="subtitle-history">
|
||||||
|
<!-- Subtitles history will be populated here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Flux Modal -->
|
||||||
|
<div class="modal fade" id="addFluxModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content bg-dark">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Ajouter un Nouveau Flux</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="channel-name" class="form-label">Nom du Canal</label>
|
||||||
|
<input type="text" class="form-control" id="channel-name" placeholder="Ex: ocelothfoufure">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="record-audio" checked>
|
||||||
|
<label class="form-check-label" for="record-audio">
|
||||||
|
Enregistrer l'audio du stream
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="addFlux()">Ajouter</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
"""
|
||||||
|
Module d'intégration entre l'interface web et le bot Twitch existant
|
||||||
|
Permet de démarrer le bot existant avec contrôle web
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
# Import du bot existant
|
||||||
|
from main_auto_loop import *
|
||||||
|
from fonction.first_class import *
|
||||||
|
|
||||||
|
class WebBotIntegration:
|
||||||
|
"""Classe pour intégrer le bot existant avec l'interface web"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.bot_instances = {}
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def start_bot_with_channel(self, channel_name: str, record_audio: bool = True, record_time: int = 60) -> dict:
|
||||||
|
"""
|
||||||
|
Démarre une instance du bot pour un canal spécifique
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_name: Nom du canal Twitch
|
||||||
|
record_audio: Si True, enregistre l'audio du stream
|
||||||
|
record_time: Durée d'enregistrement par segment
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec les informations de l'instance créée
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Créer le dossier de travail s'il n'existe pas
|
||||||
|
work_directory = f"working_bot_{channel_name}"
|
||||||
|
if not os.path.exists(work_directory):
|
||||||
|
os.makedirs(work_directory)
|
||||||
|
|
||||||
|
# Sauvegarder le répertoire courant
|
||||||
|
original_cwd = os.getcwd()
|
||||||
|
|
||||||
|
# Changer vers le répertoire de travail
|
||||||
|
os.chdir(work_directory)
|
||||||
|
|
||||||
|
instance = {
|
||||||
|
'channel_name': channel_name,
|
||||||
|
'record_audio': record_audio,
|
||||||
|
'record_time': record_time,
|
||||||
|
'work_directory': work_directory,
|
||||||
|
'original_cwd': original_cwd,
|
||||||
|
'bots': {},
|
||||||
|
'running': True
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Nettoyer les anciens fichiers
|
||||||
|
if os.path.exists("./storage/IA_generator.json"):
|
||||||
|
os.remove("./storage/IA_generator.json")
|
||||||
|
if os.path.exists("./storage/subtitle_data.json"):
|
||||||
|
os.remove("./storage/subtitle_data.json")
|
||||||
|
|
||||||
|
# Créer le dossier storage s'il n'existe pas
|
||||||
|
if not os.path.exists("storage"):
|
||||||
|
os.makedirs("storage")
|
||||||
|
|
||||||
|
# Démarrer l'enregistrement audio si demandé
|
||||||
|
if record_audio:
|
||||||
|
hprint("blue", f"Démarrage RecordTwitch pour {channel_name}")
|
||||||
|
record_tw = RecordTwitch(channel_name=channel_name, record_time=record_time)
|
||||||
|
instance['bots']['record'] = record_tw
|
||||||
|
|
||||||
|
# Démarrer l'enregistrement dans un thread
|
||||||
|
record_thread = threading.Thread(target=record_tw.main, daemon=True)
|
||||||
|
record_thread.start()
|
||||||
|
|
||||||
|
# Démarrer la traduction des sous-titres
|
||||||
|
hprint("blue", f"Démarrage Subtitle_translation pour {channel_name}")
|
||||||
|
sb_translation = Subtitle_translation("../config/config.json")
|
||||||
|
instance['bots']['subtitle'] = sb_translation
|
||||||
|
sb_translation.start_main_loop()
|
||||||
|
|
||||||
|
# Démarrer le générateur IA
|
||||||
|
hprint("blue", f"Démarrage IA_generator pour {channel_name}")
|
||||||
|
ask_text = IA_generator("../config/config.json")
|
||||||
|
instance['bots']['ia'] = ask_text
|
||||||
|
ask_text.start_main_loop()
|
||||||
|
|
||||||
|
# Démarrer le contrôleur de messages
|
||||||
|
controluser = messageTwitch("../config/user.json", channel_name)
|
||||||
|
instance['bots']['message'] = controluser
|
||||||
|
controluser.start_loop_respond()
|
||||||
|
|
||||||
|
# Démarrer le bot de chat (optionnel)
|
||||||
|
chat_bot = TwitchChatBot(channel_name)
|
||||||
|
instance['bots']['chat'] = chat_bot
|
||||||
|
chat_bot.start_background()
|
||||||
|
|
||||||
|
hprint("green", f"Bot démarré avec succès pour {channel_name}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Retourner au répertoire original
|
||||||
|
os.chdir(original_cwd)
|
||||||
|
|
||||||
|
# Stocker l'instance
|
||||||
|
self.bot_instances[channel_name] = instance
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'channel': channel_name,
|
||||||
|
'instance_id': len(self.bot_instances),
|
||||||
|
'bots_started': list(instance['bots'].keys())
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hprint("red", f"Erreur lors du démarrage du bot pour {channel_name}: {str(e)}")
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'error': str(e),
|
||||||
|
'channel': channel_name
|
||||||
|
}
|
||||||
|
|
||||||
|
def stop_bot_for_channel(self, channel_name: str) -> dict:
|
||||||
|
"""
|
||||||
|
Arrête le bot pour un canal spécifique
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_name: Nom du canal
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec le résultat de l'opération
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if channel_name not in self.bot_instances:
|
||||||
|
return {'success': False, 'error': f'Aucun bot actif pour {channel_name}'}
|
||||||
|
|
||||||
|
instance = self.bot_instances[channel_name]
|
||||||
|
instance['running'] = False
|
||||||
|
|
||||||
|
# Arrêter tous les bots de cette instance
|
||||||
|
bots_stopped = []
|
||||||
|
for bot_name, bot_instance in instance['bots'].items():
|
||||||
|
try:
|
||||||
|
if hasattr(bot_instance, 'stop'):
|
||||||
|
bot_instance.stop()
|
||||||
|
bots_stopped.append(bot_name)
|
||||||
|
hprint("yellow", f"Bot {bot_name} arrêté pour {channel_name}")
|
||||||
|
except Exception as e:
|
||||||
|
hprint("red", f"Erreur lors de l'arrêt du bot {bot_name}: {str(e)}")
|
||||||
|
|
||||||
|
# Supprimer l'instance
|
||||||
|
del self.bot_instances[channel_name]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'channel': channel_name,
|
||||||
|
'bots_stopped': bots_stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
hprint("red", f"Erreur lors de l'arrêt du bot pour {channel_name}: {str(e)}")
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
|
||||||
|
def get_bot_status(self, channel_name: str) -> dict:
|
||||||
|
"""
|
||||||
|
Récupère le statut du bot pour un canal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_name: Nom du canal
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec le statut du bot
|
||||||
|
"""
|
||||||
|
if channel_name not in self.bot_instances:
|
||||||
|
return {'active': False, 'error': 'Bot non trouvé'}
|
||||||
|
|
||||||
|
instance = self.bot_instances[channel_name]
|
||||||
|
|
||||||
|
status = {
|
||||||
|
'active': instance['running'],
|
||||||
|
'channel': channel_name,
|
||||||
|
'record_audio': instance['record_audio'],
|
||||||
|
'bots': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vérifier le statut de chaque bot
|
||||||
|
for bot_name, bot_instance in instance['bots'].items():
|
||||||
|
try:
|
||||||
|
if bot_name == 'record' and hasattr(bot_instance, 'running'):
|
||||||
|
status['bots'][bot_name] = {'running': bot_instance.running}
|
||||||
|
elif bot_name == 'subtitle' and hasattr(bot_instance, 'is_running'):
|
||||||
|
status['bots'][bot_name] = {'running': bot_instance.is_running}
|
||||||
|
elif bot_name == 'ia' and hasattr(bot_instance, 'ia_running'):
|
||||||
|
status['bots'][bot_name] = {'running': bot_instance.ia_running}
|
||||||
|
elif bot_name == 'message' and hasattr(bot_instance, 'message_running'):
|
||||||
|
status['bots'][bot_name] = {'running': bot_instance.message_running}
|
||||||
|
elif bot_name == 'chat' and hasattr(bot_instance, 'is_running'):
|
||||||
|
status['bots'][bot_name] = {'running': bot_instance.is_running}
|
||||||
|
else:
|
||||||
|
status['bots'][bot_name] = {'running': True} # Par défaut
|
||||||
|
except Exception as e:
|
||||||
|
status['bots'][bot_name] = {'running': False, 'error': str(e)}
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
def list_active_bots(self) -> dict:
|
||||||
|
"""
|
||||||
|
Liste tous les bots actifs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec la liste des bots actifs
|
||||||
|
"""
|
||||||
|
active_bots = {}
|
||||||
|
|
||||||
|
for channel_name in self.bot_instances:
|
||||||
|
active_bots[channel_name] = self.get_bot_status(channel_name)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_instances': len(self.bot_instances),
|
||||||
|
'active_bots': active_bots
|
||||||
|
}
|
||||||
|
|
||||||
|
def send_message_to_channel(self, channel_name: str, message: str) -> dict:
|
||||||
|
"""
|
||||||
|
Envoie un message dans le chat d'un canal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_name: Nom du canal
|
||||||
|
message: Message à envoyer
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec le résultat de l'opération
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if channel_name not in self.bot_instances:
|
||||||
|
return {'success': False, 'error': f'Aucun bot actif pour {channel_name}'}
|
||||||
|
|
||||||
|
instance = self.bot_instances[channel_name]
|
||||||
|
|
||||||
|
if 'message' not in instance['bots']:
|
||||||
|
return {'success': False, 'error': 'Bot de message non disponible'}
|
||||||
|
|
||||||
|
message_bot = instance['bots']['message']
|
||||||
|
message_bot.send_message(message)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'channel': channel_name,
|
||||||
|
'message': message
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
|
||||||
|
def generate_response_for_channel(self, channel_name: str, text: str = None) -> dict:
|
||||||
|
"""
|
||||||
|
Génère une réponse IA pour un canal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel_name: Nom du canal
|
||||||
|
text: Texte à utiliser pour la génération (optionnel)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict avec le résultat de l'opération
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if channel_name not in self.bot_instances:
|
||||||
|
return {'success': False, 'error': f'Aucun bot actif pour {channel_name}'}
|
||||||
|
|
||||||
|
instance = self.bot_instances[channel_name]
|
||||||
|
|
||||||
|
if 'ia' not in instance['bots']:
|
||||||
|
return {'success': False, 'error': 'Bot IA non disponible'}
|
||||||
|
|
||||||
|
ia_bot = instance['bots']['ia']
|
||||||
|
|
||||||
|
# Utiliser le texte fourni ou récupérer le dernier sous-titre
|
||||||
|
if text is None:
|
||||||
|
if 'subtitle' in instance['bots']:
|
||||||
|
subtitle_bot = instance['bots']['subtitle']
|
||||||
|
text = subtitle_bot.get_lasttext()
|
||||||
|
else:
|
||||||
|
return {'success': False, 'error': 'Aucun texte disponible pour la génération'}
|
||||||
|
|
||||||
|
# Lancer la génération
|
||||||
|
ia_bot.main_ask(text)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'channel': channel_name,
|
||||||
|
'text_used': text
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
|
||||||
|
def stop_all_bots(self):
|
||||||
|
"""Arrête tous les bots actifs"""
|
||||||
|
channels_to_stop = list(self.bot_instances.keys())
|
||||||
|
|
||||||
|
for channel_name in channels_to_stop:
|
||||||
|
self.stop_bot_for_channel(channel_name)
|
||||||
|
|
||||||
|
hprint("blue", "Tous les bots ont été arrêtés")
|
||||||
|
|
||||||
|
# Instance globale pour l'intégration
|
||||||
|
web_bot_integration = WebBotIntegration()
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
from flask import Flask, render_template, request, jsonify, redirect, url_for
|
||||||
|
from flask_socketio import SocketIO, emit
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Import des classes du bot
|
||||||
|
sys.path.append('.')
|
||||||
|
from fonction.first_class import RecordTwitch, Subtitle_translation, IA_generator, messageTwitch, TwitchChatBot, storage
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = 'your-secret-key-here'
|
||||||
|
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||||
|
|
||||||
|
class BotController:
|
||||||
|
def __init__(self):
|
||||||
|
self.bots = {} # Stockage des instances de bots (pour l'utilisation interne)
|
||||||
|
self.flux_list = [] # Liste des flux surveillés (pour l'API JSON)
|
||||||
|
self.config = self.load_config()
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
try:
|
||||||
|
with open('config/config.json', 'r') as file:
|
||||||
|
return json.load(file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_config(self):
|
||||||
|
with open('config/config.json', 'w') as file:
|
||||||
|
json.dump(self.config, file, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
def add_flux(self, channel_name, record_audio=True):
|
||||||
|
flux_id = len(self.flux_list) + 1
|
||||||
|
|
||||||
|
# Créer l'objet flux pour l'API (sans les instances de bots)
|
||||||
|
flux_data = {
|
||||||
|
'id': flux_id,
|
||||||
|
'name': channel_name,
|
||||||
|
'twitchname': channel_name,
|
||||||
|
'record_audio': record_audio,
|
||||||
|
'active': True,
|
||||||
|
'created_at': datetime.now().isoformat(),
|
||||||
|
'status': 'starting'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Créer le bot de chat pour ce flux
|
||||||
|
chat_bot = TwitchChatBot(channel_name)
|
||||||
|
self.bots[flux_id] = {
|
||||||
|
'chat_bot': chat_bot,
|
||||||
|
'record_bot': None
|
||||||
|
}
|
||||||
|
chat_bot.start_background()
|
||||||
|
|
||||||
|
# Si enregistrement audio activé
|
||||||
|
if record_audio:
|
||||||
|
record_bot = RecordTwitch(channel_name, 60)
|
||||||
|
self.bots[flux_id]['record_bot'] = record_bot
|
||||||
|
threading.Thread(target=record_bot.main, daemon=True).start()
|
||||||
|
|
||||||
|
# Mettre à jour le statut
|
||||||
|
flux_data['status'] = 'active'
|
||||||
|
self.flux_list.append(flux_data)
|
||||||
|
return flux_id
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur lors de l'ajout du flux {channel_name}: {str(e)}")
|
||||||
|
# Nettoyer en cas d'erreur
|
||||||
|
if flux_id in self.bots:
|
||||||
|
try:
|
||||||
|
if self.bots[flux_id]['chat_bot']:
|
||||||
|
self.bots[flux_id]['chat_bot'].stop()
|
||||||
|
if self.bots[flux_id]['record_bot']:
|
||||||
|
self.bots[flux_id]['record_bot'].stop()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
del self.bots[flux_id]
|
||||||
|
flux_data['status'] = 'error'
|
||||||
|
flux_data['error'] = str(e)
|
||||||
|
self.flux_list.append(flux_data)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def remove_flux(self, flux_id):
|
||||||
|
for i, flux in enumerate(self.flux_list):
|
||||||
|
if flux['id'] == flux_id:
|
||||||
|
# Arrêter les bots si ils existent
|
||||||
|
if flux_id in self.bots:
|
||||||
|
try:
|
||||||
|
if self.bots[flux_id]['chat_bot']:
|
||||||
|
self.bots[flux_id]['chat_bot'].stop()
|
||||||
|
if self.bots[flux_id]['record_bot']:
|
||||||
|
self.bots[flux_id]['record_bot'].stop()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur lors de l'arrêt des bots: {e}")
|
||||||
|
del self.bots[flux_id]
|
||||||
|
|
||||||
|
del self.flux_list[i]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_flux_list(self):
|
||||||
|
# Retourner seulement les données JSON (pas les instances de bots)
|
||||||
|
return self.flux_list
|
||||||
|
|
||||||
|
bot_controller = BotController()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/api/flux', methods=['GET'])
|
||||||
|
def get_flux():
|
||||||
|
return jsonify(bot_controller.get_flux_list())
|
||||||
|
|
||||||
|
@app.route('/api/flux', methods=['POST'])
|
||||||
|
def add_flux():
|
||||||
|
data = request.json
|
||||||
|
channel_name = data.get('channel_name')
|
||||||
|
record_audio = data.get('record_audio', True)
|
||||||
|
|
||||||
|
if not channel_name:
|
||||||
|
return jsonify({'error': 'Nom du canal requis'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
flux_id = bot_controller.add_flux(channel_name, record_audio)
|
||||||
|
return jsonify({'success': True, 'flux_id': flux_id})
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Erreur lors de l'ajout du flux: {str(e)}"
|
||||||
|
print(f"Erreur API add_flux: {error_msg}")
|
||||||
|
return jsonify({'error': error_msg}), 500
|
||||||
|
|
||||||
|
@app.route('/api/flux/<int:flux_id>', methods=['DELETE'])
|
||||||
|
def remove_flux(flux_id):
|
||||||
|
if bot_controller.remove_flux(flux_id):
|
||||||
|
return jsonify({'success': True})
|
||||||
|
return jsonify({'error': 'Flux non trouvé'}), 404
|
||||||
|
|
||||||
|
@app.route('/api/flux/<int:flux_id>/status', methods=['GET'])
|
||||||
|
def get_flux_status(flux_id):
|
||||||
|
"""Obtenir le statut détaillé d'un flux spécifique"""
|
||||||
|
for flux in bot_controller.flux_list:
|
||||||
|
if flux['id'] == flux_id:
|
||||||
|
status = {
|
||||||
|
'id': flux_id,
|
||||||
|
'name': flux['name'],
|
||||||
|
'active': flux['active'],
|
||||||
|
'status': flux.get('status', 'unknown'),
|
||||||
|
'created_at': flux['created_at'],
|
||||||
|
'bots': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ajouter les informations des bots si disponibles
|
||||||
|
if flux_id in bot_controller.bots:
|
||||||
|
bots = bot_controller.bots[flux_id]
|
||||||
|
if bots['chat_bot']:
|
||||||
|
status['bots']['chat'] = {
|
||||||
|
'running': bots['chat_bot'].is_running if hasattr(bots['chat_bot'], 'is_running') else True
|
||||||
|
}
|
||||||
|
if bots['record_bot']:
|
||||||
|
status['bots']['record'] = {
|
||||||
|
'running': bots['record_bot'].running if hasattr(bots['record_bot'], 'running') else True
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify(status)
|
||||||
|
|
||||||
|
return jsonify({'error': 'Flux non trouvé'}), 404
|
||||||
|
|
||||||
|
@app.route('/api/flux/<int:flux_id>/toggle', methods=['POST'])
|
||||||
|
def toggle_flux(flux_id):
|
||||||
|
"""Activer/désactiver un flux"""
|
||||||
|
for flux in bot_controller.flux_list:
|
||||||
|
if flux['id'] == flux_id:
|
||||||
|
flux['active'] = not flux['active']
|
||||||
|
|
||||||
|
# Arrêter/démarrer les bots selon le nouveau statut
|
||||||
|
if flux_id in bot_controller.bots:
|
||||||
|
bots = bot_controller.bots[flux_id]
|
||||||
|
if not flux['active']:
|
||||||
|
# Arrêter les bots
|
||||||
|
try:
|
||||||
|
if bots['chat_bot']:
|
||||||
|
bots['chat_bot'].stop()
|
||||||
|
if bots['record_bot']:
|
||||||
|
bots['record_bot'].stop()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur lors de l'arrêt des bots: {e}")
|
||||||
|
else:
|
||||||
|
# Redémarrer les bots
|
||||||
|
try:
|
||||||
|
if bots['chat_bot']:
|
||||||
|
bots['chat_bot'].start_background()
|
||||||
|
if bots['record_bot']:
|
||||||
|
threading.Thread(target=bots['record_bot'].main, daemon=True).start()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur lors du redémarrage des bots: {e}")
|
||||||
|
|
||||||
|
return jsonify({'success': True, 'active': flux['active']})
|
||||||
|
|
||||||
|
return jsonify({'error': 'Flux non trouvé'}), 404
|
||||||
|
|
||||||
|
@app.route('/api/config/prompts', methods=['GET'])
|
||||||
|
def get_prompts():
|
||||||
|
return jsonify(bot_controller.config.get('list_prompt', []))
|
||||||
|
|
||||||
|
@app.route('/api/config/prompts', methods=['POST'])
|
||||||
|
def update_prompts():
|
||||||
|
data = request.json
|
||||||
|
prompts = data.get('prompts', [])
|
||||||
|
bot_controller.config['list_prompt'] = prompts
|
||||||
|
bot_controller.save_config()
|
||||||
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
@app.route('/api/subtitles', methods=['GET'])
|
||||||
|
def get_subtitles():
|
||||||
|
data = storage.read("subtitle_data")
|
||||||
|
return jsonify(data)
|
||||||
|
|
||||||
|
@app.route('/api/generations', methods=['GET'])
|
||||||
|
def get_generations():
|
||||||
|
data = storage.read("IA_generator")
|
||||||
|
return jsonify(data)
|
||||||
|
|
||||||
|
@app.route('/api/send-message', methods=['POST'])
|
||||||
|
def send_message():
|
||||||
|
data = request.json
|
||||||
|
message = data.get('message')
|
||||||
|
channel = data.get('channel', 'default')
|
||||||
|
|
||||||
|
if not message:
|
||||||
|
return jsonify({'error': 'Message requis'}), 400
|
||||||
|
|
||||||
|
# Trouver le bot de message pour ce canal
|
||||||
|
try:
|
||||||
|
msg_bot = messageTwitch("config/user.json", channel)
|
||||||
|
msg_bot.send_message(message)
|
||||||
|
return jsonify({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/generate-response', methods=['POST'])
|
||||||
|
def generate_response():
|
||||||
|
data = request.json
|
||||||
|
text = data.get('text', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
ia_gen = IA_generator("config/config.json")
|
||||||
|
ia_gen.main_ask(text)
|
||||||
|
return jsonify({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/api/status', methods=['GET'])
|
||||||
|
def get_status():
|
||||||
|
status = {
|
||||||
|
'flux_count': len(bot_controller.flux_list),
|
||||||
|
'active_recordings': sum(1 for f in bot_controller.flux_list if f['record_audio'] and f['active']),
|
||||||
|
'chat_connections': sum(1 for f in bot_controller.flux_list if f['active']),
|
||||||
|
'last_subtitle': '',
|
||||||
|
'next_message': '',
|
||||||
|
'recent_messages': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Récupérer le dernier sous-titre
|
||||||
|
subtitle_data = storage.read("subtitle_data")
|
||||||
|
if subtitle_data:
|
||||||
|
sorted_keys = sorted(subtitle_data.keys())
|
||||||
|
if sorted_keys:
|
||||||
|
status['last_subtitle'] = subtitle_data[sorted_keys[-1]]
|
||||||
|
|
||||||
|
# Récupérer la dernière génération
|
||||||
|
generation_data = storage.read("IA_generator")
|
||||||
|
if generation_data:
|
||||||
|
sorted_keys = sorted(generation_data.keys())
|
||||||
|
if sorted_keys:
|
||||||
|
status['next_message'] = generation_data[sorted_keys[-1]]
|
||||||
|
|
||||||
|
return jsonify(status)
|
||||||
|
|
||||||
|
@socketio.on('connect')
|
||||||
|
def handle_connect():
|
||||||
|
print('Client connecté')
|
||||||
|
emit('status', {'message': 'Connecté au serveur'})
|
||||||
|
|
||||||
|
@socketio.on('disconnect')
|
||||||
|
def handle_disconnect():
|
||||||
|
print('Client déconnecté')
|
||||||
|
|
||||||
|
# Thread pour envoyer les mises à jour en temps réel
|
||||||
|
def background_updates():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
status = {
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'flux_count': len(bot_controller.flux_list),
|
||||||
|
'active_recordings': sum(1 for f in bot_controller.flux_list if f['record_audio'] and f['active']),
|
||||||
|
}
|
||||||
|
socketio.emit('status_update', status)
|
||||||
|
time.sleep(5) # Mise à jour toutes les 5 secondes
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur dans background_updates: {e}")
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Démarrer les mises à jour en arrière-plan
|
||||||
|
update_thread = threading.Thread(target=background_updates, daemon=True)
|
||||||
|
update_thread.start()
|
||||||
|
|
||||||
|
# Démarrer l'application Flask
|
||||||
|
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"text": " festival de la morue \u00e0 non oppos\u00e9s idon c'est quoi trop bien c'est trop bien si c'est \u00e7a on n'est que des nouveaux trucs tu as vu mon truc pour \u00e7a ouais une r\u00e9pastonnage aussi", "segments": [{"id": 0, "seek": 0, "start": 0.0, "end": 7.6000000000000005, "text": " festival de la morue \u00e0 non oppos\u00e9s idon c'est quoi", "tokens": [50365, 12091, 368, 635, 1896, 622, 1531, 2107, 4665, 2191, 4496, 266, 269, 6, 377, 11714, 50745], "temperature": 0.0, "avg_logprob": -0.43424586032299284, "compression_ratio": 1.3644859813084111, "no_speech_prob": 0.21884578466415405}, {"id": 1, "seek": 0, "start": 7.6000000000000005, "end": 21.0, "text": " trop bien c'est trop bien si c'est \u00e7a on n'est que des nouveaux trucs tu as vu mon truc pour", "tokens": [50745, 9006, 3610, 269, 6, 377, 9006, 3610, 1511, 269, 6, 377, 2788, 322, 297, 6, 377, 631, 730, 44952, 33505, 2604, 382, 9732, 1108, 14805, 2016, 51415], "temperature": 0.0, "avg_logprob": -0.43424586032299284, "compression_ratio": 1.3644859813084111, "no_speech_prob": 0.21884578466415405}, {"id": 2, "seek": 2100, "start": 21.0, "end": 26.28, "text": " \u00e7a ouais une r\u00e9pastonnage aussi", "tokens": [50365, 2788, 30570, 2251, 14243, 525, 19968, 609, 6212, 50629], "temperature": 0.0, "avg_logprob": -0.3519504720514471, "compression_ratio": 0.8048780487804879, "no_speech_prob": 0.00010390546958660707}], "language": "fr"}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
1
|
|
||||||
00:00:00,000 --> 00:00:07,600
|
|
||||||
festival de la morue à non opposés idon c'est quoi
|
|
||||||
|
|
||||||
2
|
|
||||||
00:00:07,600 --> 00:00:21,000
|
|
||||||
trop bien c'est trop bien si c'est ça on n'est que des nouveaux trucs tu as vu mon truc pour
|
|
||||||
|
|
||||||
3
|
|
||||||
00:00:21,000 --> 00:00:26,280
|
|
||||||
ça ouais une répastonnage aussi
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
start end text
|
|
||||||
0 7600 festival de la morue à non opposés idon c'est quoi
|
|
||||||
7600 21000 trop bien c'est trop bien si c'est ça on n'est que des nouveaux trucs tu as vu mon truc pour
|
|
||||||
21000 26280 ça ouais une répastonnage aussi
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
festival de la morue à non opposés idon c'est quoi
|
|
||||||
trop bien c'est trop bien si c'est ça on n'est que des nouveaux trucs tu as vu mon truc pour
|
|
||||||
ça ouais une répastonnage aussi
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
WEBVTT
|
|
||||||
|
|
||||||
00:00.000 --> 00:07.600
|
|
||||||
festival de la morue à non opposés idon c'est quoi
|
|
||||||
|
|
||||||
00:07.600 --> 00:21.000
|
|
||||||
trop bien c'est trop bien si c'est ça on n'est que des nouveaux trucs tu as vu mon truc pour
|
|
||||||
|
|
||||||
00:21.000 --> 00:26.280
|
|
||||||
ça ouais une répastonnage aussi
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"14h30m20s": "Salut ! Ça va super bien merci ! 😊",
|
||||||
|
"14h31m28s": "Ce jeu a l'air génial, bonne chance !",
|
||||||
|
"14h32m50s": "Ça arrive aux meilleurs, tu vas y arriver !",
|
||||||
|
"14h33m15s": "Bienvenue dans la communauté ! 🎉",
|
||||||
|
"14h34m35s": "Waouh cette action était épique ! 🔥",
|
||||||
|
"14h35m25s": "Bonne idée, change de tactique !",
|
||||||
|
"14h36m10s": "Bravo ! Tu maîtrises de mieux en mieux",
|
||||||
|
"14h37m28s": "Toujours là pour aider ! 💪",
|
||||||
|
"01h40m18s": "Plot twist inattendu ! 😮"
|
||||||
|
}
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
{
|
{
|
||||||
"00:12:31": "festival de la morue à non opposés idon cest quoi trop bien si ça on nest que des nouveaux trucs tu as vu mon truc pour ouais une répastonnage aussi"
|
"14h30m15s": "Salut les viewers ! Comment ça va aujourd'hui ?",
|
||||||
|
"14h31m22s": "On va jouer à ce nouveau jeu, j'ai hâte de voir ce que ça donne",
|
||||||
|
"14h32m45s": "Oh non, je suis mort déjà ! C'est plus dur que je pensais",
|
||||||
|
"14h33m12s": "Merci pour le follow @nouveau_viewer !",
|
||||||
|
"14h34m30s": "Cette partie est vraiment intense, regardez ça !",
|
||||||
|
"14h35m18s": "Je pense qu'on devrait essayer une autre stratégie",
|
||||||
|
"14h36m05s": "Excellent, on progresse enfin dans ce niveau",
|
||||||
|
"14h37m22s": "N'hésitez pas à poser vos questions dans le chat",
|
||||||
|
"01h40m18s": "Vous avez vu cette action incroyable ?"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user