add missing files
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
const personaFieldNames = ["name", "pronouns", "appearance", "background", "details", "exampleDialogues"];
|
||||||
|
|
||||||
|
export function populatePersonaForm(fields, persona = {}) {
|
||||||
|
personaFieldNames.forEach((name) => {
|
||||||
|
const field = fields[name];
|
||||||
|
if (!field) return;
|
||||||
|
const sourceName = name === "exampleDialogues" ? "example_dialogues" : name;
|
||||||
|
field.value = persona[sourceName] || "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readPersonaForm(fields) {
|
||||||
|
return personaFieldNames.reduce((payload, name) => {
|
||||||
|
const field = fields[name];
|
||||||
|
if (!field) return payload;
|
||||||
|
payload[name] = field.value.trim();
|
||||||
|
return payload;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export function setStatus(element, message = "", state = "", baseClass = "status") {
|
||||||
|
if (!element) return;
|
||||||
|
element.textContent = message;
|
||||||
|
element.className = [baseClass, state].filter(Boolean).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearStatus(element, baseClass = "status") {
|
||||||
|
setStatus(element, "", "hidden", baseClass);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
(() => {
|
||||||
|
const themes = new Set(["fakegpt", "fraud", "germini", "slopilot", "beta-ai", "confusity"]);
|
||||||
|
const storedTheme = localStorage.getItem("krishd-theme");
|
||||||
|
const storedMode = localStorage.getItem("krishd-theme-mode");
|
||||||
|
const theme = themes.has(storedTheme) ? storedTheme : "fakegpt";
|
||||||
|
const mode = storedMode === "dark" ? "dark" : "light";
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
document.documentElement.dataset.themeMode = mode;
|
||||||
|
document.addEventListener("readystatechange", () => {
|
||||||
|
if (!document.body) return;
|
||||||
|
document.body.dataset.theme = theme;
|
||||||
|
document.body.dataset.themeMode = mode;
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import {themes} from "./constants.js";
|
||||||
|
|
||||||
|
export const THEME_STORAGE_KEY = "krishd-theme";
|
||||||
|
export const THEME_MODE_STORAGE_KEY = "krishd-theme-mode";
|
||||||
|
export const DEFAULT_THEME = "fakegpt";
|
||||||
|
export const DEFAULT_THEME_MODE = "light";
|
||||||
|
|
||||||
|
export function normalizeTheme(themeKey) {
|
||||||
|
return Object.hasOwn(themes, themeKey) ? themeKey : DEFAULT_THEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeThemeMode(modeKey) {
|
||||||
|
return modeKey === "dark" ? "dark" : DEFAULT_THEME_MODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readStoredAppearance() {
|
||||||
|
return {
|
||||||
|
theme: normalizeTheme(localStorage.getItem(THEME_STORAGE_KEY)),
|
||||||
|
mode: normalizeThemeMode(localStorage.getItem(THEME_MODE_STORAGE_KEY))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyTheme(themeKey, {
|
||||||
|
persist = true,
|
||||||
|
title = null,
|
||||||
|
themeSelect = null,
|
||||||
|
nameTargets = document.querySelectorAll("[data-theme-name]"),
|
||||||
|
logoTargets = document.querySelectorAll("[data-theme-logo]")
|
||||||
|
} = {}) {
|
||||||
|
const nextTheme = normalizeTheme(themeKey);
|
||||||
|
const theme = themes[nextTheme];
|
||||||
|
document.body.dataset.theme = nextTheme;
|
||||||
|
if (typeof title === "function") document.title = title(theme);
|
||||||
|
if (typeof title === "string") document.title = title;
|
||||||
|
nameTargets.forEach((target) => { target.textContent = theme.name; });
|
||||||
|
logoTargets.forEach((target) => { target.textContent = theme.short; });
|
||||||
|
if (themeSelect) themeSelect.value = nextTheme;
|
||||||
|
if (persist) localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
|
||||||
|
document.dispatchEvent(new CustomEvent("appearancechange", {detail: {theme: nextTheme}}));
|
||||||
|
return nextTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyThemeMode(modeKey, {persist = true, modeSelect = null} = {}) {
|
||||||
|
const nextMode = normalizeThemeMode(modeKey);
|
||||||
|
document.body.dataset.themeMode = nextMode;
|
||||||
|
if (modeSelect) modeSelect.value = nextMode;
|
||||||
|
if (persist) localStorage.setItem(THEME_MODE_STORAGE_KEY, nextMode);
|
||||||
|
document.dispatchEvent(new CustomEvent("appearancechange", {detail: {mode: nextMode}}));
|
||||||
|
return nextMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeAppearance(options = {}) {
|
||||||
|
const appearance = readStoredAppearance();
|
||||||
|
applyTheme(appearance.theme, {...options, persist: false});
|
||||||
|
applyThemeMode(appearance.mode, {
|
||||||
|
persist: false,
|
||||||
|
modeSelect: options.modeSelect || null
|
||||||
|
});
|
||||||
|
return appearance;
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
export const retryStylePrompts = Object.freeze({
|
||||||
|
shorter: "Rewrite the assistant reply to be shorter and tighter while preserving its meaning.",
|
||||||
|
direct: "Rewrite the assistant reply to be more direct and less padded.",
|
||||||
|
emotional: "Rewrite the assistant reply to be more emotionally expressive without changing the core meaning.",
|
||||||
|
"stay-in-character": "Rewrite the assistant reply to stay more strongly in character and voice.",
|
||||||
|
dialogue: "Rewrite the assistant reply as dialogue only. Remove narration unless required for clarity."
|
||||||
|
});
|
||||||
|
|
||||||
|
export function clampRetryActiveIndex(index, variants) {
|
||||||
|
const numeric = Number(index);
|
||||||
|
if (!Number.isInteger(numeric) || numeric < 0) return 0;
|
||||||
|
return Math.min(numeric, Math.max(0, variants.length - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRetryVariants(rawValue, content) {
|
||||||
|
if (!rawValue) return [content || ""];
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(rawValue);
|
||||||
|
if (Array.isArray(parsed)) {
|
||||||
|
const normalized = parsed.map((item) => String(item || "").trim()).filter(Boolean);
|
||||||
|
if (normalized.length) return normalized;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return [content || ""];
|
||||||
|
}
|
||||||
|
return [content || ""];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatChatMessage(row) {
|
||||||
|
if (!row) return null;
|
||||||
|
const retryVariants = parseRetryVariants(row.retry_variants, row.content);
|
||||||
|
const retryActiveIndex = clampRetryActiveIndex(row.retry_active_index, retryVariants);
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
role: row.role,
|
||||||
|
content: row.content,
|
||||||
|
modelName: row.model_name || null,
|
||||||
|
retryVariants,
|
||||||
|
retryActiveIndex,
|
||||||
|
retryRetriesUsed: Number(row.retry_retries_used || 0),
|
||||||
|
retryPromptMessageId: row.retry_prompt_message_id ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildMemoryPrompt(memories) {
|
||||||
|
const facts = (memories || [])
|
||||||
|
.map((memory) => String(memory.fact || "").trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 30);
|
||||||
|
if (!facts.length) return null;
|
||||||
|
return [
|
||||||
|
"LONG-TERM MEMORY:",
|
||||||
|
"These are durable facts the assistant should remember for this chat/persona context.",
|
||||||
|
"Use them for continuity, but do not mention them unless relevant.",
|
||||||
|
...facts.map((fact, index) => `${index + 1}. ${fact}`)
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildContextSummaryPrompt(summary) {
|
||||||
|
if (!summary) return null;
|
||||||
|
return [
|
||||||
|
"CONVERSATION SUMMARY:",
|
||||||
|
"This is compressed background context from earlier messages in this chat.",
|
||||||
|
"Use it for continuity. Recent raw messages below are more authoritative.",
|
||||||
|
summary
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPersonaStatePrompt(state) {
|
||||||
|
if (!state) return null;
|
||||||
|
const lines = [
|
||||||
|
["Relationship notes", state.relationship_notes],
|
||||||
|
["Tone", state.tone],
|
||||||
|
["Current location", state.current_location],
|
||||||
|
["Goals", state.goals],
|
||||||
|
["Unresolved threads", state.unresolved_threads],
|
||||||
|
["Boundaries", state.boundaries]
|
||||||
|
]
|
||||||
|
.map(([label, value]) => [label, String(value || "").trim()])
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([label, value]) => `${label}: ${value}`);
|
||||||
|
if (!lines.length) return null;
|
||||||
|
return [
|
||||||
|
"PERSONA RELATIONSHIP STATE:",
|
||||||
|
"Use this roleplay continuity state for the current assistant/user persona pair.",
|
||||||
|
"Keep it consistent unless the latest user message clearly changes it.",
|
||||||
|
...lines
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatMessagesForSummary(messages) {
|
||||||
|
return messages.map((message) => {
|
||||||
|
const speaker = message.role === "bot" ? "Assistant" : message.role === "user" ? "User" : message.role;
|
||||||
|
return `${speaker}: ${String(message.content || "").trim()}`;
|
||||||
|
}).join("\n\n");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user