synthèse vocale
This commit is contained in:
@@ -15,7 +15,8 @@ Cest la relation à la parole et laliénation du sujet en tant que nous y av
|
|||||||
Tout est très différent.
|
Tout est très différent.
|
||||||
Les arbres longeant la caserne, seront éclairés, pour déboucher la perspective montante dans l'avenue Robert Schuman.
|
Les arbres longeant la caserne, seront éclairés, pour déboucher la perspective montante dans l'avenue Robert Schuman.
|
||||||
La jeune femme, belle voix, beaux gestes, débute par J'ai un faible pour les forts (1932), un classique souvent traité en marche de cavalerie.
|
La jeune femme, belle voix, beaux gestes, débute par J'ai un faible pour les forts (1932), un classique souvent traité en marche de cavalerie.
|
||||||
Le premier film de la série était tout de même regardable, mais on dirait que cela va en s'empirant d'un film à l'autre.Je ne veux plus rien savoir de cette série que je trouve de moins en moins drôle et de plus en plus prétentieuse.
|
Le premier film de la série était tout de même regardable, mais on dirait que cela va en s'empirant d'un film à l'autre.
|
||||||
|
Je ne veux plus rien savoir de cette série que je trouve de moins en moins drôle et de plus en plus prétentieuse.
|
||||||
Le nouveau complexe de jeux rêvé par Loto-Québec comprendrait un hôtel de luxe de 350 chambres et le casino serait souterrain.
|
Le nouveau complexe de jeux rêvé par Loto-Québec comprendrait un hôtel de luxe de 350 chambres et le casino serait souterrain.
|
||||||
Dwight Freeney et ses coéquipiers en défensive veulent prouver qu'ils peuvent stopper la course.
|
Dwight Freeney et ses coéquipiers en défensive veulent prouver qu'ils peuvent stopper la course.
|
||||||
Avec l'arrivée de Paul Painlevé à la présidence du Conseil, le sous-secrétariat d'Etat passe sous la tutelle du ministère de la guerre, à la tête duquel se trouve Painlevé, et est rattaché à la section technique du génie.
|
Avec l'arrivée de Paul Painlevé à la présidence du Conseil, le sous-secrétariat d'Etat passe sous la tutelle du ministère de la guerre, à la tête duquel se trouve Painlevé, et est rattaché à la section technique du génie.
|
||||||
|
|||||||
206
index.html
Normal file
206
index.html
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Chatβeta*</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="cat.svg">
|
||||||
|
<link rel="apple-touch-icon" sizes="240x240" href="thumbnail.png" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Chatβeta*" />
|
||||||
|
<meta property="og:title" content="😸 Chatβeta*" />
|
||||||
|
<meta property="og:type" content="game" />
|
||||||
|
<meta property="og:locale" content="fr_FR" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-height: 600px) {
|
||||||
|
body > header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(var(--pico-background-color) 60%, transparent);
|
||||||
|
text-shadow: 0 0 10px var(--pico-background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > header .container {
|
||||||
|
max-width: 750px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > main {
|
||||||
|
flex-grow: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reponse::before {
|
||||||
|
content: "😸 ";
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="container">
|
||||||
|
<h1>Chat<small><em>βeta</em><span style="color: #9B2318">*</span></small></h1>
|
||||||
|
<button id="bouton_synthese_vocale" type="button" class="outline" style="display: none;">🕨</button>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main class="container overflow-auto" id="conversation">
|
||||||
|
<p class="reponse">Posez-moi toutes vos questions !</p>
|
||||||
|
</main>
|
||||||
|
<footer class="container">
|
||||||
|
<form id="formulaire" action="question.php" method="post" role="group">
|
||||||
|
<textarea id="question" name="question" placeholder="Ma question" required></textarea>
|
||||||
|
<button type="submit">Envoyer</button>
|
||||||
|
</form>
|
||||||
|
</footer>
|
||||||
|
<dialog id="boite_synthese_vocale">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<button id="bouton_fermer" aria-label="Close" rel="prev"></button>
|
||||||
|
<p>
|
||||||
|
<strong>🕨 Synthèse vocale</strong>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<form>
|
||||||
|
<fieldset>
|
||||||
|
<label>Voix disponibles</label>
|
||||||
|
<select id="select_voix">
|
||||||
|
<option value="">Pas de synthèse vocale</option>
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<footer>
|
||||||
|
<button id="bouton_annuler" class="secondary">Annuler</button>
|
||||||
|
<button id="bouton_ok">OK</button>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
</dialog>
|
||||||
|
<script>
|
||||||
|
const formulaire = document.getElementById('formulaire');
|
||||||
|
const bouton = document.querySelector('button[type="submit"]');
|
||||||
|
const conversation = document.getElementById('conversation');
|
||||||
|
const question = document.getElementById('question');
|
||||||
|
const bouton_synthese_vocale = document.getElementById('bouton_synthese_vocale');
|
||||||
|
const boite_synthese_vocale = document.getElementById('boite_synthese_vocale');
|
||||||
|
const select_voix = document.getElementById('select_voix');
|
||||||
|
const bouton_fermer = document.getElementById('bouton_fermer');
|
||||||
|
const bouton_annuler = document.getElementById('bouton_annuler');
|
||||||
|
const bouton_ok = document.getElementById('bouton_ok');
|
||||||
|
const langue = "fr-FR";
|
||||||
|
|
||||||
|
let voix = null;
|
||||||
|
if ('speechSynthesis' in window) {
|
||||||
|
let liste_voix = speechSynthesis.getVoices().filter(voice => voice.lang === langue);
|
||||||
|
liste_voix.forEach((v, i) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = i;
|
||||||
|
option.innerText = v.name;
|
||||||
|
select_voix.appendChild(option);
|
||||||
|
if (v.voiceURI === window.localStorage.getItem('voix')) {
|
||||||
|
select_voix.value = i;
|
||||||
|
voix = v;
|
||||||
|
option.selected = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bouton_synthese_vocale.style.display = 'block';
|
||||||
|
bouton_synthese_vocale.addEventListener('click', () => {
|
||||||
|
boite_synthese_vocale.showModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
bouton_fermer.addEventListener('click', () => {
|
||||||
|
boite_synthese_vocale.close();
|
||||||
|
});
|
||||||
|
bouton_annuler.addEventListener('click', () => {
|
||||||
|
boite_synthese_vocale.close();
|
||||||
|
});
|
||||||
|
bouton_ok.addEventListener('click', () => {
|
||||||
|
boite_synthese_vocale.close();
|
||||||
|
if (select_voix.value) {
|
||||||
|
voix = liste_voix[select_voix.value];
|
||||||
|
window.localStorage.setItem('voix', voix.voiceURI);
|
||||||
|
} else {
|
||||||
|
voix = null;
|
||||||
|
window.localStorage.removeItem('voix');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
question.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.target.form.requestSubmit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
question.addEventListener('focus', () => {
|
||||||
|
question.scrollIntoView({ block: 'nearest' });
|
||||||
|
});
|
||||||
|
|
||||||
|
formulaire.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (bouton.disabled == true) return;
|
||||||
|
|
||||||
|
bouton.disabled = true;
|
||||||
|
bouton.setAttribute("aria-busy", true)
|
||||||
|
const formulaireData = new FormData(formulaire);
|
||||||
|
const citation = document.createElement('article');
|
||||||
|
citation.innerText = formulaireData.get('question');
|
||||||
|
conversation.appendChild(citation);
|
||||||
|
formulaire.reset();
|
||||||
|
|
||||||
|
const paragraphe = document.createElement('p');
|
||||||
|
paragraphe.setAttribute("aria-busy", "true");
|
||||||
|
conversation.appendChild(paragraphe);
|
||||||
|
conversation.scrollTop = conversation.scrollHeight;
|
||||||
|
|
||||||
|
const requete = await fetch(formulaire.action, {
|
||||||
|
method: formulaire.method,
|
||||||
|
body: formulaireData
|
||||||
|
});
|
||||||
|
paragraphe.setAttribute('aria-busy', 'false');
|
||||||
|
const reponse = await requete.text();
|
||||||
|
paragraphe.setAttribute('aria-busy', 'false');
|
||||||
|
|
||||||
|
if (voix) {
|
||||||
|
const utterance = new SpeechSynthesisUtterance(reponse);
|
||||||
|
utterance.lang = langue;
|
||||||
|
utterance.voice = voix;
|
||||||
|
utterance.rate = 1;
|
||||||
|
speechSynthesis.speak(utterance);
|
||||||
|
}
|
||||||
|
let t = 0;
|
||||||
|
Array.from(reponse).forEach((lettre, i) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (lettre == "\n") {
|
||||||
|
paragraphe.innerHTML += "<br>";
|
||||||
|
} else {
|
||||||
|
paragraphe.innerHTML += lettre;
|
||||||
|
}
|
||||||
|
conversation.scrollTop = conversation.scrollHeight;
|
||||||
|
}, t += 100 * Math.random());
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
conversation.innerHTML += '<p class="reponse">Voulez-vous que je réponde à une autre question ?</p>';
|
||||||
|
conversation.scrollTop = conversation.scrollHeight;
|
||||||
|
bouton.disabled = false;
|
||||||
|
bouton.setAttribute("aria-busy", false);
|
||||||
|
question.focus()
|
||||||
|
}, t);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
88
index.php
88
index.php
@@ -1,88 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>ChatEB</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
|
||||||
<link rel="icon" type="image/svg+xml" href="cat.svg">
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#conversation {
|
|
||||||
flex-grow: 2;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<main class="container">
|
|
||||||
<h1>ChatEB</h1>
|
|
||||||
<div id="conversation">
|
|
||||||
<p>😸 Posez-moi toutes vos questions !</p>
|
|
||||||
</div>
|
|
||||||
<form id="formulaire" action="question.php" method="post" role="group">
|
|
||||||
<textarea id="question" name="question" placeholder="Ma question" required></textarea>
|
|
||||||
<button type="submit">Envoyer</button>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
<script>
|
|
||||||
const formulaire = document.getElementById('formulaire');
|
|
||||||
const bouton = document.querySelector('button[type="submit"]');
|
|
||||||
const conversation = document.getElementById('conversation');
|
|
||||||
const question = document.getElementById('question');
|
|
||||||
|
|
||||||
question.addEventListener('keydown', e => {
|
|
||||||
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.target.form.requestSubmit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
formulaire.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
bouton.disabled = true;
|
|
||||||
bouton.setAttribute("aria-busy", true)
|
|
||||||
const formulaireData = new FormData(formulaire);
|
|
||||||
const citation = document.createElement('blockquote');
|
|
||||||
citation.innerText = formulaireData.get('question');
|
|
||||||
conversation.appendChild(citation);
|
|
||||||
formulaire.reset();
|
|
||||||
|
|
||||||
const paragraphe = document.createElement('p');
|
|
||||||
paragraphe.setAttribute('aria-busy', 'true');
|
|
||||||
conversation.appendChild(paragraphe);
|
|
||||||
|
|
||||||
const requete = await fetch(formulaire.action, {
|
|
||||||
method: formulaire.method,
|
|
||||||
body: formulaireData
|
|
||||||
});
|
|
||||||
paragraphe.setAttribute('aria-busy', 'false');
|
|
||||||
|
|
||||||
const reader = requete.body.getReader();
|
|
||||||
const decoder = new TextDecoder("utf-8");
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const { value, done } = await reader.read();
|
|
||||||
if (done) break;
|
|
||||||
const lettre = decoder.decode(value, { stream: true });
|
|
||||||
if (lettre == "\n") paragraphe.innerHTML += "<br>";
|
|
||||||
else paragraphe.innerHTML += lettre;
|
|
||||||
await new Promise(r => setTimeout(r, 0)); // micro pause pour rendre l'UI responsive
|
|
||||||
}
|
|
||||||
|
|
||||||
conversation.innerHTML += "<p>😸 Voulez-vous que je réponde à une autre question ?</p>";
|
|
||||||
conversation.scrollTop = conversation.scrollHeight;
|
|
||||||
bouton.disabled = false;
|
|
||||||
bouton.setAttribute("aria-busy", false);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
22
markov.py
22
markov.py
@@ -1,22 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
from random import choice, randrange
|
|
||||||
|
|
||||||
suivants = defaultdict(list)
|
|
||||||
|
|
||||||
with open("fra_mixed_2009_10K-sentences.txt", "r", encoding="utf-8") as fichier:
|
|
||||||
for phrase in fichier:
|
|
||||||
antepenultieme, penultieme = "", ""
|
|
||||||
for word in phrase.split():
|
|
||||||
suivants[(antepenultieme, penultieme)].append(word)
|
|
||||||
antepenultieme, penultieme = penultieme, word
|
|
||||||
|
|
||||||
phrases = []
|
|
||||||
for _ in range(randrange(1, 4)):
|
|
||||||
antepenultieme, penultieme = "", ""
|
|
||||||
phrase = []
|
|
||||||
while mots_possibles := suivants[(antepenultieme, penultieme)]:
|
|
||||||
mot_suivants = choice(mots_possibles)
|
|
||||||
phrase.append(mot_suivants)
|
|
||||||
antepenultieme, penultieme = penultieme, mot_suivants
|
|
||||||
phrases.append(" ".join(phrase))
|
|
||||||
print("\n".join(phrases))
|
|
||||||
67
question.php
67
question.php
@@ -3,18 +3,59 @@ header('Content-Type: text/plain; charset=utf-8');
|
|||||||
header('Cache-Control: no-cache'); // pour éviter le buffering du navigateur
|
header('Cache-Control: no-cache'); // pour éviter le buffering du navigateur
|
||||||
header('X-Accel-Buffering: no'); // si nginx, pour désactiver le buffering
|
header('X-Accel-Buffering: no'); // si nginx, pour désactiver le buffering
|
||||||
|
|
||||||
$reponse = `python markov.py`;
|
$suivants = [];
|
||||||
|
$total = [];
|
||||||
|
|
||||||
// désactiver le buffering PHP
|
// Lire le fichier fra_mixed_2009_100K-sentences.txt
|
||||||
@ini_set('output_buffering', 'off');
|
$fichier = fopen("fra_mixed_2009_10K-sentences.txt", "r");
|
||||||
@ini_set('zlib.output_compression', 'off');
|
if ($fichier) {
|
||||||
while (ob_get_level()) ob_end_flush();
|
while (($phrase = fgets($fichier)) !== false) {
|
||||||
flush();
|
$antepenultieme = "";
|
||||||
|
$penultieme = "";
|
||||||
// envoyer chaque lettre avec un délai
|
$mots = explode(" ", trim($phrase));
|
||||||
$len = strlen($reponse);
|
foreach ($mots as $mot) {
|
||||||
for ($i = 0; $i < $len; $i++) {
|
$cle = $antepenultieme . " " . $penultieme;
|
||||||
echo $reponse[$i];
|
if (!isset($suivants[$cle])) {
|
||||||
flush(); // forcer l'envoi immédiat
|
$suivants[$cle] = [];
|
||||||
usleep(40000); // 40ms = 40000 microsecondes
|
|
||||||
}
|
}
|
||||||
|
if (!isset($suivants[$cle][$mot])) {
|
||||||
|
$suivants[$cle][$mot] = 0;
|
||||||
|
}
|
||||||
|
$suivants[$cle][$mot]++;
|
||||||
|
if (!isset($total[$cle])) {
|
||||||
|
$total[$cle] = 0;
|
||||||
|
}
|
||||||
|
$total[$cle]++;
|
||||||
|
$antepenultieme = $penultieme;
|
||||||
|
$penultieme = $mot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fichier);
|
||||||
|
} else {
|
||||||
|
http_response_code(500);
|
||||||
|
die("Impossible d'ouvrir le fichier fra_mixed_2009_10K-sentences.txt\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$phrases = [];
|
||||||
|
for ($i = 0; $i < rand(1, 3); $i++) {
|
||||||
|
$antepenultieme = "";
|
||||||
|
$penultieme = "";
|
||||||
|
$phrase = [];
|
||||||
|
while (isset($suivants[$antepenultieme . " " . $penultieme]) && !empty($suivants[$antepenultieme . " " . $penultieme])) {
|
||||||
|
$cle = $antepenultieme . " " . $penultieme;
|
||||||
|
$choix = rand(1, $total[$cle]);
|
||||||
|
foreach ($suivants[$cle] as $mot => $occurences) {
|
||||||
|
if ($choix <= $occurences) {
|
||||||
|
$suivant = $mot;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$choix -= $occurences;
|
||||||
|
}
|
||||||
|
$phrase[] = $suivant;
|
||||||
|
$antepenultieme = $penultieme;
|
||||||
|
$penultieme = $suivant;
|
||||||
|
}
|
||||||
|
$phrases[] = implode(" ", $phrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo implode("\n", $phrases) . "\n";
|
||||||
|
|||||||
Reference in New Issue
Block a user