diff --git a/speech/talk.php b/speech/talk.php
index f1949a3209ac46885bfc08e1b50af5290db70d66..1bd2af2aa71b2715595f548972f8e2ad51907b71 100644
--- a/speech/talk.php
+++ b/speech/talk.php
@@ -1,30 +1,48 @@
 <?php
-	require_once("../../conf.php");
-	require_once("../../lib/sql_interface.php");
+	require_once("../conf.php");
+/*
+CREATE TABLE IF NOT EXISTS speech (
+				token VARCHAR(20) PRIMARY KEY,
+				msg VARCHAR(255),
+				host VARCHAR(50),
+				t TIMESTAMP DEFAULT now()
+			)";
+*/
+	function speechRead($token) {
+		global $sql;
+		$query = "SELECT msg FROM speech WHERE token=$1";
+		$data = $sql->sql_secure($query, array($token));
+		$err = $sql->sql_error();
+		return (!empty($err))? $err: $data[0]['msg'];
+	}
+	function speechUpsert($token, $msg) {
+		global $sql;
+		$query = "DELETE FROM speech WHERE EXTRACT(EPOCH FROM NOW()-t) > 36000"; 
+		$sql->sql_query($query);
+		$query = "INSERT INTO speech (token, msg, host) VALUES ($1, $2, '".($_SERVER['REMOTE_ADDR'].'_'.$_SERVER['HTTP_X_FORWARDED_FOR'])."') ON CONFLICT (token) DO UPDATE SET msg=$3";
+		$data = $sql->sql_secure($query, array($token, $msg, $msg));
+		$err = $sql->sql_error();
+		if (!empty($err)) echo $err;
+	}
 	$sql = open_db();
+	// if (!empty($_REQUEST['testupsert'])) {speechUpsert($_REQUEST['upsert'], $_REQUEST['msg']);exit();}
+	// if (!empty($_REQUEST['testsread'])) {die(speechRead($_REQUEST['sread']));}
 	$local = isset($_REQUEST['cdn'])? false: true; // true = use only locally installed modules, false = use cdn
-	$hs = ['192.168.231.21'=>'do', '192.168.231.22'=>'re', '192.168.231.23'=>'mi', '192.168.231.24'=>'fa', '192.168.231.25'=>'sol', '192.168.231.26'=>'la', '192.168.231.27'=>'si'];
 	$lang = empty($_REQUEST['lang'])? 'it': substr($_REQUEST['lang'], 0, 2);
 	$host = isset($hs[$_SERVER['REMOTE_ADDR']])? $hs[$_SERVER['REMOTE_ADDR']]: $_SERVER['REMOTE_ADDR'];
 	function mylog() {
 		file_put_contents('./logs'.date('Y').'.txt', date('Y-m-d H:i:s').' '.$_SERVER['REMOTE_ADDR'].'_'.$_SERVER['HTTP_X_FORWARDED_FOR'].' - '.$_SERVER['QUERY_STRING'].PHP_EOL , FILE_APPEND | LOCK_EX);
 	}
