v0.8.3: WebUI models, providers and keys; garbled thinking fixed

Streaming: Chinese reasoning showed up garbled in the WebUI. The agent
loop now feeds visible/thinking text in runs instead of byte by byte,
and the WebUI holds an incomplete UTF-8 tail until the next chunk
(cst_utf8_hold_feed), so every think_delta/assistant_delta is whole.

src/provider_switch.c: model list fetch/cache, key verify-and-save and
provider switching moved out of tui.c and shared by TUI and WebUI.

WebUI API:
- GET /api/providers, GET /api/models[?refresh=1] (401 on a rejected
  key, still listing built-ins), POST /api/provider, POST /api/key
  (key checked against /v1/models, saved to credentials 0600, never
  echoed in replies, events or logs)
- /api/config validates everything before applying, persists model,
  effort and approval (not yolo), warns when effort does not apply;
  plan_mode no longer forces read-only
- state: turn_usage, api_base, key_set/key_from, model_info,
  effort_applies, sees_images, max_steps, tool_profile, perm_timeout;
  permission events carry their timeout

Frontend: searchable model picker with refresh and manual entry,
provider picker and key dialog, effort/vision hints, toasts instead of
silent failures, Chinese error text, real protocol in the settings and
usage panels, approval countdown from the server's timeout.

Tests: utf8 hold unit test, scripts/e2e_webui_settings.py (fake
/v1/models, wrong key, switching, persistence, Chinese thinking) run
from e2e_webui.sh, scripts/tui_keys/provider.py.
This commit is contained in:
WHC
2026-10-09 15:54:53 +08:00
parent e238f5f37d
commit 2d1052fd5f
18 changed files with 1553 additions and 182 deletions
+20
View File
@@ -99,6 +99,26 @@ size_t cst_utf8_floor(const char *s, size_t n) {
return i - 1; /* sequence incomplete: cut before lead byte */
}
char *cst_utf8_hold_feed(cst_utf8_hold_t *h, const char *s, size_t n,
size_t *out_len) {
size_t have = h->n;
char *all = malloc(have + n + 1);
if (!all)
return NULL;
memcpy(all, h->b, have);
if (n)
memcpy(all + have, s, n);
size_t len = have + n;
size_t cut = cst_utf8_floor(all, len);
if (len - cut > sizeof(h->b))
cut = len;
h->n = len - cut;
memcpy(h->b, all + cut, h->n);
all[cut] = 0;
*out_len = cut;
return all;
}
static size_t utf8_seq_ok(const unsigned char *p, size_t left) {
unsigned char c = p[0];
size_t need;