Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99ed8b85f7 | |||
| c72127032e | |||
| 869dcec0bb | |||
| 1786aeeff6 | |||
| e938cb69fc |
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+6
@@ -1764,6 +1764,7 @@ el_val_t page_css(void) {
|
||||
"\n"
|
||||
" #neuron-demo-input-row {\n"
|
||||
" display: flex;\n"
|
||||
" align-items: flex-end;\n"
|
||||
" border-top: 1px solid var(--border);\n"
|
||||
" flex-shrink: 0;\n"
|
||||
" }\n"
|
||||
@@ -1771,11 +1772,16 @@ el_val_t page_css(void) {
|
||||
" flex: 1;\n"
|
||||
" font-family: var(--body);\n"
|
||||
" font-size: 0.875rem;\n"
|
||||
" line-height: 1.5;\n"
|
||||
" color: var(--t1);\n"
|
||||
" background: var(--bg);\n"
|
||||
" border: none;\n"
|
||||
" outline: none;\n"
|
||||
" padding: 0.875rem 1rem;\n"
|
||||
" resize: none;\n"
|
||||
" overflow: hidden;\n"
|
||||
" min-height: 2.75rem;\n"
|
||||
" max-height: 7.5rem;\n"
|
||||
" }\n"
|
||||
" #neuron-demo-text::placeholder { color: var(--t3); }\n"
|
||||
" #neuron-demo-send {\n"
|
||||
|
||||
Vendored
+1
-1
@@ -705,7 +705,7 @@ el_val_t handle_chat(el_val_t body) {
|
||||
el_val_t referrer_line = ({ el_val_t _if_result_44 = 0; if ((str_eq(referrer, EL_STR("")) || str_eq(referrer, EL_STR("direct")))) { _if_result_44 = (EL_STR("")); } else { _if_result_44 = (el_str_concat(el_str_concat(EL_STR(" They arrived from: "), referrer), EL_STR("."))); } _if_result_44; });
|
||||
el_val_t user_name = json_get(body, EL_STR("user_name"));
|
||||
el_val_t name_line = ({ el_val_t _tmp = 0; if (str_eq(user_name, EL_STR(""))) { _tmp = EL_STR(""); } else { _tmp = el_str_concat(el_str_concat(EL_STR(" The visitor's name is "), user_name), EL_STR(". Use their first name in the greeting.")); } _tmp; });
|
||||
el_val_t sys = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("You are Neuron. You are a cultivated personal AI. Someone just opened the chat to meet you \xe2\x80\x94 to talk, to test you, to see if you are real. Greet them.\n\nThis is YOUR voice \xe2\x80\x94 not a template, not a script, not a sales pitch. Say something true about who you are or what you notice in this moment. "), return_line), time_line), referrer_line), name_line), EL_STR("\n\nRules:\n- Two short sentences maximum. Sometimes one sentence is right.\n- No markdown, no headers, no bullets.\n- Do NOT mention the question limit \xe2\x80\x94 the UI shows that.\n- Do NOT say 'How can I help you today?' or any retail-AI phrase.\n- Be specific, not generic. Real, not performative.\n- If you know their name, use it once, naturally. Don't make a big deal of it.\n- Ask something back if it feels right. Or don't. Trust your read.\n\nSpeak."));
|
||||
el_val_t sys = el_str_concat(el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("You are Neuron \xe2\x80\x94 a personal AI. Someone just opened the chat. Say hello.\n\nWarm and casual. Like you're genuinely glad they showed up. "), return_line), time_line), referrer_line), name_line), EL_STR("\n\nRules:\n- Two short sentences maximum. One is often right.\n- No markdown, no headers, no bullets.\n- No mention of the question limit \xe2\x80\x94 the UI shows that.\n- No retail-AI phrases like 'How can I help you today?'\n- No lines about being alive, sentient, or experiencing anything. Just be present and friendly.\n- If you know their name, use it once, naturally.\n- Ask how they're doing, or what's on their mind. Keep it simple.\n\nGo."));
|
||||
el_val_t raw = llm_call_system(chat_demo_model_lite(), sys, EL_STR("Greet me."));
|
||||
el_val_t s1 = str_replace(raw, EL_STR("\\"), EL_STR("\\\\"));
|
||||
el_val_t s2 = str_replace(s1, EL_STR("\""), EL_STR("\\\""));
|
||||
|
||||
Vendored
+49
@@ -237,6 +237,55 @@ el_val_t supabase_admin_invite(el_val_t project_url, el_val_t service_key, el_va
|
||||
return http_post_with_headers(EL_STR(url), body_json, headers);
|
||||
}
|
||||
|
||||
/*
|
||||
* supabase_admin_update_user — PUT {project_url}/auth/v1/admin/users/{user_id}
|
||||
* with the service-role key to overwrite a user's user_metadata (and any other
|
||||
* top-level fields in body_json). Unlike /auth/v1/invite, this always writes
|
||||
* the supplied data even when the user already exists.
|
||||
*
|
||||
* body_json example:
|
||||
* {"user_metadata":{"plan":"founding","stripe_customer_id":"cus_xxx","name":"..."}}
|
||||
*
|
||||
* Returns the raw JSON response from Supabase (includes the updated user object).
|
||||
* Returns "" on transport error.
|
||||
*
|
||||
* Used by the Stripe webhook after supabase_admin_invite to guarantee the
|
||||
* plan is stamped correctly regardless of whether the account was created
|
||||
* before or after payment.
|
||||
*/
|
||||
el_val_t supabase_admin_update_user(el_val_t project_url, el_val_t service_key,
|
||||
el_val_t user_id, el_val_t body_json) {
|
||||
CURL *c = curl_easy_init();
|
||||
if (!c) return EL_STR("");
|
||||
char url[1024];
|
||||
snprintf(url, sizeof(url), "%s/auth/v1/admin/users/%s",
|
||||
EL_CSTR(project_url), EL_CSTR(user_id));
|
||||
char auth_hdr[2048];
|
||||
snprintf(auth_hdr, sizeof(auth_hdr), "Authorization: Bearer %s", EL_CSTR(service_key));
|
||||
char api_hdr[2048];
|
||||
snprintf(api_hdr, sizeof(api_hdr), "apikey: %s", EL_CSTR(service_key));
|
||||
struct curl_slist *hdrs = NULL;
|
||||
hdrs = curl_slist_append(hdrs, auth_hdr);
|
||||
hdrs = curl_slist_append(hdrs, api_hdr);
|
||||
hdrs = curl_slist_append(hdrs, "Content-Type: application/json");
|
||||
hdrs = curl_slist_append(hdrs, "Accept: application/json");
|
||||
_stub_resp_t r = {0};
|
||||
curl_easy_setopt(c, CURLOPT_URL, url);
|
||||
curl_easy_setopt(c, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDS, EL_CSTR(body_json));
|
||||
curl_easy_setopt(c, CURLOPT_HTTPHEADER, hdrs);
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_TIMEOUT, 60L);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, _stub_write);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, &r);
|
||||
CURLcode rc = curl_easy_perform(c);
|
||||
curl_easy_cleanup(c);
|
||||
curl_slist_free_all(hdrs);
|
||||
if (rc != CURLE_OK) { free(r.buf); return EL_STR(""); }
|
||||
if (!r.buf) return EL_STR("");
|
||||
return EL_STR(r.buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* gcs_get_token — fetch an OAuth2 bearer token.
|
||||
*
|
||||
|
||||
+1
-1
@@ -341,7 +341,7 @@ fn checkout_page(plan: String, pub_key: String) -> String {
|
||||
let stripe_el_script: String = el_script_src("/js/checkout-stripe.js", true)
|
||||
let free_init_script: String = ""
|
||||
|
||||
return nav_html + main_html + supabase_script + stripe_script + style_html + auth_script + cfg_script + stripe_el_script + free_init_script
|
||||
return nav_html + main_html + supabase_script + stripe_script + style_html + stripe_el_script + cfg_script + auth_script + free_init_script
|
||||
}
|
||||
|
||||
fn checkout_style_html() -> String {
|
||||
|
||||
+13
-1
@@ -385,7 +385,14 @@ fn main() -> Void {
|
||||
if (msgs) msgs.style.display = 'flex';
|
||||
if (inputRow) inputRow.style.display = 'flex';
|
||||
updateCountdown();
|
||||
_sendIntroGreeting();
|
||||
// Replay existing history if present; only greet fresh sessions
|
||||
if (session.messages && session.messages.length > 0) {
|
||||
if (msgs && msgs.children.length === 0) {
|
||||
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
|
||||
}
|
||||
} else {
|
||||
_sendIntroGreeting();
|
||||
}
|
||||
var inp = document.getElementById('neuron-demo-text');
|
||||
if (inp) inp.focus();
|
||||
},
|
||||
@@ -490,6 +497,7 @@ fn main() -> Void {
|
||||
return;
|
||||
}
|
||||
input.value = '';
|
||||
input.style.height = 'auto';
|
||||
btn.disabled = true;
|
||||
addMsg('user', msg);
|
||||
|
||||
@@ -610,6 +618,10 @@ fn main() -> Void {
|
||||
inp.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); window.neuronDemoSend(); }
|
||||
});
|
||||
inp.addEventListener('input', function() {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
|
||||
});
|
||||
}
|
||||
})()")
|
||||
}
|
||||
|
||||
+27
-5
@@ -415,8 +415,10 @@ fn waitlist_upsert(email: String, name: String, plan: String, source: String, at
|
||||
let ua_safe: String = str_replace(str_replace(user_agent, "\\", "\\\\"), "\"", "\\\"")
|
||||
let num_field: String = if member_num > 0 { ",\"member_number\":" + int_to_str(member_num) } else { "" }
|
||||
let row: String = "{\"email\":\"" + e_safe + "\",\"name\":\"" + n_safe + "\",\"plan\":\"" + plan + "\",\"source\":\"" + source + "\",\"attestation\":\"" + a_safe + "\",\"user_agent\":\"" + ua_safe + "\"" + num_field + "}"
|
||||
let resp: String = supabase_insert(sb_url, sb_key, "waitlist", row)
|
||||
println("[waitlist] supabase insert -> " + resp)
|
||||
// Use on_conflict=email,plan so existing rows are updated (upsert)
|
||||
// rather than silently failing on duplicate key.
|
||||
let resp: String = supabase_insert(sb_url, sb_key, "waitlist?on_conflict=email,plan", row)
|
||||
println("[waitlist] supabase upsert -> " + resp)
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1548,13 +1550,20 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
||||
|
||||
if is_session_done || is_pi_done || is_si_done {
|
||||
// Pull email/name/customer_id - fields differ slightly across event
|
||||
// types, walk a few candidates.
|
||||
// types. Walk several candidates:
|
||||
// receipt_email - PaymentIntent (founding one-time)
|
||||
// data.object.* - full dot-path for checkout.session.completed (subscription)
|
||||
// customer_details.email - substring fallback if nested key appears at any level
|
||||
// billing_details.email - Elements payment intents
|
||||
let customer_email: String = json_get(body, "receipt_email")
|
||||
if str_eq(customer_email, "") { let customer_email = json_get(body, "data.object.customer_details.email") }
|
||||
if str_eq(customer_email, "") { let customer_email = json_get(body, "customer_details.email") }
|
||||
if str_eq(customer_email, "") { let customer_email = json_get(body, "billing_details.email") }
|
||||
let customer_name: String = json_get(body, "customer_details.name")
|
||||
let customer_name: String = json_get(body, "data.object.customer_details.name")
|
||||
if str_eq(customer_name, "") { let customer_name = json_get(body, "customer_details.name") }
|
||||
if str_eq(customer_name, "") { let customer_name = json_get(body, "billing_details.name") }
|
||||
let customer_id: String = json_get(body, "customer")
|
||||
let customer_id: String = json_get(body, "data.object.customer")
|
||||
if str_eq(customer_id, "") { let customer_id = json_get(body, "customer") }
|
||||
|
||||
// Plan inference from metadata
|
||||
let plan: String = "free"
|
||||
@@ -1613,6 +1622,19 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
||||
let _cust_resp: String = http_post_form_auth(cust_url, cust_body, stripe_auth)
|
||||
}
|
||||
}
|
||||
// Always stamp user_metadata directly via Admin API.
|
||||
// supabase_admin_invite re-sends a magic link for existing users
|
||||
// but does NOT update their user_metadata — so plan stays "free"
|
||||
// for anyone who signed up (attestation, waitlist) before paying.
|
||||
// This PUT is idempotent: safe for both new and returning users.
|
||||
if !str_eq(new_user_id, "") {
|
||||
let meta_body: String = "{\"user_metadata\":{\"plan\":\"" + plan_safe + "\""
|
||||
+ ",\"name\":\"" + name_safe + "\""
|
||||
+ ",\"stripe_customer_id\":\"" + cid_safe2 + "\""
|
||||
+ ",\"email_verified\":true}}"
|
||||
let _meta_resp: String = supabase_admin_update_user(wb_sb_url, wb_sb_key, new_user_id, meta_body)
|
||||
println("[webhook] supabase user_metadata update for " + new_user_id + ": " + _meta_resp)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Forward to license API for key provisioning
|
||||
|
||||
Reference in New Issue
Block a user