Implement a large number of changes, including overhauled styling and a settings page
This commit is contained in:
+108
-176
@@ -1,114 +1,117 @@
|
||||
const marketPersonaList = document.getElementById("marketPersonaList");
|
||||
const marketUserPersonaList = document.getElementById("marketUserPersonaList");
|
||||
const refreshMarketBtn = document.getElementById("refreshMarket");
|
||||
const marketStatus = document.getElementById("marketStatus");
|
||||
const marketSearchInput = document.getElementById("marketSearch");
|
||||
const marketPreviewModal = document.getElementById("marketPreviewModal");
|
||||
const marketPreviewName = document.getElementById("marketPreviewName");
|
||||
const marketPreviewMeta = document.getElementById("marketPreviewMeta");
|
||||
const marketPreviewPronouns = document.getElementById("marketPreviewPronouns");
|
||||
const marketPreviewAppearance = document.getElementById("marketPreviewAppearance");
|
||||
const marketPreviewBackground = document.getElementById("marketPreviewBackground");
|
||||
const marketPreviewDetails = document.getElementById("marketPreviewDetails");
|
||||
const marketPreviewConfirm = document.getElementById("marketPreviewConfirm");
|
||||
const marketPreviewCancel = document.getElementById("marketPreviewCancel");
|
||||
const marketPreviewClose = document.getElementById("marketPreviewClose");
|
||||
let allMarketPersonas = [];
|
||||
let pendingMarketPersona = null;
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const marketPersonaList = $("marketPersonaList"), marketUserPersonaList = $("marketUserPersonaList");
|
||||
const refreshMarketBtn = $("refreshMarket"), marketStatus = $("marketStatus"), marketSearchInput = $("marketSearch");
|
||||
const marketPreviewModal = $("marketPreviewModal"), marketPreviewName = $("marketPreviewName"), marketPreviewMeta = $("marketPreviewMeta");
|
||||
const marketPreviewPronouns = $("marketPreviewPronouns"), marketPreviewAppearance = $("marketPreviewAppearance"), marketPreviewBackground = $("marketPreviewBackground"), marketPreviewDetails = $("marketPreviewDetails");
|
||||
const marketPreviewConfirm = $("marketPreviewConfirm"), marketPreviewCancel = $("marketPreviewCancel"), marketPreviewClose = $("marketPreviewClose");
|
||||
const themeNameTargets = document.querySelectorAll("[data-theme-name]");
|
||||
let allMarketPersonas = [], pendingMarketPersona = null;
|
||||
|
||||
const themes = {
|
||||
"fakegpt": {name: "FakeGPT"},
|
||||
"fraud": {name: "Fraud"},
|
||||
"germini": {name: "Germini"},
|
||||
"slopilot": {name: "Slopilot"},
|
||||
"beta-ai": {name: "Beta AI"},
|
||||
"confusity": {name: "Confusity"}
|
||||
};
|
||||
|
||||
async function request(url, data, method = "POST") {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: data ? JSON.stringify(data) : undefined
|
||||
});
|
||||
const res = await fetch(url, {method, headers: {"Content-Type": "application/json"}, body: data ? JSON.stringify(data) : undefined});
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
return {error: "Network error — please try again."};
|
||||
} catch {
|
||||
return {error: "Network error - please try again."};
|
||||
}
|
||||
}
|
||||
|
||||
async function post(url, data) {
|
||||
return request(url, data, "POST");
|
||||
}
|
||||
const get = (url) => request(url, null, "GET");
|
||||
const post = (url, data) => request(url, data, "POST");
|
||||
|
||||
async function get(url) {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
return {error: "Network error — please try again."};
|
||||
}
|
||||
function applyTheme(themeKey) {
|
||||
const nextTheme = themes[themeKey] ? themeKey : "fakegpt";
|
||||
document.body.dataset.theme = nextTheme;
|
||||
document.title = `${themes[nextTheme].name} Market`;
|
||||
themeNameTargets.forEach((target) => { target.textContent = themes[nextTheme].name; });
|
||||
}
|
||||
|
||||
function setMarketStatus(message, state = "") {
|
||||
if (!marketStatus) return;
|
||||
marketStatus.textContent = message;
|
||||
marketStatus.className = state ? `status persona-status ${state}` : "status persona-status";
|
||||
}
|
||||
|
||||
function buildPersonaMeta(persona) {
|
||||
const pronouns = persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: —";
|
||||
const usageCount = Number(persona.usage_count || 0);
|
||||
const usageLabel = `Used ${usageCount} ${usageCount === 1 ? "time" : "times"}`;
|
||||
return `By ${persona.creator_username} • ${pronouns} • ${usageLabel}`;
|
||||
}
|
||||
|
||||
function renderMarketList(personaItems, listElement, personaType) {
|
||||
if (!listElement) return;
|
||||
listElement.innerHTML = "";
|
||||
if (!personaItems.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "status persona-status";
|
||||
empty.textContent = personaType === "assistant"
|
||||
? "No AI personas listed yet. Publish the first one!"
|
||||
: "No user personas listed yet. Publish the first one!";
|
||||
listElement.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
personaItems.forEach(persona => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "persona-item";
|
||||
|
||||
const titleRow = document.createElement("div");
|
||||
titleRow.className = "persona-title-row";
|
||||
|
||||
const title = document.createElement("h4");
|
||||
title.textContent = persona.name;
|
||||
|
||||
const roleTag = document.createElement("span");
|
||||
roleTag.className = `persona-role-tag ${personaType === "assistant" ? "ai" : "user"}`;
|
||||
roleTag.textContent = personaType === "assistant" ? "AI" : "You";
|
||||
|
||||
titleRow.appendChild(title);
|
||||
titleRow.appendChild(roleTag);
|
||||
|
||||
const meta = document.createElement("p");
|
||||
meta.textContent = buildPersonaMeta(persona);
|
||||
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "persona-item-actions";
|
||||
|
||||
const getBtn = document.createElement("button");
|
||||
getBtn.type = "button";
|
||||
getBtn.textContent = "View details";
|
||||
getBtn.addEventListener("click", () => openMarketPreview(persona));
|
||||
|
||||
actions.appendChild(getBtn);
|
||||
item.appendChild(titleRow);
|
||||
item.appendChild(meta);
|
||||
item.appendChild(actions);
|
||||
listElement.appendChild(item);
|
||||
});
|
||||
const pronouns = persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: n/a";
|
||||
return `${persona.creator_username} | ${pronouns}`;
|
||||
}
|
||||
|
||||
function formatPersonaField(value) {
|
||||
return value && value.trim() ? value.trim() : "Not provided.";
|
||||
}
|
||||
|
||||
function closeMarketPreview() {
|
||||
marketPreviewModal.classList.add("hidden");
|
||||
pendingMarketPersona = null;
|
||||
}
|
||||
|
||||
function renderMarketList(items, listElement, type) {
|
||||
listElement.innerHTML = "";
|
||||
if (!items.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "status persona-status";
|
||||
empty.textContent = type === "assistant" ? "No AI personas found." : "No user personas found.";
|
||||
listElement.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach((persona) => {
|
||||
const item = document.createElement("div");
|
||||
const titleRow = document.createElement("div");
|
||||
const title = document.createElement("h4");
|
||||
const tag = document.createElement("span");
|
||||
const meta = document.createElement("p");
|
||||
const actions = document.createElement("div");
|
||||
const detailsBtn = document.createElement("button");
|
||||
|
||||
item.className = "persona-item";
|
||||
titleRow.className = "persona-title-row";
|
||||
actions.className = "persona-item-actions";
|
||||
tag.className = `persona-role-tag ${type === "assistant" ? "ai" : "user"}`;
|
||||
tag.textContent = type === "assistant" ? "AI" : "You";
|
||||
title.textContent = persona.name;
|
||||
meta.textContent = buildPersonaMeta(persona);
|
||||
detailsBtn.type = "button";
|
||||
detailsBtn.textContent = "View";
|
||||
detailsBtn.addEventListener("click", () => openMarketPreview(persona));
|
||||
|
||||
titleRow.append(title, tag);
|
||||
actions.appendChild(detailsBtn);
|
||||
item.append(titleRow, meta, actions);
|
||||
listElement.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function filterMarketPersonas() {
|
||||
const query = marketSearchInput.value.trim().toLowerCase();
|
||||
if (!query) return allMarketPersonas;
|
||||
return allMarketPersonas.filter((persona) =>
|
||||
[persona.name, persona.creator_username, persona.pronouns, persona.appearance, persona.background, persona.details]
|
||||
.some((field) => field && field.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
||||
function applyMarketFilter() {
|
||||
const filtered = filterMarketPersonas();
|
||||
renderMarketList(filtered.filter((persona) => persona.persona_type === "assistant"), marketPersonaList, "assistant");
|
||||
renderMarketList(filtered.filter((persona) => persona.persona_type === "user"), marketUserPersonaList, "user");
|
||||
if (marketSearchInput.value.trim() && !filtered.length) {
|
||||
setMarketStatus("No personas match your search.", "error");
|
||||
return;
|
||||
}
|
||||
setMarketStatus(`${filtered.length} persona${filtered.length === 1 ? "" : "s"} shown.`);
|
||||
}
|
||||
|
||||
function openMarketPreview(persona) {
|
||||
if (!marketPreviewModal) return;
|
||||
pendingMarketPersona = persona;
|
||||
marketPreviewName.textContent = persona.name;
|
||||
marketPreviewMeta.textContent = buildPersonaMeta(persona);
|
||||
@@ -116,110 +119,39 @@ function openMarketPreview(persona) {
|
||||
marketPreviewAppearance.textContent = formatPersonaField(persona.appearance);
|
||||
marketPreviewBackground.textContent = formatPersonaField(persona.background);
|
||||
marketPreviewDetails.textContent = formatPersonaField(persona.details);
|
||||
marketPreviewConfirm.textContent =
|
||||
persona.persona_type === "assistant" ? "Get & equip as AI" : "Get & equip as You";
|
||||
marketPreviewConfirm.textContent = persona.persona_type === "assistant" ? "Collect as AI persona" : "Collect as user persona";
|
||||
marketPreviewModal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function closeMarketPreview() {
|
||||
if (!marketPreviewModal) return;
|
||||
marketPreviewModal.classList.add("hidden");
|
||||
pendingMarketPersona = null;
|
||||
}
|
||||
|
||||
function filterMarketPersonas() {
|
||||
const query = marketSearchInput?.value.trim().toLowerCase() || "";
|
||||
if (!query) return allMarketPersonas;
|
||||
return allMarketPersonas.filter(persona => {
|
||||
const fields = [
|
||||
persona.name,
|
||||
persona.creator_username,
|
||||
persona.pronouns,
|
||||
persona.appearance,
|
||||
persona.background,
|
||||
persona.details
|
||||
];
|
||||
return fields.some(field => field && field.toLowerCase().includes(query));
|
||||
});
|
||||
}
|
||||
|
||||
function applyMarketFilter() {
|
||||
const filtered = filterMarketPersonas();
|
||||
const assistantMarket = filtered.filter(persona => persona.persona_type === "assistant");
|
||||
const userMarket = filtered.filter(persona => persona.persona_type === "user");
|
||||
renderMarketList(assistantMarket, marketPersonaList, "assistant");
|
||||
renderMarketList(userMarket, marketUserPersonaList, "user");
|
||||
if (marketSearchInput?.value.trim() && !filtered.length) {
|
||||
setMarketStatus("No personas match your search.", "error");
|
||||
return;
|
||||
}
|
||||
setMarketStatus("Choose an AI persona or a user persona to equip.");
|
||||
}
|
||||
|
||||
async function loadMarketPersonas() {
|
||||
setMarketStatus("Loading market personas...");
|
||||
const res = await get("/personas/market");
|
||||
if (res.error) {
|
||||
setMarketStatus(res.error, "error");
|
||||
return;
|
||||
}
|
||||
if (res.error) return setMarketStatus(res.error, "error");
|
||||
allMarketPersonas = res.personas || [];
|
||||
applyMarketFilter();
|
||||
}
|
||||
|
||||
async function collectMarketPersona(marketId, personaType) {
|
||||
setMarketStatus(
|
||||
personaType === "assistant"
|
||||
? "Equipping the AI persona..."
|
||||
: "Equipping the user persona..."
|
||||
);
|
||||
setMarketStatus(personaType === "assistant" ? "Collecting AI persona..." : "Collecting user persona...");
|
||||
const res = await post(`/personas/market/${marketId}/collect`, {equip: true});
|
||||
if (res.error) {
|
||||
setMarketStatus(res.error, "error");
|
||||
return;
|
||||
}
|
||||
if (res.error) return setMarketStatus(res.error, "error");
|
||||
await loadMarketPersonas();
|
||||
setMarketStatus(
|
||||
personaType === "assistant"
|
||||
? "AI persona equipped. Head back to chat to use it."
|
||||
: "User persona equipped. Head back to chat to use it.",
|
||||
"success"
|
||||
);
|
||||
setMarketStatus(personaType === "assistant" ? "AI persona collected." : "User persona collected.", "success");
|
||||
}
|
||||
|
||||
if (refreshMarketBtn) {
|
||||
refreshMarketBtn.addEventListener("click", () => loadMarketPersonas());
|
||||
}
|
||||
|
||||
if (marketSearchInput) {
|
||||
marketSearchInput.addEventListener("input", () => applyMarketFilter());
|
||||
}
|
||||
|
||||
if (marketPreviewCancel) {
|
||||
marketPreviewCancel.addEventListener("click", closeMarketPreview);
|
||||
}
|
||||
|
||||
if (marketPreviewClose) {
|
||||
marketPreviewClose.addEventListener("click", closeMarketPreview);
|
||||
}
|
||||
|
||||
if (marketPreviewModal) {
|
||||
marketPreviewModal.addEventListener("click", (event) => {
|
||||
if (event.target === marketPreviewModal) {
|
||||
closeMarketPreview();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (marketPreviewConfirm) {
|
||||
marketPreviewConfirm.addEventListener("click", async () => {
|
||||
if (!pendingMarketPersona) return;
|
||||
const {id, persona_type} = pendingMarketPersona;
|
||||
closeMarketPreview();
|
||||
await collectMarketPersona(id, persona_type);
|
||||
});
|
||||
}
|
||||
refreshMarketBtn.addEventListener("click", loadMarketPersonas);
|
||||
marketSearchInput.addEventListener("input", applyMarketFilter);
|
||||
marketPreviewCancel.addEventListener("click", closeMarketPreview);
|
||||
marketPreviewClose.addEventListener("click", closeMarketPreview);
|
||||
marketPreviewModal.addEventListener("click", (event) => { if (event.target === marketPreviewModal) closeMarketPreview(); });
|
||||
marketPreviewConfirm.addEventListener("click", async () => {
|
||||
if (!pendingMarketPersona) return;
|
||||
const {id, persona_type} = pendingMarketPersona;
|
||||
closeMarketPreview();
|
||||
await collectMarketPersona(id, persona_type);
|
||||
});
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt");
|
||||
loadMarketPersonas();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user