add cross-platform reset scripts, portable server manager, and frontend scaffolding

This commit is contained in:
techjarves committed 2026-06-26 07:00:17 +05:30
1 parent 1d28391243
commit 349b9e167f
6 files changed
+258 -22

No files matched your search

+13 -1
View File
@@ -78,6 +78,18 @@ if (Test-Path $ttsModelsDir) {
Write-Host " >> Preserving TTS models in app/tts-models." -ForegroundColor Cyan
}
# Preserve generated image outputs
$outputsDir = Join-Path $appDir "outputs"
if (Test-Path $outputsDir) {
Write-Host " >> Preserving generated image outputs in app/outputs." -ForegroundColor Cyan
}
# Preserve chat history
$chatHistoryDir = Join-Path $appDir "chat-history"
if (Test-Path $chatHistoryDir) {
Write-Host " >> Preserving chat history in app/chat-history." -ForegroundColor Cyan
}
# Preserve transcriptions
$transcriptionsDir = Join-Path $appDir "transcriptions"
if (Test-Path $transcriptionsDir) {
@@ -125,7 +137,7 @@ if (Test-Path $lockFile) {
Write-Host ""
Write-Host " ============================================================" -ForegroundColor Green
Write-Host " Reset complete. Image models, text models, speech models, TTS models, transcripts, TTS outputs, OpenVINO models, and generated outputs were preserved." -ForegroundColor Green
Write-Host " Reset complete. Models, generated images, chat history, transcripts, TTS outputs, TTS cache, and OpenVINO models were preserved." -ForegroundColor Green
Write-Host " ============================================================" -ForegroundColor Green
Write-Host ""
Read-Host " Press Enter to close..."
+11 -1
View File
@@ -72,6 +72,16 @@ if [[ -d "$APP_DIR/tts-models" ]]; then
echo " >> Preserving TTS models in app/tts-models."
fi
# Preserve generated image outputs
if [[ -d "$APP_DIR/outputs" ]]; then
echo " >> Preserving generated image outputs in app/outputs."
fi
# Preserve chat history
if [[ -d "$APP_DIR/chat-history" ]]; then
echo " >> Preserving chat history in app/chat-history."
fi
# Preserve transcriptions
if [[ -d "$APP_DIR/transcriptions" ]]; then
echo " >> Preserving speech transcripts in app/transcriptions."
@@ -111,7 +121,7 @@ fi
echo ""
echo " ============================================================"
echo " Reset complete. Image models, text models, speech models, TTS models, transcripts, TTS outputs, OpenVINO models, and generated outputs were preserved."
echo " Reset complete. Models, generated images, chat history, transcripts, TTS outputs, TTS cache, and OpenVINO models were preserved."
echo " ============================================================"
echo ""
read -rp " Press Enter to close..."
+151
View File
@@ -94,6 +94,10 @@ const LLM_MODELS = path.join(ROOT, "app", "llm-models");
if (!fs.existsSync(LLM_MODELS)) {
fs.mkdirSync(LLM_MODELS, { recursive: true });
}
const CHAT_HISTORY = path.join(ROOT, "app", "chat-history");
if (!fs.existsSync(CHAT_HISTORY)) {
fs.mkdirSync(CHAT_HISTORY, { recursive: true });
}
const LLM_BACKEND_PATHS = {
winCuda: path.join(ROOT, "app", "llm-backend", "win", "cuda", "llama-server.exe"),
winVulkan: path.join(ROOT, "app", "llm-backend", "win", "vulkan", "llama-server.exe"),
@@ -2706,6 +2710,9 @@ function getSetupPaths() {
cpuBackend: BACKEND_PATHS.cpu,
models: MODELS,
outputs: OUTPUTS,
chatHistory: CHAT_HISTORY,
transcriptions: TRANSCRIPTIONS,
ttsOutputs: TTS_OUTPUTS,
};
} else if (osPlatform === "darwin") {
return {
@@ -2715,6 +2722,9 @@ function getSetupPaths() {
macBackend: BACKEND_PATHS.mac,
models: MODELS,
outputs: OUTPUTS,
chatHistory: CHAT_HISTORY,
transcriptions: TRANSCRIPTIONS,
ttsOutputs: TTS_OUTPUTS,
};
} else {
// Linux / WSL
@@ -2727,6 +2737,9 @@ function getSetupPaths() {
linuxRocmBackend: BACKEND_PATHS.linuxRocm,
models: MODELS,
outputs: OUTPUTS,
chatHistory: CHAT_HISTORY,
transcriptions: TRANSCRIPTIONS,
ttsOutputs: TTS_OUTPUTS,
};
}
}
@@ -2739,6 +2752,9 @@ async function getHealth() {
getPathInfo("Frontend build", paths.distIndex),
getDirInfo("Models folder", paths.models),
getDirInfo("Outputs folder", paths.outputs),
getDirInfo("Chat history folder", paths.chatHistory),
getDirInfo("Transcriptions folder", paths.transcriptions),
getDirInfo("TTS outputs folder", paths.ttsOutputs),
];
if (osPlatform === "win32") {
@@ -2811,6 +2827,16 @@ async function getHealth() {
totalBytes: getPathSize(OUTPUTS),
totalSize: formatBytes(getPathSize(OUTPUTS)),
},
chat: {
count: fs.existsSync(CHAT_HISTORY) ? fs.readdirSync(CHAT_HISTORY).filter((file) => file.toLowerCase().endsWith(".json")).length : 0,
totalBytes: getPathSize(CHAT_HISTORY),
totalSize: formatBytes(getPathSize(CHAT_HISTORY)),
},
speech: {
transcriptions: fs.existsSync(TRANSCRIPTIONS) ? fs.readdirSync(TRANSCRIPTIONS).filter((file) => file.toLowerCase().endsWith(".json")).length : 0,
totalBytes: getPathSize(TRANSCRIPTIONS),
totalSize: formatBytes(getPathSize(TRANSCRIPTIONS)),
},
tts: {
models: fs.existsSync(TTS_MODELS) ? fs.readdirSync(TTS_MODELS).filter((file) => file.toLowerCase().endsWith(".json")).length : 0,
outputs: fs.existsSync(TTS_OUTPUTS) ? fs.readdirSync(TTS_OUTPUTS).filter((file) => file.toLowerCase().endsWith(".json")).length : 0,
@@ -2827,6 +2853,8 @@ function addCleanupCandidate(candidates, id, targetPath, reason, options = {}) {
if (!options.allowUserData && (
pathInside(targetPath, MODELS) ||
pathInside(targetPath, OUTPUTS) ||
pathInside(targetPath, CHAT_HISTORY) ||
pathInside(targetPath, TRANSCRIPTIONS) ||
pathInside(targetPath, TTS_MODELS) ||
pathInside(targetPath, TTS_OUTPUTS)
)) return;
@@ -5402,6 +5430,101 @@ function safeOutputName(value) {
.slice(0, 120);
}
function sanitizeChatMessageForStorage(message) {
if (!message || typeof message !== "object") return null;
const role = String(message.role || "").trim();
if (!["system", "user", "assistant"].includes(role)) return null;
let content = message.content;
if (Array.isArray(content)) {
content = content.map((item) => {
if (!item || typeof item !== "object") return item;
if (item.type === "image_url") {
return { type: "text", text: "[Attached image omitted from saved chat history]" };
}
return item;
});
} else if (typeof content !== "string") {
content = content === undefined || content === null ? "" : String(content);
}
return {
...message,
role,
content,
};
}
function sanitizeChatConversationForStorage(conversation = {}) {
const now = Date.now();
const id = safeOutputName(conversation.id || `chat_${now}`) || `chat_${now}`;
const messages = Array.isArray(conversation.messages)
? conversation.messages.map(sanitizeChatMessageForStorage).filter(Boolean)
: [];
const timestamp = Number(conversation.timestamp) || now;
return {
id,
title: String(conversation.title || "Chat Session").slice(0, 160),
model: String(conversation.model || ""),
messages,
timestamp,
createdAt: conversation.createdAt || new Date(timestamp).toISOString(),
updatedAt: new Date().toISOString(),
};
}
function getChatConversationPath(id) {
const safeId = safeOutputName(id);
if (!safeId) throw new Error("A chat id is required.");
const filePath = path.join(CHAT_HISTORY, `${safeId}.json`);
if (!pathInside(filePath, CHAT_HISTORY)) {
throw new Error("Invalid chat id.");
}
return filePath;
}
function saveChatConversation(conversation = {}) {
const saved = sanitizeChatConversationForStorage(conversation);
const filePath = getChatConversationPath(saved.id);
fs.writeFileSync(filePath, JSON.stringify(saved, null, 2), "utf8");
return saved;
}
function listChatConversations() {
try {
return fs.readdirSync(CHAT_HISTORY)
.filter((file) => file.toLowerCase().endsWith(".json"))
.map((file) => {
try {
const filePath = path.join(CHAT_HISTORY, file);
if (!pathInside(filePath, CHAT_HISTORY)) return null;
const conversation = JSON.parse(fs.readFileSync(filePath, "utf8"));
const stat = fs.statSync(filePath);
return {
...conversation,
filename: file,
modifiedAt: stat.mtime.toISOString(),
sizeBytes: stat.size,
size: formatBytes(stat.size),
};
} catch (_) {
return null;
}
})
.filter(Boolean)
.sort((a, b) => Number(b.timestamp || 0) - Number(a.timestamp || 0));
} catch (_) {
return [];
}
}
function deleteChatConversation(id) {
const filePath = getChatConversationPath(id);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
return true;
}
return false;
}
function saveGeneratedOutput(imageDataUrl, metadata = {}) {
const match = String(imageDataUrl || "").match(/^data:(image\/(?:png|jpeg|jpg|webp));base64,(.+)$/);
if (!match) {
@@ -5699,6 +5822,34 @@ const server = http.createServer(async (req, res) => {
return json(res, 200, { ok: true, model: filename, settings: updated });
}
if (req.url === "/api/llm/conversations" && req.method === "GET") {
return json(res, 200, { ok: true, conversations: listChatConversations() });
}
if (req.url === "/api/llm/save-conversation" && req.method === "POST") {
const body = await readJsonBody(req, res);
if (!body) return;
try {
const conversation = saveChatConversation(body.conversation || body);
return json(res, 200, { ok: true, conversation });
} catch (err) {
console.error(" [api] Failed to save chat conversation:", err);
return json(res, 500, { ok: false, error: err.message || String(err) });
}
}
if (req.url === "/api/llm/delete-conversation" && req.method === "POST") {
const body = await readJsonBody(req, res);
if (!body) return;
try {
const deleted = deleteChatConversation(body.id);
return json(res, 200, { ok: true, deleted });
} catch (err) {
console.error(" [api] Failed to delete chat conversation:", err);
return json(res, 500, { ok: false, error: err.message || String(err) });
}
}
if (req.url === "/api/llm/models" && req.method === "GET") {
return json(res, 200, { models: getLlmModels() });
}