-	if (!empty($_REQUEST['init'])) {
+	/*if (!empty($_REQUEST['init'])) {
 		$t = $_REQUEST['init']-0;
 		if ($t==0 || $t>1000000000000) die('');
 		file_put_contents("./token", $t);
-	}
-	else if (!empty($_REQUEST)) mylog();
-	if (!empty($_REQUEST['speech'])) {
-		$query = "UPDATE launcher SET speech_$lang = array_append(speech_$lang, $1) WHERE id = $2";
-		$data = $sql->sql_secure($query, array($_REQUEST['speech'],$_REQUEST['id']));
-		header('Content-Type: application/json');
-		die(json_encode($data));
-	}
+	}*/
+	if (!empty($_REQUEST['search']) || !empty($_REQUEST['open'])) mylog();
 	if (!empty($_REQUEST['list'])) {
-		$t = file_get_contents("./token");
+		/*$t = file_get_contents("./token");
 		if ($t != $_REQUEST['token']-0) die('[]');
-		if (time() -filectime("./token") > 36000) die('{}');
+		if (time() -filectime("./token") > 36000) die('{}');*/
 		$query = "SELECT id FROM launcher WHERE $1=ANY(speech_$lang)";
 		$data = $sql->sql_secure($query, array($_REQUEST['list']));
 		if ($data !== false) {
@@ -57,6 +75,18 @@
 		100 100 /runtime/bin/modmulti-gui
 		
 		*/
+		if (!empty($_REQUEST['speech'])) {
+			if (!empty($_REQUEST['id'])) {
+				$query = "UPDATE launcher SET speech_$lang = array_append(speech_$lang, $1) WHERE id = $2";
+				$data = $sql->sql_secure($query, array($_REQUEST['speech'],$_REQUEST['id']));
+			}
+			else {
+				$query = "UPDATE launcher SET speech_$lang = array_append(speech_$lang, $1) WHERE exename = $2";
+				$data = $sql->sql_secure($query, array($_REQUEST['speech'],$_REQUEST['open']));
+			}
+			// header('Content-Type: application/json');
+			// die(json_encode($data));
+		}
 		// $t = file_get_contents("./token"); if ($t != $_REQUEST['token']-0) die('[]'); if (time() -filectime("./token") > 36000) die('{}');
 		$query = "SELECT exename, path FROM launcher WHERE $1=ANY(speech_$lang)";
 		$data = $sql->sql_secure($query, array($_REQUEST['open']));
@@ -72,9 +102,9 @@
 		header('Content-Type: application/json');
 		die(json_encode($data));
 	}
-	if (isset($_REQUEST['search'])) {
-		if (isset($_REQUEST['speech'])) {
-			$speech = explode('_', strtr(strtoupper($_REQUEST['speech']), [' '=>'_', '.'=>'_']));
+	if (!empty($_REQUEST['search'])) {
+		if (!empty($_REQUEST['speech'])) {
+			$speech = explode('_', strtr(strtoupper($_REQUEST['speech']), [' '=>'_', '.'=>'_', '-'=>'_']));
 			$sr = explode('_', $_REQUEST['search']);
 			foreach ($speech as $i=>$s) {
 				if ($s != $sr[$i]) {
@@ -90,20 +120,30 @@
 			$replace[$d['speech']] = $d['replace'];
 		}
 		$search = strtr($_REQUEST['search'], $replace);
-		if (!empty($search)) {file_put_contents("./msg", $search); die("$search has been searched");}
+		if (!empty($search)) {
+			speechUpsert($_REQUEST['token'], $search);
+			for ($i=0; $i<50; $i++) {
+				$f = speechRead($_REQUEST['token']);
+				if (!empty($f) && $f != $search) {
+					speechUpsert($_REQUEST['token'], '');
+					die($f);
+				}
+				usleep(100000);
+			}
+			speechUpsert($_REQUEST['token'], '');
+			die("$search not found, timeout");
+		}
+	}
+	if (isset($_REQUEST['readresult'])) {
+		speechUpsert($_REQUEST['token'], substr($_REQUEST['readresult'], 0, 15));
 	}
 	if (isset($_REQUEST['read'])) {
 		set_time_limit(0);
-		/* $t = file_get_contents("./token");
-		if ($t != $_REQUEST['token']-0) die('');
-		if (time() -filectime("./token") > 36000) die('');*/
 		while (true) {
-			if (file_exists("./msg")) {
-				$f = file_get_contents("http://puma-01.elettra.eu/panther/speech/msg");
-				if (!empty($f)) {
-					file_put_contents("./msg", '');
-					die($f);
-				}
+			$f = speechRead($_REQUEST['token']);
+			if (!empty($f)) {
+				speechUpsert($_REQUEST['token'], '');
+				die($f);
 			}
 			usleep(100000);
 		}
@@ -144,7 +184,35 @@
 		// /home/fermi/etc/launcher/fermi/launcher_es.conf
 		// /runtime/etc/browser/fermi.xml
 		// /runtime/etc/browser/laser.xml
-		$f = strtr(file_get_contents('https://gitlab.elettra.eu/cs/etc/browser/fermi/-/raw/master/fermi.xml', false, $context), ['exename=""'=>'']);
+		if ($_REQUEST['starter'] == 'launcher_es.conf') {
+			// DELETE FROM launcher WHERE id > 2094
+			// ALTER SEQUENCE launcher_id_seq RESTART WITH 2095
+			$f = file($_REQUEST['starter']);
+			$j == -10;
+			foreach ($f as $i=>$d) {
+				if (strpos($d, '#')===0) continue;
+				if (strpos($d, '@HEADING')===0) {
+					if (strpos($f[$i+1],'Injector')===0) $lastid = 197;
+					else if (strpos($f[$i+1], 'FEL1')===0) $lastid = 1335;
+					else if (strpos($f[$i+1], 'FEL2')===0) $lastid = 1637;
+					else if (strpos($f[$i+1], 'Tools-2')===0) continue;
+					else if (strpos($f[$i+1], 'Legenda')===0) break;
+					else {$query = "INSERT INTO launcher (folder_id, title) VALUES (1, \"".trim($f[$i+1])."\")";echo "$query<br>\n"; $sql->sql_query($query); $lastid = $sql->last_insert_id() - 0;}
+					continue;
+				}
+				if (strpos($d, '@')===0 && strpos($d, 'TOOL')===2) $j = $i;
+				if ($i==$j+1) $title = $d;
+				if ($i==$j+2) {$exename = $d; if (strpos($d, '/')===0) {$exename = basename($d); $path = dirname($d);} else $path = '';}
+				if ($i==$j+4) {
+					$query = strtr("INSERT INTO launcher (folder_id, title, description, exename, path) VALUES ($lastid, \"$title\",\"$d\",\"$exename\",\"$path\")", ['""'=>'NULL','"/"'=>'NULL',"'"=>'','"'=>"'"]);
+					$sql->sql_query($query);
+					echo "$query<br>\n";
+				}
+				// echo "$d<br>\n";
+			}
+			die('done');
+		}
+		$f = strtr(file_get_contents("https://gitlab.elettra.eu/cs/etc/browser/fermi/-/raw/master/fermi.xml", false, $context), ['exename=""'=>'']);
 		// var_dump(libxml_use_internal_errors(true));
 		$xml = simplexml_load_string($f, "SimpleXMLElement", LIBXML_NOERROR |  LIBXML_ERR_NONE);
 		if ($xml === false) {
@@ -180,18 +248,22 @@
 	</head>
 	<body onLoad='myload()'>
 		<div id='transcriptDiv' style='display:none;'></div>
-		<div style="max-width: 500px; text-align: center">
+		<div style="max-width: 280px; max-height: 30px; text-align: center;">
 			<audio id='speechText' onended='speakagain();' onerror='showLog("audio err: "+event.error)'></audio>
 			<span style='color: darkgreen; font-weight: bold;' id='title'>Talk to ...</span>
 
-			<img src='./microphone-na.png' title='not available' onClick='$("#micstart").show();$("#micna").hide();$("#miclabel").hide();'  id='micna'>
-			<img src='./microphone-on.png' onClick='stopRec()' id='micstart' style='display: none; padding-right: 60px;'>
+			<img src='./microphone-na.png' onClick='naClick()' title='not available' id='micna'>
+			<img src='./microphone-on.png' onClick='stopRec()' id='micstart' style='display: none; padding-right: 60px; max-width: 300px;'>
 			<img src='./microphone-off.png' onClick='startRec()' id='micstop' style='display: none; padding-right: 60px;'>
 			<div style='cursor: pointer;padding-right: 5px; text-align: right; margin-top: -1.3em;' id='langselect'>
 				<img style="max-height: 18px; margin-right: 10px;" src='./it-IT.png' onClick="switchLocale('it')">
 				<img style="max-height: 18px; margin-right: 10px;" src='./en-US.png' onClick="switchLocale('en')">
 				<span style='color: darkgreen; font-weight: bold;' onClick='commandlistinfo()'> ? </span>
 			</div>
+			<div style='padding-left: 5px; text-align: left; margin-top: -1.3em;' id='operation'>
+				<img style="margin-right: 10px;" id='open' src='./open.png'>
+				<img style="margin-right: 10px;" id='search' src='./search.png'>&nbsp;
+			</div>
 			<span style='color: darkblue; font-weight: bold;' id='miclabel'>comandi vocali&nbsp;&nbsp;&nbsp;</span>
 			<table id ='openTable' class="table table-hover"></table>
 			<pre id='log'></pre>
@@ -200,12 +272,15 @@
 				// showLog('SpeechRecognition: '+ typeof window.SpeechRecognition);
 				window.SpeechRecognition = window.webkitSpeechRecognition || window.SpeechRecognition;
 				if (typeof window.SpeechRecognition == 'undefined' && document.location.search.indexOf('?d=')==-1) {
-					const tok =  Math.round(Math.random()*1000000000000+1);
+					let tok = document.location.search.indexOf('token=')==-1? Math.round(Math.random()*1000000000000+1): document.location.search.split('token=')[1].split('&')[0];
+					if (isNaN(tok) || tok<1000000000) tok = Math.round(Math.random()*1000000000000+1);
 					console.log('./qr.php?data='+tok+'&host=<?php echo $host;?>');
 					// fetch('./talk.php?init='+tok+'&host=<?php echo $host;?>');
 					document.getElementById('micstart').src = './qr.php?data='+tok+'&host=<?php echo $host;?>';
 					document.getElementById('langselect').style.display = 'none';
 					document.getElementById('miclabel').style.display = 'none';
+					$('#search').hide(); $('#open').hide();
+					if (window.parent && window.parent.document.getElementById('talk')) window.parent.document.getElementById('talk').style.height = '20px';
 				}
 				const launcher = ["<?php echo implode('","', $launcher);?>"];
 			</script>