171 lines
5.3 KiB
JavaScript
171 lines
5.3 KiB
JavaScript
const { validatedRequest } = require("../../utils/middleware/validatedRequest");
|
|
const {
|
|
flexUserRoleValid,
|
|
ROLES,
|
|
} = require("../../utils/middleware/multiUserProtected");
|
|
const { reqBody } = require("../../utils/http");
|
|
const { safeJsonParse, decodeHtmlEntities } = require("../../utils/http");
|
|
const {
|
|
parseLemonadeServerEndpoint,
|
|
} = require("../../utils/AiProviders/lemonade");
|
|
|
|
function lemonadeUtilsEndpoints(app) {
|
|
if (!app) return;
|
|
|
|
app.post(
|
|
"/utils/lemonade/download-model",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { modelId, basePath = "" } = reqBody(request);
|
|
const lemonadeUrl = new URL(
|
|
parseLemonadeServerEndpoint(
|
|
basePath ?? process.env.LEMONADE_LLM_BASE_PATH,
|
|
"base"
|
|
)
|
|
);
|
|
lemonadeUrl.pathname += "api/v1/pull";
|
|
response.writeHead(200, {
|
|
"Content-Type": "text/event-stream",
|
|
"Cache-Control": "no-cache",
|
|
Connection: "keep-alive",
|
|
});
|
|
|
|
const lemonadeResponse = await fetch(lemonadeUrl.toString(), {
|
|
method: "POST",
|
|
headers: {
|
|
...(!!process.env.LEMONADE_LLM_API_KEY
|
|
? { Authorization: `Bearer ${process.env.LEMONADE_LLM_API_KEY}` }
|
|
: {}),
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model_name: String(modelId),
|
|
stream: true,
|
|
}),
|
|
});
|
|
if (!lemonadeResponse.ok)
|
|
throw new Error(
|
|
lemonadeResponse.statusText ||
|
|
"An error occurred while downloading the model"
|
|
);
|
|
const reader = lemonadeResponse.body.getReader();
|
|
let done = false;
|
|
let currentEvent = null;
|
|
|
|
while (!done) {
|
|
const { value, done: readerDone } = await reader.read();
|
|
if (readerDone) done = true;
|
|
|
|
const chunk = new TextDecoder("utf-8").decode(value);
|
|
const lines = chunk.split("\n");
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
|
|
if (line.startsWith("event:")) {
|
|
currentEvent = line.replace("event:", "").trim();
|
|
continue;
|
|
}
|
|
|
|
if (line.startsWith("data:")) {
|
|
const jsonStr = line.replace("data:", "").trim();
|
|
const decodedLine = decodeHtmlEntities(jsonStr);
|
|
const data = safeJsonParse(decodedLine);
|
|
if (!data) continue;
|
|
|
|
if (currentEvent === "error") {
|
|
throw new Error(
|
|
data.message ||
|
|
"An error occurred while downloading the model"
|
|
);
|
|
} else if (currentEvent === "complete") {
|
|
response.write(
|
|
`data: ${JSON.stringify({ type: "success", percentage: 100, message: "Model downloaded successfully" })}\n\n`
|
|
);
|
|
done = true;
|
|
} else if (currentEvent === "progress") {
|
|
const percentage = data.percent ?? 0;
|
|
const message = data.file
|
|
? `Downloading ${data.file}`
|
|
: "Downloading model...";
|
|
response.write(
|
|
`data: ${JSON.stringify({ type: "progress", percentage, message })}\n\n`
|
|
);
|
|
}
|
|
|
|
currentEvent = null;
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
response.write(
|
|
`data: ${JSON.stringify({ type: "error", message: e.message })}\n\n`
|
|
);
|
|
} finally {
|
|
response.end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/utils/lemonade/delete-model",
|
|
[validatedRequest, flexUserRoleValid([ROLES.admin])],
|
|
async (request, response) => {
|
|
try {
|
|
const { modelId, basePath = "" } = reqBody(request);
|
|
if (!modelId) {
|
|
return response.status(400).json({
|
|
success: false,
|
|
error: "modelId is required",
|
|
});
|
|
}
|
|
|
|
const lemonadeUrl = new URL(
|
|
parseLemonadeServerEndpoint(
|
|
basePath ?? process.env.LEMONADE_LLM_BASE_PATH,
|
|
"base"
|
|
)
|
|
);
|
|
lemonadeUrl.pathname += "api/v1/delete";
|
|
|
|
const lemonadeResponse = await fetch(lemonadeUrl.toString(), {
|
|
method: "POST",
|
|
headers: {
|
|
...(!!process.env.LEMONADE_LLM_API_KEY
|
|
? { Authorization: `Bearer ${process.env.LEMONADE_LLM_API_KEY}` }
|
|
: {}),
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model_name: String(modelId),
|
|
}),
|
|
});
|
|
|
|
const data = await lemonadeResponse.json();
|
|
if (!lemonadeResponse.ok || data.status === "error") {
|
|
return response.status(lemonadeResponse.status || 500).json({
|
|
success: false,
|
|
error: data.message || "An error occurred while deleting the model",
|
|
});
|
|
}
|
|
|
|
return response.status(200).json({
|
|
success: true,
|
|
message: data.message || `Deleted model: ${modelId}`,
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
return response.status(500).json({
|
|
success: false,
|
|
error: e.message || "An error occurred while deleting the model",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
module.exports = {
|
|
lemonadeUtilsEndpoints,
|
|
};
|