551 lines
30 KiB
JavaScript
551 lines
30 KiB
JavaScript
import {buildPersonaSnapshot} from "./personaSnapshot.js";
|
|
import {createHistoryRepository} from "./historyRepository.js";
|
|
import {createAccountStatements, createAccountRepository} from "./accountRepository.js";
|
|
import {createChatStatements, createChatRepository} from "./chatRepository.js";
|
|
import {createPersonaStatements, createPersonaRepository} from "./personaRepository.js";
|
|
/**
|
|
* @file Bündelt Datenbankzugriffe auf Konten, Chats, Nachrichten, Personas und den Marktplatz.
|
|
*/
|
|
import {attachmentMetadata} from "./attachments.js";
|
|
|
|
const parseJsonArray = (value) => {
|
|
if (!value) return [];
|
|
try {
|
|
const parsed = JSON.parse(value);
|
|
return Array.isArray(parsed) ? parsed : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const MARKET_SORT_ORDERS = {
|
|
best: "pm.favorite_count DESC, rating_average DESC, pm.usage_count DESC, datetime(pm.created_at) DESC, pm.id DESC",
|
|
newest: "datetime(pm.created_at) DESC, pm.id DESC",
|
|
most_favorited: "pm.favorite_count DESC, rating_average DESC, pm.usage_count DESC, datetime(pm.created_at) DESC, pm.id DESC",
|
|
most_popular: "pm.usage_count DESC, pm.favorite_count DESC, rating_average DESC, datetime(pm.created_at) DESC, pm.id DESC",
|
|
top_rated: "rating_average DESC, pm.rating_count DESC, pm.favorite_count DESC, datetime(pm.created_at) DESC, pm.id DESC",
|
|
alphabetical: "LOWER(pm.name) ASC, datetime(pm.created_at) DESC, pm.id DESC"
|
|
};
|
|
|
|
function normalizeMarketSort(sort) {
|
|
const normalized = String(sort || "best").trim().toLowerCase().replace(/-/g, "_");
|
|
return MARKET_SORT_ORDERS[normalized] ? normalized : "best";
|
|
}
|
|
|
|
function normalizeMarketPersonaType(personaType) {
|
|
return ["assistant", "user"].includes(personaType) ? personaType : null;
|
|
}
|
|
|
|
export function createRepositories(db, config) {
|
|
const statements = {
|
|
...createAccountStatements(db),
|
|
...createChatStatements(db),
|
|
...createPersonaStatements(db),
|
|
updateChatMessageLinks: db.prepare("UPDATE chat_messages SET parent_message_id = ?, retry_prompt_message_id = ? WHERE id = ? AND chat_id = ?"),
|
|
insertMarketPersona: db.prepare("INSERT INTO personas (username, name, pronouns, appearance, background, details, example_dialogues, persona_type, source_market_id, source_creator_username) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
|
getPersonaBySourceMarket: db.prepare(`
|
|
SELECT id
|
|
FROM personas
|
|
WHERE username = ?
|
|
AND source_market_id = ?
|
|
`),
|
|
listMarketPersonas: db.prepare(`
|
|
SELECT pm.id, pm.persona_id, pm.creator_username, pm.name, pm.pronouns, pm.appearance, pm.background, pm.details,
|
|
pm.example_dialogues, pm.persona_type, pm.tags, pm.usage_count, pm.favorite_count, pm.rating_total, pm.rating_count,
|
|
pm.moderation_status, pm.moderation_notes, pm.created_at, pm.updated_at, pm.soft_deleted_at,
|
|
CASE WHEN pm.rating_count > 0 THEN CAST(pm.rating_total AS REAL) / pm.rating_count ELSE 0 END AS rating_average
|
|
FROM persona_market pm
|
|
WHERE pm.soft_deleted_at IS NULL
|
|
AND pm.moderation_status != 'rejected'
|
|
ORDER BY pm.favorite_count DESC, rating_average DESC, datetime(pm.updated_at) DESC
|
|
`),
|
|
listMarketPersonasAdmin: db.prepare(`
|
|
SELECT pm.id, pm.persona_id, pm.creator_username, pm.name, pm.pronouns, pm.appearance, pm.background, pm.details,
|
|
pm.example_dialogues, pm.persona_type, pm.tags, pm.usage_count, pm.favorite_count, pm.rating_total, pm.rating_count,
|
|
pm.moderation_status, pm.moderation_notes, pm.created_at, pm.updated_at, pm.soft_deleted_at,
|
|
CASE WHEN pm.rating_count > 0 THEN CAST(pm.rating_total AS REAL) / pm.rating_count ELSE 0 END AS rating_average
|
|
FROM persona_market pm
|
|
ORDER BY datetime(pm.updated_at) DESC
|
|
`),
|
|
getMarketPersona: db.prepare(`
|
|
SELECT pm.id, pm.persona_id, pm.creator_username, pm.name, pm.pronouns, pm.appearance, pm.background, pm.details,
|
|
pm.example_dialogues, pm.persona_type, pm.tags, pm.usage_count, pm.favorite_count, pm.rating_total, pm.rating_count,
|
|
pm.moderation_status, pm.moderation_notes, pm.soft_deleted_at,
|
|
CASE WHEN pm.rating_count > 0 THEN CAST(pm.rating_total AS REAL) / pm.rating_count ELSE 0 END AS rating_average
|
|
FROM persona_market pm
|
|
WHERE pm.id = ?
|
|
`),
|
|
getMarketPersonaByPersonaId: db.prepare(`
|
|
SELECT id, persona_id, creator_username, name, pronouns, appearance, background, details,
|
|
example_dialogues, persona_type, tags, usage_count, favorite_count, rating_total, rating_count,
|
|
moderation_status, moderation_notes, soft_deleted_at
|
|
FROM persona_market
|
|
WHERE persona_id = ?
|
|
AND creator_username = ?
|
|
`),
|
|
upsertMarketPersona: db.prepare(`
|
|
INSERT INTO persona_market (persona_id, creator_username, name, pronouns, appearance, background, details, example_dialogues, persona_type, tags)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(persona_id, creator_username) DO UPDATE SET
|
|
name = excluded.name,
|
|
pronouns = excluded.pronouns,
|
|
appearance = excluded.appearance,
|
|
background = excluded.background,
|
|
details = excluded.details,
|
|
example_dialogues = excluded.example_dialogues,
|
|
persona_type = excluded.persona_type,
|
|
tags = excluded.tags,
|
|
moderation_status = 'approved',
|
|
moderation_notes = NULL,
|
|
soft_deleted_at = NULL,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
`),
|
|
listPublishedPersonaIds: db.prepare("SELECT persona_id FROM persona_market WHERE creator_username = ? AND soft_deleted_at IS NULL"),
|
|
insertMarketFavorite: db.prepare("INSERT OR IGNORE INTO persona_market_favorites (market_id, username) VALUES (?, ?)"),
|
|
deleteMarketFavorite: db.prepare("DELETE FROM persona_market_favorites WHERE market_id = ? AND username = ?"),
|
|
isMarketFavorite: db.prepare("SELECT 1 AS favorite FROM persona_market_favorites WHERE market_id = ? AND username = ?"),
|
|
recountMarketFavorites: db.prepare("UPDATE persona_market SET favorite_count = (SELECT COUNT(*) FROM persona_market_favorites WHERE market_id = ?) WHERE id = ?"),
|
|
upsertMarketRating: db.prepare(`
|
|
INSERT INTO persona_market_ratings (market_id, username, rating)
|
|
VALUES (?, ?, ?)
|
|
ON CONFLICT(market_id, username) DO UPDATE SET
|
|
rating = excluded.rating,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
`),
|
|
recountMarketRatings: db.prepare(`
|
|
UPDATE persona_market
|
|
SET rating_total = COALESCE((SELECT SUM(rating) FROM persona_market_ratings WHERE market_id = ?), 0),
|
|
rating_count = COALESCE((SELECT COUNT(*) FROM persona_market_ratings WHERE market_id = ?), 0)
|
|
WHERE id = ?
|
|
`),
|
|
insertMarketReport: db.prepare("INSERT INTO persona_market_reports (market_id, reporter_username, reason, details) VALUES (?, ?, ?, ?)"),
|
|
listMarketReports: db.prepare(`
|
|
SELECT id, market_id, reporter_username, reason, details, status, created_at, updated_at
|
|
FROM persona_market_reports
|
|
ORDER BY CASE WHEN status = 'open' THEN 0 ELSE 1 END, datetime(created_at) DESC
|
|
`),
|
|
updateMarketReportStatus: db.prepare("UPDATE persona_market_reports SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"),
|
|
updateMarketModeration: db.prepare(`
|
|
UPDATE persona_market
|
|
SET moderation_status = ?,
|
|
moderation_notes = ?,
|
|
soft_deleted_at = CASE WHEN ? THEN COALESCE(soft_deleted_at, CURRENT_TIMESTAMP) ELSE NULL END,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
`),
|
|
insertPromptTemplate: db.prepare(`
|
|
INSERT INTO prompt_templates (username, persona_id, name, description, category, prompt_text, starter_text, is_shared)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`),
|
|
getPromptTemplate: db.prepare(`
|
|
SELECT id, username, persona_id, name, description, category, prompt_text, starter_text, is_shared, created_at, updated_at
|
|
FROM prompt_templates
|
|
WHERE id = ?
|
|
`),
|
|
updatePromptTemplate: db.prepare(`
|
|
UPDATE prompt_templates
|
|
SET name = ?,
|
|
description = ?,
|
|
category = ?,
|
|
prompt_text = ?,
|
|
starter_text = ?,
|
|
is_shared = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
AND (username = ? OR (username IS NULL AND ? = 'system'))
|
|
`),
|
|
deletePromptTemplate: db.prepare("DELETE FROM prompt_templates WHERE id = ? AND username = ?"),
|
|
listPromptTemplates: db.prepare(`
|
|
SELECT id, username, persona_id, name, description, category, prompt_text, starter_text, is_shared, created_at, updated_at
|
|
FROM prompt_templates
|
|
WHERE username = ?
|
|
OR (is_shared = 1)
|
|
OR (username IS NULL)
|
|
ORDER BY is_shared DESC, datetime(updated_at) DESC
|
|
`),
|
|
listPersonaPromptTemplates: db.prepare(`
|
|
SELECT id, username, persona_id, name, description, category, prompt_text, starter_text, is_shared, created_at, updated_at
|
|
FROM prompt_templates
|
|
WHERE (persona_id = ? AND (username = ? OR username IS NULL OR is_shared = 1))
|
|
OR (persona_id IS NULL AND (username = ? OR username IS NULL OR is_shared = 1))
|
|
ORDER BY is_shared DESC, datetime(updated_at) DESC
|
|
`),
|
|
getChatMessageCountForUser: db.prepare(`
|
|
SELECT COUNT(*) as count
|
|
FROM chat_messages cm
|
|
JOIN chat_sessions cs ON cs.id = cm.chat_id
|
|
WHERE cs.username = ?
|
|
`),
|
|
getPersonaCountForUser: db.prepare("SELECT COUNT(*) as count FROM personas WHERE username = ?"),
|
|
getPublishedPersonaCountForUser: db.prepare("SELECT COUNT(*) as count FROM persona_market WHERE creator_username = ? AND soft_deleted_at IS NULL"),
|
|
getRecentActivity: db.prepare(`
|
|
SELECT cs.id AS chat_id,
|
|
cs.title,
|
|
MAX(cm.created_at) AS last_message_at,
|
|
COUNT(cm.id) AS message_count
|
|
FROM chat_sessions cs
|
|
LEFT JOIN chat_messages cm ON cm.chat_id = cs.id
|
|
WHERE cs.username = ?
|
|
GROUP BY cs.id
|
|
ORDER BY datetime(MAX(COALESCE(cm.created_at, cs.updated_at))) DESC
|
|
LIMIT 5
|
|
`),
|
|
getMostUsedPersonas: db.prepare(`
|
|
SELECT p.id, p.name, COUNT(cs.id) AS usage_count
|
|
FROM personas p
|
|
LEFT JOIN chat_sessions cs ON cs.assistant_persona_id = p.id OR cs.user_persona_id = p.id
|
|
WHERE p.username = ?
|
|
GROUP BY p.id
|
|
ORDER BY usage_count DESC, datetime(p.updated_at) DESC
|
|
LIMIT 5
|
|
`),
|
|
getAverageChatLength: db.prepare(`
|
|
SELECT AVG(message_count) AS average_length
|
|
FROM (
|
|
SELECT COUNT(cm.id) AS message_count
|
|
FROM chat_sessions cs
|
|
LEFT JOIN chat_messages cm ON cm.chat_id = cs.id
|
|
WHERE cs.username = ?
|
|
GROUP BY cs.id
|
|
)
|
|
`),
|
|
getFavoriteModel: db.prepare(`
|
|
SELECT model_name, COUNT(*) AS usage_count
|
|
FROM chat_messages cm
|
|
JOIN chat_sessions cs ON cs.id = cm.chat_id
|
|
WHERE cs.username = ?
|
|
AND cm.model_name IS NOT NULL
|
|
GROUP BY model_name
|
|
ORDER BY usage_count DESC
|
|
LIMIT 1
|
|
`),
|
|
getCollectedPersonaCount: db.prepare("SELECT COUNT(*) AS count FROM personas WHERE username = ? AND source_market_id IS NOT NULL"),
|
|
getPublishedPersonaUsage: db.prepare(`
|
|
SELECT COUNT(*) AS published_count, COALESCE(SUM(usage_count), 0) AS usage_total
|
|
FROM persona_market
|
|
WHERE creator_username = ?
|
|
AND soft_deleted_at IS NULL
|
|
`),
|
|
incrementMarketUsageCount: db.prepare("UPDATE persona_market SET usage_count = usage_count + 1 WHERE id = ?")
|
|
};
|
|
|
|
const importWorkspaceTransaction = db.transaction((username, workspace) => {
|
|
const imported = {personas: 0, chats: 0, templates: 0};
|
|
const personaIds = new Map();
|
|
const importedChats = [];
|
|
for (const persona of workspace.personas || []) {
|
|
const personaId = statements.insertPersona.run(
|
|
username,
|
|
persona.name,
|
|
persona.pronouns ?? null,
|
|
persona.appearance ?? null,
|
|
persona.background ?? null,
|
|
persona.details ?? null,
|
|
persona.example_dialogues ?? null,
|
|
persona.persona_type || "assistant"
|
|
).lastInsertRowid;
|
|
if (persona.sourceId != null) personaIds.set(persona.sourceId, Number(personaId));
|
|
statements.insertPersonaVersion.run(personaId, username, 1, buildPersonaSnapshot(statements.getPersona.get(personaId, username)));
|
|
imported.personas += 1;
|
|
}
|
|
|
|
for (const template of workspace.templates || []) {
|
|
statements.insertPromptTemplate.run(
|
|
username,
|
|
personaIds.get(template.persona_id) ?? null,
|
|
template.name,
|
|
template.description ?? null,
|
|
template.category ?? null,
|
|
template.prompt_text,
|
|
template.starter_text ?? null,
|
|
template.is_shared ? 1 : 0
|
|
);
|
|
imported.templates += 1;
|
|
}
|
|
|
|
for (const chat of workspace.chats || []) {
|
|
const assistantPersonaId = personaIds.get(chat.assistant_persona_id) ?? null;
|
|
const userPersonaId = personaIds.get(chat.user_persona_id) ?? null;
|
|
const chatId = statements.insertChatSession.run(
|
|
username,
|
|
chat.stored_title || chat.title || "Imported chat",
|
|
assistantPersonaId,
|
|
userPersonaId,
|
|
chat.folder_name ?? null,
|
|
chat.is_pinned ? 1 : 0,
|
|
chat.archived_at ?? null,
|
|
chat.scenario_prompt ?? null,
|
|
chat.scenario_summary ?? null
|
|
).lastInsertRowid;
|
|
db.prepare("UPDATE chat_sessions SET personality_id = ? WHERE id = ?").run(chat.personality_id || "general-assistant", chatId);
|
|
const importedMessageIds = new Map();
|
|
importedChats.push({chat, chatId, messageIds: importedMessageIds});
|
|
for (const message of chat.messages || []) {
|
|
const result = statements.insertChatMessage.run(
|
|
chatId,
|
|
message.role,
|
|
message.content,
|
|
message.model_name ?? null,
|
|
JSON.stringify(message.retryVariants || [message.content || ""]),
|
|
Number(message.retryActiveIndex || 0),
|
|
Number(message.retryRetriesUsed || 0),
|
|
null,
|
|
message.deliveryStatus || "complete",
|
|
message.errorMessage || null,
|
|
null
|
|
);
|
|
if (message.sourceId != null) importedMessageIds.set(message.sourceId, Number(result.lastInsertRowid));
|
|
if (message.sources) statements.updateChatMessageSources.run(JSON.stringify(message.sources), result.lastInsertRowid, chatId);
|
|
if (message.thinking) statements.updateChatMessageThinking.run(message.thinking, result.lastInsertRowid, chatId);
|
|
for (const version of message.responseVersions || []) {
|
|
db.prepare("INSERT INTO chat_message_versions (message_id, content, thinking, sources, model_name, created_at) VALUES (?, ?, ?, ?, ?, COALESCE(?, CURRENT_TIMESTAMP))")
|
|
.run(result.lastInsertRowid, version.content, version.thinking, JSON.stringify(version.sources), version.modelName, version.createdAt);
|
|
}
|
|
for (const [position, attachment] of (message.attachments || []).slice(0, config.uploadMaxFiles || 5).entries()) {
|
|
// JSON imports reconnect owned images; archive imports also reconnect newly restored images.
|
|
db.prepare("INSERT OR IGNORE INTO message_attachments(message_id,attachment_id,position) SELECT ?,id,? FROM attachments WHERE id=? AND username=? AND status IN ('temporary','attached')")
|
|
.run(result.lastInsertRowid, position, attachment.id, username);
|
|
db.prepare("UPDATE attachments SET status='attached' WHERE id=? AND username=?").run(attachment.id, username);
|
|
}
|
|
}
|
|
// Resolve links after every message has an ID, including forward references.
|
|
for (const message of chat.messages || []) {
|
|
statements.updateChatMessageLinks.run(
|
|
importedMessageIds.get(message.parentMessageId) ?? null,
|
|
importedMessageIds.get(message.retryPromptMessageId) ?? null,
|
|
importedMessageIds.get(message.sourceId),
|
|
chatId
|
|
);
|
|
}
|
|
statements.updateChatGenerationSettings.run(
|
|
chat.preferred_model ?? null,
|
|
chat.temperature ?? null,
|
|
chat.context_length ?? null,
|
|
chat.response_length ?? null,
|
|
chat.system_instruction ?? null,
|
|
chatId,
|
|
username
|
|
);
|
|
const importedActiveLeaf = importedMessageIds.get(chat.active_leaf_message_id)
|
|
?? Array.from(importedMessageIds.values()).at(-1)
|
|
?? null;
|
|
statements.setChatActiveLeafById.run(importedActiveLeaf, chatId);
|
|
if (chat.context_summary) {
|
|
const mappedSummaryMessageId = importedMessageIds.get(Number(chat.context_summary_message_id || 0)) || 0;
|
|
statements.updateChatContextSummary.run(
|
|
chat.context_summary,
|
|
mappedSummaryMessageId,
|
|
chatId,
|
|
username
|
|
);
|
|
}
|
|
for (const memory of chat.memories || []) {
|
|
const fact = String(memory?.fact || "").trim();
|
|
if (!fact) continue;
|
|
statements.insertChatMemory.run(username, chatId,
|
|
memory.assistant_persona_id === undefined ? assistantPersonaId : personaIds.get(memory.assistant_persona_id) ?? null,
|
|
memory.user_persona_id === undefined ? userPersonaId : personaIds.get(memory.user_persona_id) ?? null, fact);
|
|
}
|
|
const personaState = chat.persona_state || chat.personaChatState;
|
|
if (personaState) {
|
|
statements.upsertPersonaChatState.run(
|
|
username,
|
|
chatId,
|
|
personaIds.get(personaState.assistant_persona_id) ?? null,
|
|
personaIds.get(personaState.user_persona_id) ?? null,
|
|
personaState.relationship_notes ?? personaState.relationshipNotes ?? null,
|
|
personaState.tone ?? null,
|
|
personaState.current_location ?? personaState.currentLocation ?? null,
|
|
personaState.goals ?? null,
|
|
personaState.unresolved_threads ?? personaState.unresolvedThreads ?? null,
|
|
personaState.boundaries ?? null
|
|
);
|
|
}
|
|
imported.chats += 1;
|
|
}
|
|
|
|
const chatsBySourceId = new Map(importedChats.filter(({chat}) => chat.sourceId != null).map(entry => [entry.chat.sourceId, entry]));
|
|
for (const {chat, chatId} of importedChats) {
|
|
const sourceChat = chatsBySourceId.get(chat.source_chat_id);
|
|
statements.updateChatBranchSource.run(sourceChat?.chatId ?? null,
|
|
sourceChat?.messageIds.get(chat.branched_from_message_id) ?? null, chatId, username);
|
|
}
|
|
|
|
return imported;
|
|
});
|
|
|
|
return {
|
|
...createHistoryRepository(db),
|
|
...createAccountRepository(db, statements),
|
|
...createChatRepository(db, config, statements),
|
|
...createPersonaRepository(db, statements),
|
|
listMarketPersonas({admin = false, username = null, personaType = null, sort = "best"} = {}) {
|
|
const normalizedPersonaType = normalizeMarketPersonaType(personaType);
|
|
const normalizedSort = normalizeMarketSort(sort);
|
|
const params = [username];
|
|
const filters = [];
|
|
|
|
if (!admin) {
|
|
filters.push("pm.soft_deleted_at IS NULL");
|
|
filters.push("pm.moderation_status != 'rejected'");
|
|
}
|
|
|
|
if (normalizedPersonaType) {
|
|
filters.push("pm.persona_type = ?");
|
|
params.push(normalizedPersonaType);
|
|
}
|
|
|
|
const whereClause = filters.length ? `WHERE ${filters.join(" AND ")}` : "";
|
|
const query = db.prepare(`
|
|
SELECT pm.id, pm.persona_id, pm.creator_username, pm.name, pm.pronouns, pm.appearance, pm.background, pm.details,
|
|
pm.example_dialogues, pm.persona_type, pm.tags, pm.usage_count, pm.favorite_count, pm.rating_total, pm.rating_count,
|
|
pm.moderation_status, pm.moderation_notes, pm.created_at, pm.updated_at, pm.soft_deleted_at,
|
|
CASE WHEN pm.rating_count > 0 THEN CAST(pm.rating_total AS REAL) / pm.rating_count ELSE 0 END AS rating_average,
|
|
CASE WHEN mf.username IS NULL THEN 0 ELSE 1 END AS is_favorite
|
|
FROM persona_market pm
|
|
LEFT JOIN persona_market_favorites mf ON mf.market_id = pm.id AND mf.username = ?
|
|
${whereClause}
|
|
ORDER BY ${MARKET_SORT_ORDERS[normalizedSort]}
|
|
`);
|
|
|
|
return query.all(...params).map((persona) => ({
|
|
...persona,
|
|
is_favorite: Boolean(persona.is_favorite),
|
|
tags: parseJsonArray(persona.tags)
|
|
}));
|
|
},
|
|
getMarketPersona: (marketId) => {
|
|
const persona = statements.getMarketPersona.get(marketId);
|
|
return persona ? {...persona, tags: parseJsonArray(persona.tags)} : null;
|
|
},
|
|
getMarketPersonaByPersonaId: (personaId, username) => {
|
|
const persona = statements.getMarketPersonaByPersonaId.get(personaId, username);
|
|
return persona ? {...persona, tags: parseJsonArray(persona.tags)} : null;
|
|
},
|
|
upsertMarketPersona(personaId, username, persona, tags = []) {
|
|
statements.upsertMarketPersona.run(
|
|
personaId,
|
|
username,
|
|
persona.name,
|
|
persona.pronouns,
|
|
persona.appearance,
|
|
persona.background,
|
|
persona.details,
|
|
persona.example_dialogues,
|
|
persona.persona_type,
|
|
JSON.stringify(tags)
|
|
);
|
|
return this.getMarketPersonaByPersonaId(personaId, username);
|
|
},
|
|
listPublishedPersonaIds: (username) => statements.listPublishedPersonaIds.all(username).map((row) => row.persona_id),
|
|
getPersonaBySourceMarket: (username, marketId) => statements.getPersonaBySourceMarket.get(username, marketId),
|
|
collectMarketPersona(username, marketPersona) {
|
|
const personaId = statements.insertMarketPersona.run(
|
|
username,
|
|
marketPersona.name,
|
|
marketPersona.pronouns,
|
|
marketPersona.appearance,
|
|
marketPersona.background,
|
|
marketPersona.details,
|
|
marketPersona.example_dialogues,
|
|
marketPersona.persona_type,
|
|
marketPersona.id,
|
|
marketPersona.creator_username
|
|
).lastInsertRowid;
|
|
const persona = statements.getPersona.get(personaId, username);
|
|
statements.insertPersonaVersion.run(personaId, username, 1, buildPersonaSnapshot(persona));
|
|
return persona;
|
|
},
|
|
incrementMarketUsageCount: (marketId) => statements.incrementMarketUsageCount.run(marketId),
|
|
toggleMarketFavorite(marketId, username) {
|
|
const favorite = statements.isMarketFavorite.get(marketId, username);
|
|
if (favorite) statements.deleteMarketFavorite.run(marketId, username);
|
|
else statements.insertMarketFavorite.run(marketId, username);
|
|
statements.recountMarketFavorites.run(marketId, marketId);
|
|
return !favorite;
|
|
},
|
|
rateMarketPersona(marketId, username, rating) {
|
|
statements.upsertMarketRating.run(marketId, username, rating);
|
|
statements.recountMarketRatings.run(marketId, marketId, marketId);
|
|
return this.getMarketPersona(marketId);
|
|
},
|
|
reportMarketPersona: (marketId, username, reason, details) => statements.insertMarketReport.run(marketId, username, reason, details),
|
|
listMarketReports: () => statements.listMarketReports.all(),
|
|
updateMarketReportStatus: (reportId, status) => statements.updateMarketReportStatus.run(status, reportId),
|
|
updateMarketModeration: (marketId, {status, notes = null, softDelete = false}) => statements.updateMarketModeration.run(status, notes, softDelete ? 1 : 0, marketId),
|
|
deletePublishedPersona: (personaId, username) => statements.deleteMarketPersonaByPersonaId.run(personaId, username),
|
|
createPromptTemplate(username, payload) {
|
|
const id = statements.insertPromptTemplate.run(
|
|
username,
|
|
payload.personaId ?? null,
|
|
payload.name,
|
|
payload.description ?? null,
|
|
payload.category ?? null,
|
|
payload.promptText,
|
|
payload.starterText ?? null,
|
|
payload.isShared ? 1 : 0
|
|
).lastInsertRowid;
|
|
return statements.getPromptTemplate.get(id);
|
|
},
|
|
updatePromptTemplate(templateId, username, payload) {
|
|
return statements.updatePromptTemplate.run(
|
|
payload.name,
|
|
payload.description ?? null,
|
|
payload.category ?? null,
|
|
payload.promptText,
|
|
payload.starterText ?? null,
|
|
payload.isShared ? 1 : 0,
|
|
templateId,
|
|
username,
|
|
username
|
|
);
|
|
},
|
|
deletePromptTemplate: (templateId, username) => statements.deletePromptTemplate.run(templateId, username),
|
|
listPromptTemplates: (username) => statements.listPromptTemplates.all(username),
|
|
listPromptTemplatesForPersona: (username, personaId = null) => statements.listPersonaPromptTemplates.all(personaId, username, username),
|
|
getPromptTemplate: (templateId) => statements.getPromptTemplate.get(templateId),
|
|
getDashboardSummary(username) {
|
|
const favoriteModel = statements.getFavoriteModel.get(username);
|
|
const publishedUsage = statements.getPublishedPersonaUsage.get(username);
|
|
return {
|
|
chats: statements.countChatSessions.get(username).count,
|
|
messages: statements.getChatMessageCountForUser.get(username).count,
|
|
personas: statements.getPersonaCountForUser.get(username).count,
|
|
published: statements.getPublishedPersonaCountForUser.get(username).count,
|
|
marketPersonas: statements.listMarketPersonas.all().length,
|
|
mostUsedPersonas: statements.getMostUsedPersonas.all(username),
|
|
averageChatLength: Number(statements.getAverageChatLength.get(username)?.average_length || 0),
|
|
favoriteModel: favoriteModel?.model_name || null,
|
|
recentActivity: statements.getRecentActivity.all(username),
|
|
collectedPersonaCount: statements.getCollectedPersonaCount.get(username).count,
|
|
publishedPersonaPerformance: {
|
|
publishedCount: publishedUsage.published_count,
|
|
totalUses: publishedUsage.usage_total
|
|
}
|
|
};
|
|
},
|
|
exportWorkspace(username) {
|
|
return {
|
|
exportedAt: new Date().toISOString(),
|
|
chats: this.listChats(username).map((chat) => ({
|
|
...chat,
|
|
memories: this.listChatMemories(chat.id, username),
|
|
persona_state: this.getPersonaChatState(chat.id, username),
|
|
messages: this.listChatMessages(chat.id).map((message) => ({
|
|
...message,
|
|
responseVersions: this.listMessageVersions(message.id).map(version => ({
|
|
content: version.content, thinking: version.thinking,
|
|
sources: parseJsonArray(version.sources), modelName: version.model_name, createdAt: version.created_at
|
|
})),
|
|
attachments: db.prepare("SELECT a.* FROM attachments a JOIN message_attachments m ON a.id=m.attachment_id WHERE m.message_id=? ORDER BY m.position").all(message.id).map(attachmentMetadata),
|
|
retryVariants: parseJsonArray(message.retry_variants)
|
|
}))
|
|
})),
|
|
personas: this.listAllPersonas(username),
|
|
templates: this.listPromptTemplates(username)
|
|
};
|
|
},
|
|
importWorkspace: (username, workspace) => importWorkspaceTransaction(username, workspace)
|
|
};
|
|
}
|