Upload app.js
Browse files
app.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const tools = [
|
| 2 |
+
{
|
| 3 |
+
type: "function",
|
| 4 |
+
function: {
|
| 5 |
+
name: "terminal",
|
| 6 |
+
description: "Run a shell command. This demo displays the request but does not execute it.",
|
| 7 |
+
parameters: {
|
| 8 |
+
type: "object",
|
| 9 |
+
properties: { command: { type: "string", description: "The command to run." } },
|
| 10 |
+
required: ["command"],
|
| 11 |
+
},
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
type: "function",
|
| 16 |
+
function: {
|
| 17 |
+
name: "read_file",
|
| 18 |
+
description: "Read a UTF-8 text file. This demo only displays the requested path.",
|
| 19 |
+
parameters: {
|
| 20 |
+
type: "object",
|
| 21 |
+
properties: { path: { type: "string", description: "Path of the file to read." } },
|
| 22 |
+
required: ["path"],
|
| 23 |
+
},
|
| 24 |
+
},
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
type: "function",
|
| 28 |
+
function: {
|
| 29 |
+
name: "web_search",
|
| 30 |
+
description: "Search the web. This demo displays the query but does not send it.",
|
| 31 |
+
parameters: {
|
| 32 |
+
type: "object",
|
| 33 |
+
properties: { query: { type: "string", description: "The search query." } },
|
| 34 |
+
required: ["query"],
|
| 35 |
+
},
|
| 36 |
+
},
|
| 37 |
+
},
|
| 38 |
+
];
|
| 39 |
+
|
| 40 |
+
const $ = (selector) => document.querySelector(selector);
|
| 41 |
+
const runButton = $("#run");
|
| 42 |
+
const status = $("#status");
|
| 43 |
+
const answer = $("#answer");
|
| 44 |
+
const calls = $("#calls");
|
| 45 |
+
const latency = $("#latency");
|
| 46 |
+
|
| 47 |
+
function parseValue(value) {
|
| 48 |
+
const trimmed = value.trim();
|
| 49 |
+
try { return JSON.parse(trimmed); } catch { return trimmed; }
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
function parseAtem(text) {
|
| 53 |
+
const parsed = [];
|
| 54 |
+
const invokes = text.matchAll(/<atem:invoke\s+name="([^"]+)">([\s\S]*?)<\/atem:invoke>/g);
|
| 55 |
+
for (const match of invokes) {
|
| 56 |
+
const argumentsObject = {};
|
| 57 |
+
for (const parameter of match[2].matchAll(/<atem:parameter\s+name="([^"]+)">([\s\S]*?)<\/atem:parameter>/g)) {
|
| 58 |
+
argumentsObject[parameter[1]] = parseValue(parameter[2]);
|
| 59 |
+
}
|
| 60 |
+
parsed.push({ name: match[1], arguments: argumentsObject });
|
| 61 |
+
}
|
| 62 |
+
return parsed;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
function normalizeCalls(message) {
|
| 66 |
+
if (Array.isArray(message.tool_calls) && message.tool_calls.length) {
|
| 67 |
+
return message.tool_calls.map((call) => {
|
| 68 |
+
let argumentsObject = call.function?.arguments ?? {};
|
| 69 |
+
if (typeof argumentsObject === "string") {
|
| 70 |
+
try { argumentsObject = JSON.parse(argumentsObject); } catch { argumentsObject = { raw: argumentsObject }; }
|
| 71 |
+
}
|
| 72 |
+
return { name: call.function?.name ?? "unknown", arguments: argumentsObject };
|
| 73 |
+
});
|
| 74 |
+
}
|
| 75 |
+
return parseAtem(message.content ?? "");
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
function visibleContent(content) {
|
| 79 |
+
if (!content) return "No user-facing answer was produced. See the proposed tool call below.";
|
| 80 |
+
const withoutCalls = content.replace(/<atem:function_calls>[\s\S]*?<\/atem:function_calls>/g, "");
|
| 81 |
+
const withoutProtocol = withoutCalls.replace(/<\|[^>]+\|>/g, "").replace(/^\s*to=[^<\n]+/, "").trim();
|
| 82 |
+
return withoutProtocol || "No user-facing answer was produced. See the proposed tool call below.";
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
async function generate() {
|
| 86 |
+
const endpoint = $("#endpoint").value.trim().replace(/\/+$/, "");
|
| 87 |
+
const prompt = $("#prompt").value.trim();
|
| 88 |
+
if (!endpoint || !prompt) {
|
| 89 |
+
status.textContent = "Enter both a public endpoint and a request.";
|
| 90 |
+
return;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
runButton.disabled = true;
|
| 94 |
+
status.textContent = "";
|
| 95 |
+
latency.textContent = "Generating…";
|
| 96 |
+
const started = performance.now();
|
| 97 |
+
try {
|
| 98 |
+
const response = await fetch(`${endpoint}/v1/chat/completions`, {
|
| 99 |
+
method: "POST",
|
| 100 |
+
headers: { "Content-Type": "application/json" },
|
| 101 |
+
body: JSON.stringify({
|
| 102 |
+
model: "muse-glimmer-30b",
|
| 103 |
+
messages: [
|
| 104 |
+
{
|
| 105 |
+
role: "system",
|
| 106 |
+
content: `You are a helpful AI assistant. Reasoning strength: ${$("#reasoning").value}. Use a tool only when necessary. Never claim a tool succeeded because this demo does not execute tools. Confirm destructive actions.`,
|
| 107 |
+
},
|
| 108 |
+
{ role: "user", content: prompt },
|
| 109 |
+
],
|
| 110 |
+
tools,
|
| 111 |
+
temperature: Number($("#temperature").value),
|
| 112 |
+
top_p: 0.95,
|
| 113 |
+
max_tokens: Number($("#maxTokens").value),
|
| 114 |
+
}),
|
| 115 |
+
});
|
| 116 |
+
const payload = await response.json().catch(() => ({}));
|
| 117 |
+
if (!response.ok) throw new Error(payload.error?.message || `Endpoint returned HTTP ${response.status}.`);
|
| 118 |
+
const message = payload.choices?.[0]?.message;
|
| 119 |
+
if (!message) throw new Error("The endpoint response did not contain choices[0].message.");
|
| 120 |
+
|
| 121 |
+
const proposed = normalizeCalls(message);
|
| 122 |
+
answer.textContent = visibleContent(message.content);
|
| 123 |
+
answer.classList.remove("empty");
|
| 124 |
+
calls.textContent = JSON.stringify(proposed, null, 2);
|
| 125 |
+
latency.textContent = `${((performance.now() - started) / 1000).toFixed(1)} s · ${payload.usage?.completion_tokens ?? "?"} tokens`;
|
| 126 |
+
} catch (error) {
|
| 127 |
+
status.textContent = `${error.message} Check endpoint reachability, HTTPS, and CORS.`;
|
| 128 |
+
latency.textContent = "Request failed";
|
| 129 |
+
} finally {
|
| 130 |
+
runButton.disabled = false;
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
runButton.addEventListener("click", generate);
|
| 135 |
+
$("#prompt").addEventListener("keydown", (event) => {
|
| 136 |
+
if ((event.ctrlKey || event.metaKey) && event.key === "Enter") generate();
|
| 137 |
+
});
|
| 138 |
+
document.querySelectorAll("[data-prompt]").forEach((button) => {
|
| 139 |
+
button.addEventListener("click", () => { $("#prompt").value = button.dataset.prompt; });
|
| 140 |
+
});
|