feat: add Cloudflare Pages Functions and wrangler configuration with Jev API key
This commit is contained in:
parent
6b8c5404bf
commit
e555a6bbd9
3 changed files with 223 additions and 0 deletions
202
functions/api/ask.js
Normal file
202
functions/api/ask.js
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
const DEFAULT_KEY = "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";
|
||||
const JEV_DECISIONS_URL = "https://openrouter.ai/api/alpha/decisions";
|
||||
const MODEL = "typesafe/jev-1.13";
|
||||
|
||||
const APHORISMS = {
|
||||
certain: { text: "It is certain.", sentiment: "affirmative" },
|
||||
decidedly: { text: "It is decidedly so.", sentiment: "affirmative" },
|
||||
without_doubt: { text: "Without a doubt.", sentiment: "affirmative" },
|
||||
yes_definitely: { text: "Yes definitely.", sentiment: "affirmative" },
|
||||
rely: { text: "You may rely on it.", sentiment: "affirmative" },
|
||||
as_i_see_it: { text: "As I see it, yes.", sentiment: "affirmative" },
|
||||
most_likely: { text: "Most likely.", sentiment: "affirmative" },
|
||||
outlook_good: { text: "Outlook good.", sentiment: "affirmative" },
|
||||
yes: { text: "Yes.", sentiment: "affirmative" },
|
||||
signs_yes: { text: "Signs point to yes.", sentiment: "affirmative" },
|
||||
|
||||
reply_hazy: { text: "Reply hazy, try again.", sentiment: "neutral" },
|
||||
ask_again: { text: "Ask again later.", sentiment: "neutral" },
|
||||
better_not: { text: "Better not tell you now.", sentiment: "neutral" },
|
||||
cannot_predict: { text: "Cannot predict now.", sentiment: "neutral" },
|
||||
concentrate: { text: "Concentrate and ask again.", sentiment: "neutral" },
|
||||
|
||||
dont_count: { text: "Don't count on it.", sentiment: "negative" },
|
||||
my_reply_no: { text: "My reply is no.", sentiment: "negative" },
|
||||
sources_no: { text: "My sources say no.", sentiment: "negative" },
|
||||
outlook_bad: { text: "Outlook not so good.", sentiment: "negative" },
|
||||
very_doubtful: { text: "Very doubtful.", sentiment: "negative" }
|
||||
};
|
||||
|
||||
export async function onRequestPost(context) {
|
||||
const { request, env } = context;
|
||||
const apiKey = env.OPENROUTER_API_KEY || DEFAULT_KEY;
|
||||
|
||||
let body;
|
||||
try {
|
||||
body = await request.json();
|
||||
} catch (e) {
|
||||
return new Response(JSON.stringify({ error: "Invalid JSON input." }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
|
||||
const question = body.question;
|
||||
if (!question || typeof question !== "string" || !question.trim()) {
|
||||
return new Response(JSON.stringify({ error: "Question is required." }), {
|
||||
status: 400,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
|
||||
const cleanQuestion = question.trim().slice(0, 300);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
model: MODEL,
|
||||
state: { question: cleanQuestion },
|
||||
questions: {
|
||||
favorable: {
|
||||
type: "noul",
|
||||
instructions: "Is the underlying outcome, recommendation, or premise of the user's question affirmative or likely to happen?",
|
||||
criteria: {
|
||||
true: "The outcome leans affirmative, positive, or advisable.",
|
||||
false: "The outcome leans doubtful, negative, or unadvisable."
|
||||
}
|
||||
},
|
||||
sentiment: {
|
||||
type: "choice",
|
||||
instructions: "Classify the overall orientation of the oracle's verdict.",
|
||||
criteria: {
|
||||
affirmative: "A clearly favorable, encouraging, or positive answer.",
|
||||
neutral: "Uncertain, ambiguous, hazy, or premature to determine.",
|
||||
negative: "An unfavorable, contrary, or discouraging answer."
|
||||
}
|
||||
},
|
||||
aphorism: {
|
||||
type: "choice",
|
||||
instructions: "Select the most accurate Magic 8-Ball statement to deliver.",
|
||||
criteria: {
|
||||
certain: "It is certain or without a doubt.",
|
||||
yes_definitely: "Yes definitely or decidedly so.",
|
||||
signs_yes: "Signs point to yes or most likely.",
|
||||
reply_hazy: "Reply hazy, try again.",
|
||||
cannot_predict: "Cannot predict now or ask again later.",
|
||||
my_reply_no: "My reply is no or outlook not so good.",
|
||||
very_doubtful: "Very doubtful or don't count on it."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const response = await fetch(JEV_DECISIONS_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
"HTTP-Referer": "https://github.com/oiupoyt/jev-8ball",
|
||||
"X-Title": "Jev 8-Ball Oracle (Cloudflare)"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const latency = Date.now() - startTime;
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
return new Response(JSON.stringify({
|
||||
error: "OpenRouter decision error",
|
||||
details: errText,
|
||||
fallback: fallbackDecision(cleanQuestion)
|
||||
}), {
|
||||
status: response.status,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const answers = data.answers || {};
|
||||
|
||||
const favorableNoul = answers.favorable?.noul ?? 0.5;
|
||||
const sentimentChoice = answers.sentiment?.choice || (favorableNoul > 0.6 ? "affirmative" : favorableNoul < 0.4 ? "negative" : "neutral");
|
||||
const aphorismKey = answers.aphorism?.choice;
|
||||
|
||||
let answerText = APHORISMS[aphorismKey]?.text;
|
||||
if (!answerText) {
|
||||
if (sentimentChoice === "affirmative") {
|
||||
answerText = favorableNoul > 0.8 ? "It is certain." : "Signs point to yes.";
|
||||
} else if (sentimentChoice === "negative") {
|
||||
answerText = favorableNoul < 0.2 ? "My reply is no." : "Outlook not so good.";
|
||||
} else {
|
||||
answerText = "Reply hazy, try again.";
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
question: cleanQuestion,
|
||||
answer: answerText,
|
||||
sentiment: sentimentChoice,
|
||||
noul: favorableNoul,
|
||||
confidence: answers.sentiment?.confidence ?? answers.aphorism?.confidence ?? 0.85,
|
||||
probabilities: answers.sentiment?.probabilities || {},
|
||||
aphorismKey: aphorismKey || "signs_yes",
|
||||
latency,
|
||||
cost: data.usage?.cost ?? 0,
|
||||
usage: data.usage || {},
|
||||
model: data.model || MODEL,
|
||||
raw: data
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(result), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
const latency = Date.now() - startTime;
|
||||
return new Response(JSON.stringify({
|
||||
question: cleanQuestion,
|
||||
...fallbackDecision(cleanQuestion),
|
||||
latency,
|
||||
offline: true
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function onRequestOptions() {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": "Content-Type"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fallbackDecision(question) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < question.length; i++) {
|
||||
hash = (hash << 5) - hash + question.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
const keys = Object.keys(APHORISMS);
|
||||
const selectedKey = keys[Math.abs(hash) % keys.length];
|
||||
const item = APHORISMS[selectedKey];
|
||||
return {
|
||||
answer: item.text,
|
||||
sentiment: item.sentiment,
|
||||
noul: item.sentiment === "affirmative" ? 0.78 : item.sentiment === "negative" ? 0.22 : 0.5,
|
||||
confidence: 0.8,
|
||||
probabilities: { [item.sentiment]: 0.8 },
|
||||
aphorismKey: selectedKey,
|
||||
model: "typesafe/jev-1.13 (offline-cache)"
|
||||
};
|
||||
}
|
||||
16
functions/api/status.js
Normal file
16
functions/api/status.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export async function onRequestGet(context) {
|
||||
const { env } = context;
|
||||
const hasKey = Boolean(env.OPENROUTER_API_KEY || true);
|
||||
return new Response(JSON.stringify({
|
||||
status: "online",
|
||||
model: "typesafe/jev-1.13",
|
||||
engine: "TypeSafe Jev (Cloudflare Pages Edge)",
|
||||
hasKey
|
||||
}), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
});
|
||||
}
|
||||
5
wrangler.json
Normal file
5
wrangler.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "jev-8ball",
|
||||
"compatibility_date": "2024-09-23",
|
||||
"pages_build_output_dir": "./public"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue