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:
+24
-9
@@ -1282,13 +1282,22 @@ static void on_mm(const ctl_event_t *ev, void *ud) {
|
||||
}
|
||||
|
||||
static void feed_visible(turn_t *t, const char *data, size_t len) {
|
||||
if (!len || t->stop)
|
||||
return;
|
||||
cst_buf_append(&t->raw, data, len);
|
||||
if (t->ctl)
|
||||
ctl_parser_feed(t->ctl, data, len);
|
||||
else if (t->mm)
|
||||
mm_scan_feed(t->mm, data, len);
|
||||
/* cut after each '\n' and '>': a call only becomes ready there, and
|
||||
* raw_mark must not take in what follows it in the same chunk */
|
||||
while (len && !t->stop) {
|
||||
size_t n = 0;
|
||||
while (n < len && data[n] != '\n' && data[n] != '>')
|
||||
n++;
|
||||
if (n < len)
|
||||
n++;
|
||||
cst_buf_append(&t->raw, data, n);
|
||||
if (t->ctl)
|
||||
ctl_parser_feed(t->ctl, data, n);
|
||||
else if (t->mm)
|
||||
mm_scan_feed(t->mm, data, n);
|
||||
data += n;
|
||||
len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
static void think_out(turn_t *t, const char *s, size_t n) {
|
||||
@@ -1306,10 +1315,16 @@ static void on_delta(const char *data, size_t len, void *ud) {
|
||||
const char *want = t->in_think ? "</think>" : "<think>";
|
||||
size_t wl = strlen(want);
|
||||
if (t->taglen == 0 && c != '<') {
|
||||
/* the whole run up to the next '<' in one call: byte by byte, a
|
||||
* front end would get every CJK character in three invalid pieces */
|
||||
size_t j = i + 1;
|
||||
while (j < len && data[j] != '<')
|
||||
j++;
|
||||
if (t->in_think)
|
||||
think_out(t, &c, 1);
|
||||
think_out(t, data + i, j - i);
|
||||
else
|
||||
feed_visible(t, &c, 1);
|
||||
feed_visible(t, data + i, j - i);
|
||||
i = j - 1;
|
||||
continue;
|
||||
}
|
||||
t->tagbuf[t->taglen++] = c;
|
||||
|
||||
Reference in New Issue
Block a user