v1.0 - launch: full nav on gallery, chat widget auto-open, comparison logos, checkout fixes

This commit is contained in:
Will Anderson
2026-05-01 18:13:06 -05:00
parent ff1f9577db
commit 00f2323c98
41 changed files with 4740 additions and 434 deletions
+157 -59
View File
@@ -1895,12 +1895,28 @@ el_val_t json_get(el_val_t jsonv, el_val_t keyv) {
while (*p == ' ' || *p == '\t' || *p == '\n') p++;
if (*p == '"') {
p++;
const char* start = p;
while (*p && !(*p == '"' && *(p-1) != '\\')) p++;
size_t len = (size_t)(p - start);
char* out = el_strbuf(len);
memcpy(out, start, len);
out[len] = '\0';
/* Unescape the JSON string value into a clean buffer. */
size_t cap = strlen(p) + 1;
char* out = el_strbuf(cap);
char* w = out;
while (*p && *p != '"') {
if (*p == '\\' && *(p+1)) {
p++;
switch (*p) {
case '"': *w++ = '"'; break;
case '\\': *w++ = '\\'; break;
case '/': *w++ = '/'; break;
case 'n': *w++ = '\n'; break;
case 'r': *w++ = '\r'; break;
case 't': *w++ = '\t'; break;
default: *w++ = *p; break;
}
} else {
*w++ = *p;
}
p++;
}
*w = '\0';
return el_wrap_str(out);
}
const char* start = p;
@@ -4534,30 +4550,143 @@ static const char* llm_resolve_model(const char* m) {
return m;
}
/* Make an Anthropic /v1/messages request with the given JSON body. Returns
* the assistant's first text content as an owned string, or a JSON error
* fragment on transport failure. */
static el_val_t llm_request(const char* json_body) {
const char* api_key = getenv("ANTHROPIC_API_KEY");
if (!api_key || !*api_key) {
return http_error_json("ANTHROPIC_API_KEY not set");
/*
* ── Configurable LLM provider chain ──────────────────────────────────────────
*
* Providers are configured via indexed env vars. The runtime tries each in
* order (0, 1, 2, ...) and returns the first successful non-empty response.
*
* Per provider (N = 0, 1, 2, ...):
* NEURON_LLM_N_URL — endpoint URL (base URL; /v1/chat/completions appended
* if format is "openai" and not already in URL)
* NEURON_LLM_N_KEY — API key
* NEURON_LLM_N_FORMAT — "openai" (default) or "anthropic"
* NEURON_LLM_N_MODEL — model name override (optional)
*
* Example — Neuron inference primary, Anthropic fallback:
* NEURON_LLM_0_URL=https://soma.../v1/chat/completions
* NEURON_LLM_0_KEY=svc-key
* NEURON_LLM_0_FORMAT=openai
* NEURON_LLM_0_MODEL=neuron
* NEURON_LLM_1_URL=https://api.anthropic.com/v1/messages
* NEURON_LLM_1_KEY=sk-ant-...
* NEURON_LLM_1_FORMAT=anthropic
*
* If no NEURON_LLM_0_URL is set, falls back to legacy ANTHROPIC_API_KEY.
*/
#define LLM_MAX_PROVIDERS 16
/* forward declarations */
static el_val_t llm_extract_text(el_val_t resp_val);
static el_val_t llm_extract_text_openai(el_val_t resp_val);
static el_val_t llm_extract_text_openai(el_val_t resp_val) {
const char* resp = EL_CSTR(resp_val);
if (!resp || !*resp) return el_wrap_str(el_strdup(""));
if (resp[0] == '{' && strstr(resp, "\"error\"")) return el_wrap_str(el_strdup(""));
const char* choices = json_find_key(resp, "choices");
if (!choices || *choices != '[') return el_wrap_str(el_strdup(""));
choices++;
while (*choices == ' ' || *choices == '\t') choices++;
if (*choices != '{') return el_wrap_str(el_strdup(""));
const char* end = json_skip_value(choices);
size_t n = (size_t)(end - choices);
char* obj = malloc(n + 1); memcpy(obj, choices, n); obj[n] = '\0';
const char* msg = json_find_key(obj, "message");
if (!msg || *msg != '{') { free(obj); return el_wrap_str(el_strdup("")); }
const char* msg_end = json_skip_value(msg);
size_t mn = (size_t)(msg_end - msg);
char* msg_obj = malloc(mn + 1); memcpy(msg_obj, msg, mn); msg_obj[mn] = '\0';
const char* content = json_find_key(msg_obj, "content");
el_val_t result = el_wrap_str(el_strdup(""));
if (content && *content == '"') {
JsonParser jp = { .p = content, .end = content + strlen(content), .err = 0 };
char* text = jp_parse_string_raw(&jp);
if (!jp.err && text) result = el_wrap_str(text);
}
free(msg_obj); free(obj);
return result;
}
/* Send a request to one provider. Returns the raw response string.
* format: 0 = openai, 1 = anthropic */
static el_val_t llm_provider_request(const char* url, const char* key,
int format, const char* model,
const char* system_str,
const char* user_str) {
char* esc_sys = system_str && *system_str ? json_escape_alloc(system_str) : NULL;
char* esc_user = json_escape_alloc(user_str ? user_str : "");
JsonBuf b; jb_init(&b);
struct curl_slist* h = NULL;
h = curl_slist_append(h, "Content-Type: application/json");
{
size_t n = strlen(api_key) + 16;
char* line = malloc(n);
snprintf(line, n, "x-api-key: %s", api_key);
h = curl_slist_append(h, line);
free(line);
if (format == 0) { /* OpenAI */
char full_url[1024];
if (strstr(url, "/chat/completions") || strstr(url, "/messages")) {
snprintf(full_url, sizeof(full_url), "%s", url);
} else {
snprintf(full_url, sizeof(full_url), "%s/v1/chat/completions", url);
}
{ size_t n = strlen(key)+24; char* l=malloc(n); snprintf(l,n,"Authorization: Bearer %s",key); h=curl_slist_append(h,l); free(l); }
jb_putc(&b, '{');
jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, model ? model : "neuron");
jb_puts(&b, ",\"max_tokens\":4096,\"messages\":[");
if (esc_sys && *esc_sys) { jb_puts(&b,"{\"role\":\"system\",\"content\":\""); jb_puts(&b,esc_sys); jb_puts(&b,"\"},"); }
jb_puts(&b, "{\"role\":\"user\",\"content\":\""); jb_puts(&b, esc_user); jb_puts(&b, "\"}]}");
el_val_t resp = http_do("POST", full_url, b.buf, h);
curl_slist_free_all(h); free(b.buf);
if (esc_sys) free(esc_sys); free(esc_user);
return llm_extract_text_openai(resp);
} else { /* Anthropic */
{ size_t n = strlen(key)+16; char* l=malloc(n); snprintf(l,n,"x-api-key: %s",key); h=curl_slist_append(h,l); free(l); }
{ size_t n = strlen(LLM_VERSION)+32; char* l=malloc(n); snprintf(l,n,"anthropic-version: %s",LLM_VERSION); h=curl_slist_append(h,l); free(l); }
jb_putc(&b, '{');
jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, model ? model : LLM_DEFAULT_MODEL);
jb_puts(&b, ",\"max_tokens\":4096");
if (esc_sys && *esc_sys) { jb_puts(&b,",\"system\":\""); jb_puts(&b,esc_sys); jb_puts(&b,"\""); }
jb_puts(&b, ",\"messages\":[{\"role\":\"user\",\"content\":\""); jb_puts(&b, esc_user); jb_puts(&b, "\"}]}");
el_val_t resp = http_do("POST", url, b.buf, h);
curl_slist_free_all(h); free(b.buf);
if (esc_sys) free(esc_sys); free(esc_user);
return llm_extract_text(resp);
}
{
size_t n = strlen(LLM_VERSION) + 32;
char* line = malloc(n);
snprintf(line, n, "anthropic-version: %s", LLM_VERSION);
h = curl_slist_append(h, line);
free(line);
}
static el_val_t llm_chain_call(const char* system_str, const char* user_str) {
char url_key[64], key_key[64], fmt_key[64], model_key[64];
for (int i = 0; i < LLM_MAX_PROVIDERS; i++) {
snprintf(url_key, sizeof(url_key), "NEURON_LLM_%d_URL", i);
snprintf(key_key, sizeof(key_key), "NEURON_LLM_%d_KEY", i);
snprintf(fmt_key, sizeof(fmt_key), "NEURON_LLM_%d_FORMAT", i);
snprintf(model_key, sizeof(model_key), "NEURON_LLM_%d_MODEL", i);
const char* url = getenv(url_key);
const char* key = getenv(key_key);
if (!url || !*url || !key || !*key) break; /* end of chain */
const char* fmt_s = getenv(fmt_key);
int fmt = (fmt_s && strcmp(fmt_s, "anthropic") == 0) ? 1 : 0;
const char* model = getenv(model_key);
fprintf(stderr, "[llm] trying provider %d (%s)\n", i, url);
el_val_t result = llm_provider_request(url, key, fmt, model, system_str, user_str);
const char* t = EL_CSTR(result);
if (t && *t && t[0] != '{') return result; /* success */
fprintf(stderr, "[llm] provider %d failed or empty, trying next\n", i);
}
/* Legacy fallback: ANTHROPIC_API_KEY */
const char* api_key = getenv("ANTHROPIC_API_KEY");
if (!api_key || !*api_key) return http_error_json("no LLM providers configured");
fprintf(stderr, "[llm] using legacy ANTHROPIC_API_KEY fallback\n");
return llm_provider_request(LLM_API_URL, api_key, 1, NULL, system_str, user_str);
}
/* Legacy llm_request — kept for backward compat with agentic loop internals */
static el_val_t llm_request(const char* json_body) {
const char* api_key = getenv("ANTHROPIC_API_KEY");
if (!api_key || !*api_key) return http_error_json("ANTHROPIC_API_KEY not set");
struct curl_slist* h = NULL;
h = curl_slist_append(h, "Content-Type: application/json");
{ size_t n=strlen(api_key)+16; char* l=malloc(n); snprintf(l,n,"x-api-key: %s",api_key); h=curl_slist_append(h,l); free(l); }
{ size_t n=strlen(LLM_VERSION)+32; char* l=malloc(n); snprintf(l,n,"anthropic-version: %s",LLM_VERSION); h=curl_slist_append(h,l); free(l); }
el_val_t resp = http_do("POST", LLM_API_URL, json_body, h);
curl_slist_free_all(h);
return resp;
@@ -4611,45 +4740,14 @@ static el_val_t llm_extract_text(el_val_t resp_val) {
}
el_val_t llm_call(el_val_t model, el_val_t prompt) {
const char* m = llm_resolve_model(EL_CSTR(model));
const char* u = EL_CSTR(prompt);
if (!u) u = "";
char* esc_user = json_escape_alloc(u);
JsonBuf b; jb_init(&b);
jb_putc(&b, '{');
jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, m);
jb_puts(&b, ",\"max_tokens\":4096");
jb_puts(&b, ",\"messages\":[{\"role\":\"user\",\"content\":\"");
jb_puts(&b, esc_user);
jb_puts(&b, "\"}]}");
free(esc_user);
el_val_t resp = llm_request(b.buf);
free(b.buf);
return llm_extract_text(resp);
const char* u = EL_CSTR(prompt); if (!u) u = "";
return llm_chain_call(NULL, u);
}
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt) {
const char* m = llm_resolve_model(EL_CSTR(model));
const char* s = EL_CSTR(system_prompt); if (!s) s = "";
const char* u = EL_CSTR(user_prompt); if (!u) u = "";
char* esc_sys = json_escape_alloc(s);
char* esc_user = json_escape_alloc(u);
JsonBuf b; jb_init(&b);
jb_putc(&b, '{');
jb_puts(&b, "\"model\":"); jb_emit_escaped(&b, m);
jb_puts(&b, ",\"max_tokens\":4096");
if (*s) {
jb_puts(&b, ",\"system\":\"");
jb_puts(&b, esc_sys);
jb_puts(&b, "\"");
}
jb_puts(&b, ",\"messages\":[{\"role\":\"user\",\"content\":\"");
jb_puts(&b, esc_user);
jb_puts(&b, "\"}]}");
free(esc_sys); free(esc_user);
el_val_t resp = llm_request(b.buf);
free(b.buf);
return llm_extract_text(resp);
return llm_chain_call(s, u);
}
/* ── Tool registry for llm_call_agentic ─────────────────────────────────── */