feat(chat): markdown formattato nella chat + testo pulito per la voce (v0.5.2)
- src/markdown.{h,cpp}: convertitore minimale, due funzioni:
* toRichText() -> HTML per QML Text.RichText: grassetti (**x**/__x__),
corsivi (*x*/_x_ con confini di parola per non toccare snake_case),
titoli (#), liste (-/1.), citazioni (>), codice (`x` e blocchi ```,
in monospace), link [t](u) -> solo testo, barrato ~~x~~; escape di
< & > contro injection nel rich text
* toSpeech() -> testo piatto per il TTS: via recinti di codice, marcatori
di titoli/liste/citazioni, enfasi, backtick; link/immagini -> testo;
tabelle | a | b | -> "a, b"; righe vuote compattate
- ChatModel: nuovo ruolo contentRich (calcolato in C++, testabile);
incluso nei dataChanged dello streaming
- ChatPage: le bolle dell'assistente usano contentRich con
textFormat: Text.RichText; i messaggi utente restano testo semplice
- ApiClient::speak(): il testo passa da Markdown::toSpeech (niente più
simboli di formattazione letti ad alta voce)
- core-test: 19 casi nuovi per il convertitore (tutti locali, senza device)
Verificato: build zero warning + unit test completi.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "apiclient.h"
|
||||
#include "chatmodel.h"
|
||||
#include "markdown.h"
|
||||
#include "settings.h"
|
||||
#include "sseparser.h"
|
||||
|
||||
@@ -78,6 +79,51 @@ static QString readFileTrim(const QString &path)
|
||||
return QString::fromUtf8(f.readAll()).trimmed();
|
||||
}
|
||||
|
||||
static void testMarkdown()
|
||||
{
|
||||
qDebug() << "== unit: markdown ==";
|
||||
|
||||
CHECK(Markdown::toRichText("**ciao**") == QLatin1String("<b>ciao</b>"),
|
||||
"grassetto -> <b>");
|
||||
CHECK(Markdown::toRichText("__ciao__") == QLatin1String("<b>ciao</b>"),
|
||||
"grassetto con underscore -> <b>");
|
||||
CHECK(Markdown::toRichText("*ciao*") == QLatin1String("<i>ciao</i>"),
|
||||
"corsivo -> <i>");
|
||||
CHECK(Markdown::toRichText("# Titolo") == QLatin1String("<b>Titolo</b>"),
|
||||
"titolo -> grassetto");
|
||||
CHECK(Markdown::toRichText("- uno\n- due") == QStringLiteral("• uno<br/>• due"),
|
||||
"lista puntata -> bullet");
|
||||
CHECK(Markdown::toRichText("1. uno\n2. due") == QLatin1String("1. uno<br/>2. due"),
|
||||
"lista numerata conservata");
|
||||
CHECK(Markdown::toRichText("`x = 1`").contains(QLatin1String("monospace")),
|
||||
"codice inline -> monospace");
|
||||
CHECK(Markdown::toRichText("[Hermes](https://x.example)") == QLatin1String("Hermes"),
|
||||
"link -> solo testo");
|
||||
CHECK(Markdown::toRichText("a < b & c") == QLatin1String("a < b & c"),
|
||||
"escape di < e &");
|
||||
CHECK(Markdown::toRichText("2 * 3 = 6") == QLatin1String("2 * 3 = 6"),
|
||||
"asterisco spaiato lasciato intatto");
|
||||
CHECK(Markdown::toRichText("> citazione") == QStringLiteral("▏ citazione"),
|
||||
"citazione con barretta");
|
||||
CHECK(Markdown::toRichText("```\nint x;\n```").contains(QLatin1String("int x;")),
|
||||
"blocco codice: contenuto mostrato");
|
||||
|
||||
CHECK(Markdown::toSpeech("**ciao** *a* `b`") == QLatin1String("ciao a b"),
|
||||
"speech: via enfasi e backtick");
|
||||
CHECK(Markdown::toSpeech("# Titolo\n- uno\n> due") == QLatin1String("Titolo\nuno\ndue"),
|
||||
"speech: titoli/liste/citazioni senza marcatori");
|
||||
CHECK(Markdown::toSpeech("[t](https://x) e ") == QLatin1String("t e img"),
|
||||
"speech: link e immagini -> testo");
|
||||
CHECK(Markdown::toSpeech("snake_case resta") == QLatin1String("snake_case resta"),
|
||||
"speech: underscore in identificatori intatti");
|
||||
CHECK(Markdown::toSpeech("| a | b |\n|---|---|\n| c | d |") == QLatin1String("a, b\nc, d"),
|
||||
"speech: tabella compattata");
|
||||
CHECK(Markdown::toSpeech("```\ncodice()\n```") == QLatin1String("codice()"),
|
||||
"speech: blocco codice senza recinti");
|
||||
CHECK(Markdown::toSpeech("**grassetto**\n\n\n\n*testo*") == QLatin1String("grassetto\n\ntesto"),
|
||||
"speech: righe vuote compattate");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
@@ -85,6 +131,7 @@ int main(int argc, char **argv)
|
||||
app.setApplicationName(QStringLiteral("hermes-test"));
|
||||
|
||||
testParser();
|
||||
testMarkdown();
|
||||
|
||||
if (!app.arguments().contains(QStringLiteral("--live"))) {
|
||||
qDebug() << (failures == 0 ? "UNIT: tutti i test passati" : "UNIT: FALLIMENTI");
|
||||
|
||||
Reference in New Issue
Block a user