Improve AI translation: skip Swedish/English, /trans toggle, better prompt
- All messages sent to LLM (not just non-Latin) - LLM detects language and returns SKIP for understood languages - /trans toggles echoing translations publicly to channel - PM nicks ordered most-recent-first for tab completion - tool_choice:none to fix vLLM/litellm 400 error - Updated README with /trans command
This commit is contained in:
@@ -77,6 +77,7 @@ static struct {
|
||||
|
||||
/* Query target (for /q) */
|
||||
static char query_target[64] = "";
|
||||
static int translate_public = 0; /* /trans toggle: echo translations to channel */
|
||||
|
||||
/* Track nicks who sent us private messages */
|
||||
#define MAX_PM_NICKS 32
|
||||
@@ -85,10 +86,22 @@ static int pm_nick_count = 0;
|
||||
|
||||
static void pm_nick_add(const char *n)
|
||||
{
|
||||
for (int i = 0; i < pm_nick_count; i++)
|
||||
if (strcasecmp(pm_nicks[i], n) == 0) return;
|
||||
/* Move to front if already present */
|
||||
for (int i = 0; i < pm_nick_count; i++) {
|
||||
if (strcasecmp(pm_nicks[i], n) == 0) {
|
||||
if (i == 0) return;
|
||||
char tmp[NICK_LEN];
|
||||
memcpy(tmp, pm_nicks[i], NICK_LEN);
|
||||
memmove(pm_nicks[1], pm_nicks[0], i * NICK_LEN);
|
||||
memcpy(pm_nicks[0], tmp, NICK_LEN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Insert at front */
|
||||
if (pm_nick_count < MAX_PM_NICKS)
|
||||
snprintf(pm_nicks[pm_nick_count++], NICK_LEN, "%s", n);
|
||||
pm_nick_count++;
|
||||
memmove(pm_nicks[1], pm_nicks[0], (pm_nick_count - 1) * NICK_LEN);
|
||||
snprintf(pm_nicks[0], NICK_LEN, "%s", n);
|
||||
}
|
||||
|
||||
/* AI translation config */
|
||||
@@ -109,6 +122,7 @@ static struct {
|
||||
int fd; /* read end of pipe from child */
|
||||
int level; /* window to display in */
|
||||
pid_t pid;
|
||||
char target[128]; /* channel/nick to echo translation to */
|
||||
} translate_pending[MAX_TRANSLATE];
|
||||
static int translate_count = 0;
|
||||
|
||||
@@ -173,30 +187,15 @@ static void ai_config_load(void)
|
||||
static int needs_translation(const char *text)
|
||||
{
|
||||
if (!ai_cfg.enabled) return 0;
|
||||
int non_latin_count = 0;
|
||||
for (const unsigned char *p = (const unsigned char *)text; *p; p++) {
|
||||
if (*p >= 0x80) {
|
||||
if ((*p & 0xE0) == 0xC0 && (p[1] & 0xC0) == 0x80) {
|
||||
unsigned int cp = ((*p & 0x1F) << 6) | (p[1] & 0x3F);
|
||||
if (cp > 0x024F) non_latin_count++;
|
||||
p++;
|
||||
} else if ((*p & 0xF0) == 0xE0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) {
|
||||
non_latin_count++;
|
||||
p += 2;
|
||||
} else if ((*p & 0xF8) == 0xF0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80 && (p[3] & 0xC0) == 0x80) {
|
||||
non_latin_count++;
|
||||
p += 3;
|
||||
} else {
|
||||
/* Not valid UTF-8 — skip ISO-8859-1 Latin chars (0xC0-0xFF) */
|
||||
if (*p < 0xC0) non_latin_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Require at least 3 non-Latin chars to avoid triggering on stray åäö */
|
||||
return non_latin_count >= 3;
|
||||
/* Skip very short messages (nicks, URLs, single words like "ok") */
|
||||
int words = 0;
|
||||
for (const char *p = text; *p; p++)
|
||||
if (*p == ' ') words++;
|
||||
if (words < 1 && strlen(text) < 6) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void translate_async(const char *text, int level)
|
||||
static void translate_async(const char *text, int level, const char *target)
|
||||
{
|
||||
if (translate_count >= MAX_TRANSLATE) return;
|
||||
|
||||
@@ -242,10 +241,10 @@ static void translate_async(const char *text, int level)
|
||||
char body[2048];
|
||||
snprintf(body, sizeof(body),
|
||||
"{\"model\":\"%s\",\"messages\":["
|
||||
"{\"role\":\"system\",\"content\":\"Translate to %s. Reply with only the translation, nothing else.\"},"
|
||||
"{\"role\":\"system\",\"content\":\"You are a translator for an IRC chat. The user understands %s. If the message is in any of those languages or a mix of them, respond with exactly SKIP. Only translate messages in other languages to %s. Respond with only the translation or SKIP.\"},"
|
||||
"{\"role\":\"user\",\"content\":\"%s\"}"
|
||||
"],\"stream\":false}",
|
||||
ai_cfg.model, ai_cfg.target_lang, escaped);
|
||||
"],\"stream\":false,\"tool_choice\":\"none\"}",
|
||||
ai_cfg.model, ai_cfg.skip_langs, ai_cfg.target_lang, escaped);
|
||||
|
||||
char req[4096];
|
||||
int rlen = snprintf(req, sizeof(req),
|
||||
@@ -305,6 +304,9 @@ static void translate_async(const char *text, int level)
|
||||
translate_pending[translate_count].fd = pipefd[0];
|
||||
translate_pending[translate_count].level = level;
|
||||
translate_pending[translate_count].pid = pid;
|
||||
snprintf(translate_pending[translate_count].target,
|
||||
sizeof(translate_pending[translate_count].target),
|
||||
"%s", target ? target : "");
|
||||
translate_count++;
|
||||
}
|
||||
|
||||
@@ -827,12 +829,12 @@ static void handle_line(char *line)
|
||||
wprintf(WL_MSG, "<%s> %s\n", sender, text);
|
||||
pm_nick_add(sender);
|
||||
if (needs_translation(text))
|
||||
translate_async(text, WL_MSG);
|
||||
translate_async(text, WL_MSG, NULL);
|
||||
} else {
|
||||
int lvl = chan_to_level(target);
|
||||
wprintf(lvl, "<%s> %s\n", sender, text);
|
||||
if (needs_translation(text))
|
||||
translate_async(text, lvl);
|
||||
translate_async(text, lvl, translate_public ? target : NULL);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(cmd, "NOTICE") == 0 && params) {
|
||||
@@ -1185,6 +1187,10 @@ static void handle_input(char *line)
|
||||
irc_send_raw("PRIVMSG %s :\x01" "ACTION %s\x01", query_target, slap);
|
||||
wprintf(WL_MSG, "* %s %s\n", nick, slap);
|
||||
}
|
||||
} else if (strcasecmp(cmd, "trans") == 0) {
|
||||
translate_public = !translate_public;
|
||||
wprintf(current_level, "* Translation echo %s\n",
|
||||
translate_public ? "ON" : "OFF");
|
||||
} else if (strcasecmp(cmd, "ctcp") == 0 && args) {
|
||||
char *target = args;
|
||||
char *ctcp_cmd = strchr(args, ' ');
|
||||
@@ -1467,9 +1473,23 @@ int main(int argc, char *argv[])
|
||||
waitpid(translate_pending[ti].pid, NULL, 0);
|
||||
if (n > 0) {
|
||||
tbuf[n] = '\0';
|
||||
/* Display in italic */
|
||||
wprintf(translate_pending[ti].level,
|
||||
" \033[3m%s\033[0m\n", tbuf);
|
||||
/* Skip if LLM says it's already in a skip language */
|
||||
if (strcasecmp(tbuf, "SKIP") == 0 ||
|
||||
strncasecmp(tbuf, "SKIP", 4) == 0) {
|
||||
/* do nothing */
|
||||
} else {
|
||||
wprintf(translate_pending[ti].level,
|
||||
" \033[3m%s\033[0m\n", tbuf);
|
||||
if (translate_pending[ti].target[0]) {
|
||||
char pfx[IRC_MAX];
|
||||
snprintf(pfx, sizeof(pfx), "PRIVMSG %s :",
|
||||
translate_pending[ti].target);
|
||||
irc_send_converted(pfx,
|
||||
(unsigned char *)tbuf, strlen(tbuf));
|
||||
wprintf(translate_pending[ti].level,
|
||||
"<%s> %s\n", nick, tbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
translate_count--;
|
||||
if (ti < translate_count)
|
||||
|
||||
Reference in New Issue
Block a user