feat(profile): selezione del profilo di cui vedere le sessioni (v0.5.0)

- Impostazioni > Sessions: selettore "Profile" (elenco da /api/profiles;
  "(server active)" come prima voce = nessun cookie di profilo)
- ApiClient: fetchProfiles() + switchProfile(); lo switch usa
  /api/profile/switch (process_wide=False lato server: il cookie firmato
  hermes_profile scopa le richieste dell'app per-client; il profilo attivo
  globale del server non viene toccato); refresh automatico delle sessioni
- CookieJar::removeCookiesByName per l'opzione "(server active)"
- Il profilo scelto viene riapplicato automaticamente dopo il login
- i18n: nuove stringhe tradotte it/fr/de
This commit is contained in:
2026-09-12 22:46:27 +02:00
parent 22ccb49424
commit 7b8e5e1102
15 changed files with 421 additions and 104 deletions
+6 -3
View File
@@ -16,6 +16,8 @@ chat with your Hermes agent from the phone — and use it with your **voice**.
read-aloud loop, auto-stopping on speech pauses (tunable)
- **Localized UI**: English (source), Italian, French and German — follows
the system language
- **Profile selection**: pick which server profile's sessions to show
(Settings → Sessions; per-client via the WebUI signed cookie)
- Cancel a running turn, resync from server state, cover page
## Requirements
@@ -35,6 +37,7 @@ requires a server address.
| Setting | Meaning |
|---|---|
| Server | Hermes Web UI address, e.g. `https://hermes.example.com` (required) |
| Profile | server profile whose sessions are shown (empty = server's active) |
| Read-aloud voice / TTS engine | empty = server defaults |
| Voice/engine presets | comma-separated lists used to fill the pickers |
| Read replies aloud | auto-play every reply (off by default) |
@@ -73,6 +76,6 @@ speaks (validated live against a real instance).
## Status
Version 0.4.0 — protocol and C++ core validated against a real `hermes-webui`
(login, sessions, streaming chat, TTS, STT, voice dialog); UI in English,
Italian, French and German; on-device testing still ongoing.
Version 0.5.0 — protocol and C++ core validated against a real `hermes-webui`
(login, sessions, streaming chat, TTS, STT, voice dialog, profile selection);
UI in English, Italian, French and German; on-device testing still ongoing.
+3 -3
View File
@@ -76,12 +76,12 @@ richiede il probe WebUI su :8899). Esito 12/09/2026: unit 9/9, live completo OK.
```bash
cd ~/workspace
tar czf harbour-hermes-0.4.0.tar.gz \
--transform 's,^harbour-hermes,harbour-hermes-0.4.0,' \
tar czf harbour-hermes-0.5.0.tar.gz \
--transform 's,^harbour-hermes,harbour-hermes-0.5.0,' \
--exclude='.git' harbour-hermes
```
Lo spec si aspetta la directory `harbour-hermes-0.4.0/` (pattern degli altri
Lo spec si aspetta la directory `harbour-hermes-0.5.0/` (pattern degli altri
progetti: cercato in modo robusto anche per i sorgenti live di sfdk).
## Traduzioni
+22 -1
View File
@@ -173,7 +173,28 @@ Annullamento: `GET /api/chat/cancel?stream_id=ID` →
- Engine: `edge` (default, gratis), `elevenlabs`, `openai` (richiedono chiavi
configurate sul server). `browser` è solo client-side, non usabile qui.
## 5. Note operative
## 5. Profili (selezione dell'utente)
Le sessioni del WebUI appartengono a un *profilo* (campo `profile` nel payload
di `/api/sessions`). Il client sceglie di quale profilo vedere le sessioni con
lo switch per-client:
| Endpoint | Metodo | Note |
|---|---|---|
| `/api/profiles` | GET | `{"profiles": [{"name", "path", "is_default", "is_active", ...}], "active": "..."}` |
| `/api/profile/switch` | POST | body `{"name": "carlo"}` → `200` + `Set-Cookie: hermes_profile=<name>.<firma>` |
Il cookie `hermes_profile` e' **firmato lato server** (HMAC legato al token di
sessione, anti-forging): il client non puo' inventarlo, deve riceverlo dallo
switch e conservarlo nel cookie jar. Da quel momento **tutte** le richieste
dell'app sono profilate lato server (`server.py` applica il profilo
per-request dal cookie). Lo switch usa `process_wide=False`: **il profilo
attivo globale del server non viene toccato** (la vista del browser resta la
sua). Per tornare al profilo attivo del server basta rimuovere il cookie.
Validato contro il server reale (2026-09: switch → 200 + Set-Cookie firmato;
profilo inesistente → 404 `Profile 'x' does not exist`).
## 6. Note operative
- Tutti gli errori API sono JSON `{"error": "..."}`.
- Il server non espone CORS: il client nativo non serve preflight.
+81
View File
@@ -8,6 +8,9 @@ Page {
// impostazioni (ttsVoices/ttsEngines) e il valore corrente è sempre
// incluso. loading evita di salvare durante il caricamento iniziale.
property bool loading: true
// Feedback del selettore profilo (mostrato sotto il ComboBox).
property string profileStatusText: ""
property bool profileStatusError: false
SilicaFlickable {
anchors.fill: parent
@@ -34,6 +37,34 @@ Page {
}
}
SectionHeader { text: qsTr("Sessions") }
ComboBox {
label: qsTr("Profile")
currentIndex: page.profileIndex()
// Stesso pattern dei selettori voce (Repeater, provato in tide).
menu: ContextMenu {
Repeater {
model: page.profileChoices().length
delegate: MenuItem {
text: page.profileChoices()[index]
onClicked: api.switchProfile(page.profileValue(index))
}
}
}
}
Label {
x: Theme.horizontalPageMargin
width: parent.width - 2 * Theme.horizontalPageMargin
font.pixelSize: Theme.fontSizeExtraSmall
color: page.profileStatusError ? Theme.highlightColor
: Theme.secondaryHighlightColor
wrapMode: Text.WordWrap
visible: text.length > 0
text: page.profileStatusText
}
SectionHeader { text: qsTr("Voice") }
ComboBox {
@@ -160,11 +191,60 @@ Page {
VerticalScrollDecorator {}
}
Connections {
target: api
onProfileSwitched: {
page.profileStatusError = false
page.profileStatusText = name.length > 0
? qsTr("Profile: %1").arg(name)
: qsTr("Server active profile")
statusClear.restart()
}
onProfileSwitchFailed: {
page.profileStatusError = true
page.profileStatusText = error
statusClear.restart()
}
}
Timer {
id: statusClear
interval: 4000
onTriggered: page.profileStatusText = ""
}
function signOut() {
api.logout()
pageStack.replace(Qt.resolvedUrl("LoginPage.qml"))
}
// Valori del selettore profilo: "" (attivo sul server) + profili del
// server (da /api/profiles); il valore corrente e' sempre incluso.
function profileChoices() {
var out = [qsTr("(server active)")]
var list = api.profiles
for (var i = 0; i < list.length; i++)
out.push(list[i])
var cur = appSettings.profile
if (cur.length > 0 && out.indexOf(cur) < 0)
out.push(cur)
return out
}
function profileValue(i) {
if (i <= 0)
return ""
return page.profileChoices()[i]
}
function profileIndex() {
var cur = appSettings.profile
if (cur.length === 0)
return 0
var i = page.profileChoices().indexOf(cur)
return i < 0 ? 0 : i
}
// Elenchi dei selettori: preset configurati + valore corrente (così resta
// selezionabile anche se non è tra i preset). Indice 0 = "(server default)".
function voiceChoices() {
@@ -204,5 +284,6 @@ Page {
voicesField.text = appSettings.ttsVoices.join(", ")
enginesField.text = appSettings.ttsEngines.join(", ")
page.loading = false
api.fetchProfiles()
}
}
+8 -1
View File
@@ -1,6 +1,6 @@
Name: harbour-hermes
Summary: Client for the Hermes Web UI with voice
Version: 0.4.0
Version: 0.5.0
Release: 1
Group: Qt/Qt
License: MIT
@@ -25,6 +25,13 @@ Sessions list, chat with live streaming replies, voice dictation
(server-side TTS), plus a hands-free conversation mode.
%changelog
* Sat Sep 12 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.5.0-1
- Sezione Sessions nelle Impostazioni: selettore profilo (le sessioni
mostrate sono quelle del profilo scelto). Switch per-client via
/api/profile/switch con cookie firmato — il profilo attivo globale del
server NON viene toccato; elenco profili da /api/profiles; opzione
"(attivo sul server)" che rimuove il cookie; reapply del profilo dopo
il login
* Sat Sep 12 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.4.0-1
- Localizzazione dell'interfaccia: italiano, inglese (sorgente),
francese e tedesco (78 stringhe per lingua); .qm generati con
+77
View File
@@ -1,6 +1,7 @@
#include "apiclient.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
@@ -29,6 +30,9 @@ ApiClient::ApiClient(Settings *settings, QObject *parent)
m_jar->setFilePath(cfg + QStringLiteral("/cookies.dat"));
m_jar->load();
m_nam.setCookieJar(m_jar);
// Il profilo selezionato (impostazione app) e' esposto anche da ApiClient.
connect(m_settings, &Settings::profileChanged, this, &ApiClient::profileChanged);
}
// ---------------------------------------------------------------- helpers --
@@ -170,6 +174,11 @@ void ApiClient::login(const QString &password)
return;
}
setLoggedIn(true);
// Riapplica il profilo scelto dall'utente: il nuovo cookie di sessione
// va rilegato al profilo (switchProfile aggiorna anche le sessioni).
if (!m_settings->profile().isEmpty())
switchProfile(m_settings->profile());
else
refreshSessions();
});
}
@@ -221,6 +230,74 @@ int ApiClient::sessionsCount() const
return m_sessionsModel->rowCount();
}
QString ApiClient::profile() const
{
return m_settings->profile();
}
void ApiClient::fetchProfiles()
{
QNetworkReply *reply = m_nam.get(jsonRequest(QStringLiteral("/api/profiles")));
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
const QByteArray data = reply->readAll();
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status < 200 || status >= 300) {
qDebug() << "[api] fetchProfiles HTTP" << status;
return;
}
const QJsonArray arr = QJsonDocument::fromJson(data).object()
.value(QStringLiteral("profiles")).toArray();
QStringList names;
for (int i = 0; i < arr.size(); ++i) {
const QString n = arr.at(i).toObject().value(QStringLiteral("name")).toString();
if (!n.isEmpty())
names << n;
}
qDebug() << "[api] profili disponibili:" << names;
if (names != m_profiles) {
m_profiles = names;
emit profilesChanged();
}
});
}
void ApiClient::switchProfile(const QString &name)
{
const QString target = name.trimmed();
if (target.isEmpty()) {
// "(attivo sul server)": rimuove il cookie di profilo e torna a
// seguire il profilo attivo del server.
m_jar->removeCookiesByName(QStringLiteral("hermes_profile"));
m_settings->setProfile(QString());
emit profileSwitched(QString());
refreshSessions();
return;
}
QJsonObject body;
body.insert(QStringLiteral("name"), target);
QNetworkReply *reply = m_nam.post(jsonRequest(QStringLiteral("/api/profile/switch")),
QJsonDocument(body).toJson(QJsonDocument::Compact));
connect(reply, &QNetworkReply::finished, this, [this, reply, target]() {
reply->deleteLater();
const QByteArray data = reply->readAll();
const int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status >= 200 && status < 300) {
// Il Set-Cookie firmato (hermes_profile) viene salvato dal jar:
// da qui in poi le richieste dell'app sono profilate lato server.
m_settings->setProfile(target);
qDebug() << "[api] profilo selezionato:" << target;
emit profileSwitched(target);
refreshSessions();
} else {
const QString err = errorFromReply(reply, data);
qDebug() << "[api] switch profilo fallito:" << err;
emit profileSwitchFailed(err);
}
});
}
void ApiClient::openSession(const QString &id)
{
setLastError(QString());
+12
View File
@@ -40,6 +40,8 @@ class ApiClient : public QObject
Q_PROPERTY(SessionsModel *sessions READ sessions NOTIFY sessionsChanged)
Q_PROPERTY(int sessionsCount READ sessionsCount NOTIFY sessionsChanged)
Q_PROPERTY(QVariantMap session READ session NOTIFY sessionChanged)
Q_PROPERTY(QStringList profiles READ profiles NOTIFY profilesChanged)
Q_PROPERTY(QString profile READ profile NOTIFY profileChanged)
public:
explicit ApiClient(Settings *settings, QObject *parent = nullptr);
@@ -52,6 +54,8 @@ public:
SessionsModel *sessions() const { return m_sessionsModel; }
int sessionsCount() const;
QVariantMap session() const { return m_session; }
QStringList profiles() const { return m_profiles; }
QString profile() const;
// Avvio: verifica lo stato auth (cookie persistito o auth disabilitata).
Q_INVOKABLE void checkAuth();
@@ -59,6 +63,9 @@ public:
Q_INVOKABLE void logout();
Q_INVOKABLE void refreshSessions();
// Profilo server: elenco e selezione (per-client, cookie firmato WebUI).
Q_INVOKABLE void fetchProfiles();
Q_INVOKABLE void switchProfile(const QString &name);
Q_INVOKABLE void openSession(const QString &id);
Q_INVOKABLE void newSession();
@@ -80,6 +87,10 @@ signals:
void lastErrorChanged();
void sessionsChanged();
void sessionChanged();
void profilesChanged();
void profileChanged();
void profileSwitched(const QString &name); // ok (vuoto = attivo server)
void profileSwitchFailed(const QString &error);
void authChecked(bool loggedIn);
void sessionOpened(); // openSession/newSession completata
@@ -125,6 +136,7 @@ private:
SessionsModel *m_sessionsModel;
QVariantMap m_session;
QStringList m_profiles;
QString m_streamId;
QNetworkReply *m_streamReply;
SseParser m_parser;
+15
View File
@@ -63,6 +63,21 @@ void CookieJar::clear()
QFile::remove(m_path);
}
void CookieJar::removeCookiesByName(const QString &name)
{
const QByteArray raw = name.toUtf8();
const QList<QNetworkCookie> cookies = allCookies();
bool removed = false;
for (int i = 0; i < cookies.size(); ++i) {
if (cookies.at(i).name() == raw) {
deleteCookie(cookies.at(i)); // protected: accessibile dal subclass
removed = true;
}
}
if (removed)
save();
}
bool CookieJar::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
const bool changed = QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
+3
View File
@@ -21,6 +21,9 @@ public:
bool save() const;
void clear();
// Rimuove tutti i cookie con questo nome (es. hermes_profile) e salva.
void removeCookiesByName(const QString &name);
protected:
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url) override;
+2 -2
View File
@@ -21,9 +21,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
// AppConfigLocation = ~/.config/harbour/hermes (zona persistente della sandbox).
app->setOrganizationName(QStringLiteral("harbour"));
app->setApplicationName(QStringLiteral("hermes"));
app->setApplicationVersion(QStringLiteral("0.4.0"));
app->setApplicationVersion(QStringLiteral("0.5.0"));
qDebug() << "harbour-hermes v0.4.0 build" << __DATE__ << __TIME__;
qDebug() << "harbour-hermes v0.5.0 build" << __DATE__ << __TIME__;
// Traduzioni: it/fr/de da /usr/share/harbour-hermes/translations, in base
// alla lingua di sistema (l'inglese e' la lingua sorgente: nessun .qm).
+16
View File
@@ -123,6 +123,22 @@ void Settings::setVadMaxMs(int ms)
emit vadMaxMsChanged();
}
// Profilo server di cui mostrare le sessioni (vuoto = profilo attivo del
// server). L'app lo applica per-client tramite il cookie firmato del WebUI.
QString Settings::profile() const
{
return m_settings.value(QStringLiteral("profile"), QString()).toString();
}
void Settings::setProfile(const QString &name)
{
const QString clean = name.trimmed();
if (clean == profile())
return;
m_settings.setValue(QStringLiteral("profile"), clean);
emit profileChanged();
}
QStringList Settings::ttsVoices() const
{
return m_settings.value(QStringLiteral("ttsVoices")).toStringList();
+7
View File
@@ -28,6 +28,8 @@ class Settings : public QObject
NOTIFY vadThresholdChanged)
Q_PROPERTY(int vadMaxMs READ vadMaxMs WRITE setVadMaxMs
NOTIFY vadMaxMsChanged)
Q_PROPERTY(QString profile READ profile WRITE setProfile
NOTIFY profileChanged)
Q_PROPERTY(QStringList ttsVoices READ ttsVoices WRITE setTtsVoices
NOTIFY ttsVoicesChanged)
Q_PROPERTY(QStringList ttsEngines READ ttsEngines WRITE setTtsEngines
@@ -62,6 +64,10 @@ public:
int vadMaxMs() const;
void setVadMaxMs(int ms);
// Profilo server di cui mostrare le sessioni (vuoto = attivo del server).
QString profile() const;
void setProfile(const QString &name);
// Preset per i selettori voce/engine (liste configurabili, default vuote).
QStringList ttsVoices() const;
void setTtsVoices(const QStringList &list);
@@ -81,6 +87,7 @@ signals:
void vadSilenceMsChanged();
void vadThresholdChanged();
void vadMaxMsChanged();
void profileChanged();
void ttsVoicesChanged();
void ttsEnginesChanged();
void defaultWorkspaceChanged();
+56 -31
View File
@@ -4,17 +4,17 @@
<context>
<name>ApiClient</name>
<message>
<location filename="../src/apiclient.cpp" line="68" />
<location filename="../src/apiclient.cpp" line="72" />
<source>Authentication required</source>
<translation>Authentifizierung erforderlich</translation>
</message>
<message>
<location filename="../src/apiclient.cpp" line="70" />
<location filename="../src/apiclient.cpp" line="74" />
<source>HTTP %1</source>
<translation>HTTP %1</translation>
</message>
<message>
<location filename="../src/apiclient.cpp" line="155" />
<location filename="../src/apiclient.cpp" line="159" />
<source>Server address not configured</source>
<translation>Serveradresse nicht konfiguriert</translation>
</message>
@@ -264,141 +264,166 @@
<context>
<name>SettingsPage</name>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="21" />
<location filename="../qml/pages/SettingsPage.qml" line="24" />
<source>Settings</source>
<translation>Einstellungen</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="23" />
<location filename="../qml/pages/SettingsPage.qml" line="26" />
<source>Server</source>
<translation>Server</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="28" />
<location filename="../qml/pages/SettingsPage.qml" line="31" />
<source>Web UI address</source>
<translation>Web-UI-Adresse</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="37" />
<location filename="../qml/pages/SettingsPage.qml" line="40" />
<source>Sessions</source>
<translation>Sitzungen</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="43" />
<source>Profile</source>
<translation>Profil</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="68" />
<source>Voice</source>
<translation>Stimme</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="40" />
<location filename="../qml/pages/SettingsPage.qml" line="71" />
<source>Read-aloud voice</source>
<translation>Vorlese-Stimme</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="45" />
<location filename="../qml/pages/SettingsPage.qml" line="73" />
<location filename="../qml/pages/SettingsPage.qml" line="76" />
<location filename="../qml/pages/SettingsPage.qml" line="104" />
<source>(server default)</source>
<translation>(Server-Standard)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="61" />
<location filename="../qml/pages/SettingsPage.qml" line="92" />
<source>Voice presets (comma-separated)</source>
<translation>Stimm-Presets (durch Kommas getrennt)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="69" />
<location filename="../qml/pages/SettingsPage.qml" line="100" />
<source>TTS engine</source>
<translation>TTS-Engine</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="89" />
<location filename="../qml/pages/SettingsPage.qml" line="120" />
<source>Engine presets (comma-separated)</source>
<translation>Engine-Presets (durch Kommas getrennt)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="97" />
<location filename="../qml/pages/SettingsPage.qml" line="128" />
<source>Read replies aloud</source>
<translation>Antworten vorlesen</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="98" />
<location filename="../qml/pages/SettingsPage.qml" line="129" />
<source>Speak every completed reply automatically</source>
<translation>Jede fertige Antwort automatisch vorlesen</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="103" />
<location filename="../qml/pages/SettingsPage.qml" line="134" />
<source>Voice dialog</source>
<translation>Sprachdialog</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="106" />
<location filename="../qml/pages/SettingsPage.qml" line="137" />
<source>Send right after dictation</source>
<translation>Direkt nach dem Diktat senden</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="107" />
<location filename="../qml/pages/SettingsPage.qml" line="138" />
<source>Send the transcribed text automatically, without tapping Send</source>
<translation>Den transkribierten Text automatisch senden, ohne Senden zu drücken</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="113" />
<location filename="../qml/pages/SettingsPage.qml" line="144" />
<source>Auto-stop pause</source>
<translation>Auto-Stopp-Pause</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="119" />
<location filename="../qml/pages/SettingsPage.qml" line="150" />
<source>1.0 second</source>
<translation>1,0 Sekunden</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="120" />
<location filename="../qml/pages/SettingsPage.qml" line="151" />
<source>1.5 seconds</source>
<translation>1,5 Sekunden</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="121" />
<location filename="../qml/pages/SettingsPage.qml" line="152" />
<source>2.0 seconds</source>
<translation>2,0 Sekunden</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="122" />
<location filename="../qml/pages/SettingsPage.qml" line="153" />
<source>3.0 seconds</source>
<translation>3,0 Sekunden</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="127" />
<location filename="../qml/pages/SettingsPage.qml" line="158" />
<source>Mic sensitivity</source>
<translation>Mikrofon-Empfindlichkeit</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="131" />
<location filename="../qml/pages/SettingsPage.qml" line="162" />
<source>High (even soft voices)</source>
<translation>Hoch (auch leise Stimmen)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="132" />
<location filename="../qml/pages/SettingsPage.qml" line="163" />
<source>Medium</source>
<translation>Mittel</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="133" />
<location filename="../qml/pages/SettingsPage.qml" line="164" />
<source>Low (noisy rooms)</source>
<translation>Niedrig (laute Umgebung)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="137" />
<location filename="../qml/pages/SettingsPage.qml" line="168" />
<source>Account</source>
<translation>Konto</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="141" />
<location filename="../qml/pages/SettingsPage.qml" line="172" />
<source>Sign out</source>
<translation>Abmelden</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="145" />
<location filename="../qml/pages/SettingsPage.qml" line="176" />
<source>About</source>
<translation>Über</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="154" />
<location filename="../qml/pages/SettingsPage.qml" line="185" />
<source>Native client for the Hermes Web UI (hermes-webui): sessions, live streaming chat, server-side voice dictation and read-aloud.</source>
<translation>Nativer Client für die Hermes Web UI (hermes-webui): Sitzungen, Live-Streaming-Chat, server-seitiges Diktat und Vorlesen.</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="199" />
<source>Profile: %1</source>
<translation>Profil: %1</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="200" />
<source>Server active profile</source>
<translation>Aktives Serverprofil</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="224" />
<source>(server active)</source>
<translation>(aktives Serverprofil)</translation>
</message>
</context>
<context>
<name>VoiceRecorder</name>
+56 -31
View File
@@ -4,17 +4,17 @@
<context>
<name>ApiClient</name>
<message>
<location filename="../src/apiclient.cpp" line="68" />
<location filename="../src/apiclient.cpp" line="72" />
<source>Authentication required</source>
<translation>Authentification requise</translation>
</message>
<message>
<location filename="../src/apiclient.cpp" line="70" />
<location filename="../src/apiclient.cpp" line="74" />
<source>HTTP %1</source>
<translation>HTTP %1</translation>
</message>
<message>
<location filename="../src/apiclient.cpp" line="155" />
<location filename="../src/apiclient.cpp" line="159" />
<source>Server address not configured</source>
<translation>Adresse du serveur non configurée</translation>
</message>
@@ -264,141 +264,166 @@
<context>
<name>SettingsPage</name>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="21" />
<location filename="../qml/pages/SettingsPage.qml" line="24" />
<source>Settings</source>
<translation>Paramètres</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="23" />
<location filename="../qml/pages/SettingsPage.qml" line="26" />
<source>Server</source>
<translation>Serveur</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="28" />
<location filename="../qml/pages/SettingsPage.qml" line="31" />
<source>Web UI address</source>
<translation>Adresse de la Web UI</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="37" />
<location filename="../qml/pages/SettingsPage.qml" line="40" />
<source>Sessions</source>
<translation>Sessions</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="43" />
<source>Profile</source>
<translation>Profil</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="68" />
<source>Voice</source>
<translation>Voix</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="40" />
<location filename="../qml/pages/SettingsPage.qml" line="71" />
<source>Read-aloud voice</source>
<translation>Voix de lecture</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="45" />
<location filename="../qml/pages/SettingsPage.qml" line="73" />
<location filename="../qml/pages/SettingsPage.qml" line="76" />
<location filename="../qml/pages/SettingsPage.qml" line="104" />
<source>(server default)</source>
<translation>(défaut du serveur)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="61" />
<location filename="../qml/pages/SettingsPage.qml" line="92" />
<source>Voice presets (comma-separated)</source>
<translation>Préréglages de voix (séparés par des virgules)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="69" />
<location filename="../qml/pages/SettingsPage.qml" line="100" />
<source>TTS engine</source>
<translation>Moteur TTS</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="89" />
<location filename="../qml/pages/SettingsPage.qml" line="120" />
<source>Engine presets (comma-separated)</source>
<translation>Préréglages de moteurs (séparés par des virgules)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="97" />
<location filename="../qml/pages/SettingsPage.qml" line="128" />
<source>Read replies aloud</source>
<translation>Lire les réponses à voix haute</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="98" />
<location filename="../qml/pages/SettingsPage.qml" line="129" />
<source>Speak every completed reply automatically</source>
<translation>Prononcer automatiquement chaque réponse terminée</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="103" />
<location filename="../qml/pages/SettingsPage.qml" line="134" />
<source>Voice dialog</source>
<translation>Dialogue vocal</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="106" />
<location filename="../qml/pages/SettingsPage.qml" line="137" />
<source>Send right after dictation</source>
<translation>Envoyer juste après la dictée</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="107" />
<location filename="../qml/pages/SettingsPage.qml" line="138" />
<source>Send the transcribed text automatically, without tapping Send</source>
<translation>Envoyer automatiquement le texte transcrit, sans appuyer sur Envoyer</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="113" />
<location filename="../qml/pages/SettingsPage.qml" line="144" />
<source>Auto-stop pause</source>
<translation>Pause d'arrêt auto</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="119" />
<location filename="../qml/pages/SettingsPage.qml" line="150" />
<source>1.0 second</source>
<translation>1,0 seconde</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="120" />
<location filename="../qml/pages/SettingsPage.qml" line="151" />
<source>1.5 seconds</source>
<translation>1,5 seconde</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="121" />
<location filename="../qml/pages/SettingsPage.qml" line="152" />
<source>2.0 seconds</source>
<translation>2,0 secondes</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="122" />
<location filename="../qml/pages/SettingsPage.qml" line="153" />
<source>3.0 seconds</source>
<translation>3,0 secondes</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="127" />
<location filename="../qml/pages/SettingsPage.qml" line="158" />
<source>Mic sensitivity</source>
<translation>Sensibilité du micro</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="131" />
<location filename="../qml/pages/SettingsPage.qml" line="162" />
<source>High (even soft voices)</source>
<translation>Élevée (voix faibles)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="132" />
<location filename="../qml/pages/SettingsPage.qml" line="163" />
<source>Medium</source>
<translation>Moyenne</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="133" />
<location filename="../qml/pages/SettingsPage.qml" line="164" />
<source>Low (noisy rooms)</source>
<translation>Faible (pièces bruyantes)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="137" />
<location filename="../qml/pages/SettingsPage.qml" line="168" />
<source>Account</source>
<translation>Compte</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="141" />
<location filename="../qml/pages/SettingsPage.qml" line="172" />
<source>Sign out</source>
<translation>Se déconnecter</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="145" />
<location filename="../qml/pages/SettingsPage.qml" line="176" />
<source>About</source>
<translation>À propos</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="154" />
<location filename="../qml/pages/SettingsPage.qml" line="185" />
<source>Native client for the Hermes Web UI (hermes-webui): sessions, live streaming chat, server-side voice dictation and read-aloud.</source>
<translation>Client natif pour l'Hermes Web UI (hermes-webui) : sessions, chat en streaming, dictée et lecture à voix haute côté serveur.</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="199" />
<source>Profile: %1</source>
<translation>Profil : %1</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="200" />
<source>Server active profile</source>
<translation>Profil actif sur le serveur</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="224" />
<source>(server active)</source>
<translation>(actif sur le serveur)</translation>
</message>
</context>
<context>
<name>VoiceRecorder</name>
+56 -31
View File
@@ -4,17 +4,17 @@
<context>
<name>ApiClient</name>
<message>
<location filename="../src/apiclient.cpp" line="68" />
<location filename="../src/apiclient.cpp" line="72" />
<source>Authentication required</source>
<translation>Autenticazione richiesta</translation>
</message>
<message>
<location filename="../src/apiclient.cpp" line="70" />
<location filename="../src/apiclient.cpp" line="74" />
<source>HTTP %1</source>
<translation>HTTP %1</translation>
</message>
<message>
<location filename="../src/apiclient.cpp" line="155" />
<location filename="../src/apiclient.cpp" line="159" />
<source>Server address not configured</source>
<translation>Indirizzo del server non configurato</translation>
</message>
@@ -264,141 +264,166 @@
<context>
<name>SettingsPage</name>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="21" />
<location filename="../qml/pages/SettingsPage.qml" line="24" />
<source>Settings</source>
<translation>Impostazioni</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="23" />
<location filename="../qml/pages/SettingsPage.qml" line="26" />
<source>Server</source>
<translation>Server</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="28" />
<location filename="../qml/pages/SettingsPage.qml" line="31" />
<source>Web UI address</source>
<translation>Indirizzo Web UI</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="37" />
<location filename="../qml/pages/SettingsPage.qml" line="40" />
<source>Sessions</source>
<translation>Sessioni</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="43" />
<source>Profile</source>
<translation>Profilo</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="68" />
<source>Voice</source>
<translation>Voce</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="40" />
<location filename="../qml/pages/SettingsPage.qml" line="71" />
<source>Read-aloud voice</source>
<translation>Voce di lettura</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="45" />
<location filename="../qml/pages/SettingsPage.qml" line="73" />
<location filename="../qml/pages/SettingsPage.qml" line="76" />
<location filename="../qml/pages/SettingsPage.qml" line="104" />
<source>(server default)</source>
<translation>(default del server)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="61" />
<location filename="../qml/pages/SettingsPage.qml" line="92" />
<source>Voice presets (comma-separated)</source>
<translation>Preset voci (separati da virgola)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="69" />
<location filename="../qml/pages/SettingsPage.qml" line="100" />
<source>TTS engine</source>
<translation>Motore TTS</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="89" />
<location filename="../qml/pages/SettingsPage.qml" line="120" />
<source>Engine presets (comma-separated)</source>
<translation>Preset motori (separati da virgola)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="97" />
<location filename="../qml/pages/SettingsPage.qml" line="128" />
<source>Read replies aloud</source>
<translation>Leggi le risposte ad alta voce</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="98" />
<location filename="../qml/pages/SettingsPage.qml" line="129" />
<source>Speak every completed reply automatically</source>
<translation>Pronuncia automaticamente ogni risposta completata</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="103" />
<location filename="../qml/pages/SettingsPage.qml" line="134" />
<source>Voice dialog</source>
<translation>Dialogo vocale</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="106" />
<location filename="../qml/pages/SettingsPage.qml" line="137" />
<source>Send right after dictation</source>
<translation>Invia subito dopo la dettatura</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="107" />
<location filename="../qml/pages/SettingsPage.qml" line="138" />
<source>Send the transcribed text automatically, without tapping Send</source>
<translation>Invia il testo trascritto automaticamente, senza premere Invia</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="113" />
<location filename="../qml/pages/SettingsPage.qml" line="144" />
<source>Auto-stop pause</source>
<translation>Pausa di stop automatico</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="119" />
<location filename="../qml/pages/SettingsPage.qml" line="150" />
<source>1.0 second</source>
<translation>1,0 secondi</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="120" />
<location filename="../qml/pages/SettingsPage.qml" line="151" />
<source>1.5 seconds</source>
<translation>1,5 secondi</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="121" />
<location filename="../qml/pages/SettingsPage.qml" line="152" />
<source>2.0 seconds</source>
<translation>2,0 secondi</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="122" />
<location filename="../qml/pages/SettingsPage.qml" line="153" />
<source>3.0 seconds</source>
<translation>3,0 secondi</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="127" />
<location filename="../qml/pages/SettingsPage.qml" line="158" />
<source>Mic sensitivity</source>
<translation>Sensibilità microfono</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="131" />
<location filename="../qml/pages/SettingsPage.qml" line="162" />
<source>High (even soft voices)</source>
<translation>Alta (anche voci flebili)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="132" />
<location filename="../qml/pages/SettingsPage.qml" line="163" />
<source>Medium</source>
<translation>Media</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="133" />
<location filename="../qml/pages/SettingsPage.qml" line="164" />
<source>Low (noisy rooms)</source>
<translation>Bassa (ambienti rumorosi)</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="137" />
<location filename="../qml/pages/SettingsPage.qml" line="168" />
<source>Account</source>
<translation>Account</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="141" />
<location filename="../qml/pages/SettingsPage.qml" line="172" />
<source>Sign out</source>
<translation>Esci</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="145" />
<location filename="../qml/pages/SettingsPage.qml" line="176" />
<source>About</source>
<translation>Informazioni</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="154" />
<location filename="../qml/pages/SettingsPage.qml" line="185" />
<source>Native client for the Hermes Web UI (hermes-webui): sessions, live streaming chat, server-side voice dictation and read-aloud.</source>
<translation>Client nativo per l'Hermes Web UI (hermes-webui): sessioni, chat in streaming, dettatura e lettura ad alta voce lato server.</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="199" />
<source>Profile: %1</source>
<translation>Profilo: %1</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="200" />
<source>Server active profile</source>
<translation>Profilo attivo sul server</translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="224" />
<source>(server active)</source>
<translation>(attivo sul server)</translation>
</message>
</context>
<context>
<name>VoiceRecorder</name>