Fix persona market publishing and add search

This commit is contained in:
2026-01-14 18:08:40 +01:00
parent 8600d7fabe
commit 5943647c57
4 changed files with 146 additions and 11 deletions
+4
View File
@@ -13,6 +13,10 @@
<p class="eyebrow">Community</p>
<h1>Persona Market</h1>
<p class="subtitle">Browse shared personas and choose who should embody them.</p>
<div class="market-search">
<label for="marketSearch">Search personas</label>
<input id="marketSearch" type="search" placeholder="Search by name, creator, or details"/>
</div>
</div>
<div class="market-header-actions">
<button id="refreshMarket" class="secondary-action" type="button">Refresh listings</button>
+41 -7
View File
@@ -2,6 +2,8 @@ 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");
let allMarketPersonas = [];
async function request(url, data, method = "POST") {
try {
@@ -37,7 +39,9 @@ function setMarketStatus(message, state = "") {
function buildPersonaMeta(persona) {
const pronouns = persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: —";
return `By ${persona.creator_username} • ${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) {
@@ -88,6 +92,35 @@ function renderMarketList(personaItems, listElement, personaType) {
});
}
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");
@@ -95,12 +128,8 @@ async function loadMarketPersonas() {
setMarketStatus(res.error, "error");
return;
}
const marketPersonas = res.personas || [];
const assistantMarket = marketPersonas.filter(persona => persona.persona_type === "assistant");
const userMarket = marketPersonas.filter(persona => persona.persona_type === "user");
renderMarketList(assistantMarket, marketPersonaList, "assistant");
renderMarketList(userMarket, marketUserPersonaList, "user");
setMarketStatus("Choose an AI persona or a user persona to equip.");
allMarketPersonas = res.personas || [];
applyMarketFilter();
}
async function collectMarketPersona(marketId, personaType) {
@@ -114,6 +143,7 @@ async function collectMarketPersona(marketId, personaType) {
setMarketStatus(res.error, "error");
return;
}
await loadMarketPersonas();
setMarketStatus(
personaType === "assistant"
? "AI persona equipped. Head back to chat to use it."
@@ -126,6 +156,10 @@ if (refreshMarketBtn) {
refreshMarketBtn.addEventListener("click", () => loadMarketPersonas());
}
if (marketSearchInput) {
marketSearchInput.addEventListener("input", () => applyMarketFilter());
}
window.addEventListener("load", () => {
loadMarketPersonas();
});
+27
View File
@@ -928,6 +928,33 @@ a.secondary-action {
margin: 4px 0;
}
.market-search {
display: grid;
gap: 6px;
margin-top: 12px;
max-width: 320px;
}
.market-search label {
font-size: 0.78rem;
color: var(--text-muted);
}
.market-search input {
padding: 10px 12px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(10, 10, 25, 0.8);
color: var(--text-primary);
font-size: 0.9rem;
}
.market-search input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(112, 86, 255, 0.25);
}
.market-header-actions {
display: flex;
gap: 10px;