Files
ligent/src/models.c
T
2026-10-04 13:50:33 +08:00

163 lines
4.6 KiB
C

/* models.c - GET {base_url}/models 与响应体解析 */
#include "models.h"
#include "http.h"
#include "i18n.h"
#include "json.h"
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MODELS_MAX 512 /* 再多就不是给人挑的了, 多半是服务端出了什么问题 */
static int already_have(char **list, size_t n, const char *id) {
for (size_t i = 0; i < n; i++)
if (strcmp(list[i], id) == 0)
return 1;
return 0;
}
char **models_parse(const char *json, size_t *out_n) {
if (out_n)
*out_n = 0;
if (!json || !*json)
return NULL;
JsonValue *root = json_parse(json, NULL, 0);
if (!root)
return NULL;
/* 标准形状是 {"data": [...]}; 也见过直接回一个裸数组的兼容实现 */
const JsonValue *arr = NULL;
if (root->type == JSON_ARRAY) {
arr = root;
} else {
JsonValue *data = json_object_get(root, "data");
if (data && data->type == JSON_ARRAY)
arr = data;
}
if (!arr) {
json_free(root);
return NULL;
}
size_t cap = json_array_size(arr) + 1;
if (cap > MODELS_MAX)
cap = MODELS_MAX;
char **list = xcalloc(cap ? cap : 1, sizeof *list);
size_t n = 0;
for (size_t i = 0; i < json_array_size(arr) && n < cap; i++) {
const JsonValue *item = json_array_get(arr, i);
/* 元素可能是字符串本身, 也可能是 {"id": "..."} */
const char *id = NULL;
if (item && item->type == JSON_STRING)
id = json_string_value(item);
else if (item)
id = json_get_string(item, "id");
if (!id || !*id)
continue;
if (already_have(list, n, id))
continue; /* 有些服务端会重复列 */
list[n++] = xstrdup(id);
}
json_free(root);
if (n == 0) {
free(list);
return NULL;
}
/* 保序: 服务端给的顺序是它自己的偏好, 我们不去替它排序 */
if (out_n)
*out_n = n;
return list;
}
int models_is_empty_list(const char *json) {
if (!json || !*json)
return 0;
JsonValue *root = json_parse(json, NULL, 0);
if (!root)
return 0;
const JsonValue *arr = NULL;
if (root->type == JSON_ARRAY) {
arr = root;
} else {
JsonValue *data = json_object_get(root, "data");
if (data && data->type == JSON_ARRAY)
arr = data;
}
int empty = (arr && json_array_size((JsonValue *)arr) == 0);
json_free(root);
return empty;
}
void models_free(char **list, size_t n) {
if (!list)
return;
for (size_t i = 0; i < n; i++)
free(list[i]);
free(list);
}
char **models_fetch(const LigentConfig *cfg, size_t *out_n, int verbose,
char *err, size_t errlen) {
if (out_n)
*out_n = 0;
if (err && errlen)
err[0] = '\0';
const char *bu = (cfg && cfg->base_url) ? cfg->base_url : "";
if (!*bu) {
if (err && errlen)
snprintf(err, errlen, "%s", t("models.err.no_base_url"));
return NULL;
}
size_t bl = strlen(bu);
while (bl > 0 && bu[bl - 1] == '/')
bl--;
char *url = xasprintf("%.*s/models", (int)bl, bu);
char *body = NULL;
long status = 0;
char buf[512];
/* 读列表是只读的, 值得重试; 用默认退避策略 */
HttpRetry rt = HTTP_RETRY_DEFAULT;
int rc = http_get(url, cfg->api_key, cfg->timeout, verbose, &rt, &body,
&status, NULL, buf, sizeof buf);
free(url);
char **list = NULL;
if (rc != 0) {
if (err && errlen)
snprintf(err, errlen, "%s",
buf[0] ? buf : t("models.err.request_failed"));
} else {
list = models_parse(body, out_n);
if (!list && err && errlen) {
if (models_is_empty_list(body))
snprintf(err, errlen, "%s",
tf("models.err.empty_list", "status", i18n_num(status),
NULL));
else if (body && *body) {
/* 只回显开头一段: 服务端偶尔回一大坨 HTML, 全塞进错误里没法读
*/
char head[121];
snprintf(head, sizeof head, "%.120s", body);
snprintf(err, errlen, "%s",
tf("models.err.unparsable", "status", i18n_num(status),
"body", head, NULL));
} else
snprintf(err, errlen, "%s",
tf("models.err.empty_body", "status", i18n_num(status),
NULL));
}
}
free(body);
return list;
}