fix more stuff and improve styling immensely
This commit is contained in:
@@ -278,6 +278,9 @@ export function createChatService(repositories, modelService, config) {
|
||||
: await modelService.streamReply(model, messagesPayload, onChunk);
|
||||
|
||||
if (!fullReply) return {error: "Failed to generate a valid character reply", status: 500};
|
||||
if (activePersona && containsUserVoiceInRoleplayOpener(fullReply, activeUserPersona)) {
|
||||
return {error: "Failed to generate a valid character reply", status: 500};
|
||||
}
|
||||
insertChatMessage(chatId, "bot", fullReply, {
|
||||
retryVariants: [fullReply],
|
||||
retryActiveIndex: 0,
|
||||
@@ -285,9 +288,6 @@ export function createChatService(repositories, modelService, config) {
|
||||
retryPromptMessageId: null
|
||||
}, model);
|
||||
repositories.touchChat(chatId, user);
|
||||
if (activePersona && containsUserVoiceInRoleplayOpener(fullReply, activeUserPersona)) {
|
||||
return {error: "Failed to generate a valid character reply", status: 500};
|
||||
}
|
||||
return {reply: fullReply};
|
||||
},
|
||||
async retryMessage({user, chatId, selectedModel, targetMessage, retryStyle = null}) {
|
||||
|
||||
@@ -92,18 +92,20 @@ export function createModelService(config, overrides = {}) {
|
||||
state.activeRequests += 1;
|
||||
|
||||
try {
|
||||
const response = await withTimeout(config.modelRequestTimeoutMs, (signal) => fetchImpl(`${config.modelApiBaseUrl}/chat`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({model, messages: messagesPayload, stream: true}),
|
||||
signal
|
||||
}));
|
||||
const fullReply = await withTimeout(config.modelRequestTimeoutMs, async (signal) => {
|
||||
const response = await fetchImpl(`${config.modelApiBaseUrl}/chat`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({model, messages: messagesPayload, stream: true}),
|
||||
signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ModelServiceError("MODEL_UPSTREAM_ERROR", `Model backend returned ${response.status}.`, 502);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new ModelServiceError("MODEL_UPSTREAM_ERROR", `Model backend returned ${response.status}.`, 502);
|
||||
}
|
||||
|
||||
const fullReply = await readStreamingReply(response);
|
||||
return readStreamingReply(response);
|
||||
});
|
||||
lastHealth = {ok: true, checkedAt: new Date().toISOString(), error: null};
|
||||
return fullReply;
|
||||
} catch (error) {
|
||||
@@ -124,18 +126,20 @@ export function createModelService(config, overrides = {}) {
|
||||
state.activeRequests += 1;
|
||||
|
||||
try {
|
||||
const response = await withTimeout(config.modelRequestTimeoutMs, (signal) => fetchImpl(`${config.modelApiBaseUrl}/chat`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({model, messages: messagesPayload, stream: true}),
|
||||
signal
|
||||
}));
|
||||
const fullReply = await withTimeout(config.modelRequestTimeoutMs, async (signal) => {
|
||||
const response = await fetchImpl(`${config.modelApiBaseUrl}/chat`, {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({model, messages: messagesPayload, stream: true}),
|
||||
signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ModelServiceError("MODEL_UPSTREAM_ERROR", `Model backend returned ${response.status}.`, 502);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new ModelServiceError("MODEL_UPSTREAM_ERROR", `Model backend returned ${response.status}.`, 502);
|
||||
}
|
||||
|
||||
const fullReply = await readStreamingReply(response, onChunk);
|
||||
return readStreamingReply(response, onChunk);
|
||||
});
|
||||
lastHealth = {ok: true, checkedAt: new Date().toISOString(), error: null};
|
||||
return fullReply;
|
||||
} catch (error) {
|
||||
@@ -157,11 +161,13 @@ export function createModelService(config, overrides = {}) {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await withTimeout(config.modelListTimeoutMs, (signal) => fetchImpl(`${config.modelApiBaseUrl}/tags`, {signal}));
|
||||
if (!response.ok) {
|
||||
throw new ModelServiceError("MODEL_LIST_FAILED", "Failed to load models.", 502);
|
||||
}
|
||||
modelsCache = await response.json();
|
||||
modelsCache = await withTimeout(config.modelListTimeoutMs, async (signal) => {
|
||||
const response = await fetchImpl(`${config.modelApiBaseUrl}/tags`, {signal});
|
||||
if (!response.ok) {
|
||||
throw new ModelServiceError("MODEL_LIST_FAILED", "Failed to load models.", 502);
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
modelsCacheAt = Date.now();
|
||||
lastHealth = {ok: true, checkedAt: new Date().toISOString(), error: null};
|
||||
return modelsCache;
|
||||
|
||||
@@ -7,6 +7,15 @@ const PERSONA_FIELD_LIMITS = {
|
||||
exampleDialogues: 12000
|
||||
};
|
||||
|
||||
const PERSONA_PROMPT_FIELD_LIMITS = {
|
||||
appearance: 1200,
|
||||
background: 1400,
|
||||
details: 1400,
|
||||
exampleDialogues: 1800,
|
||||
summaryField: 360,
|
||||
scenarioPrompt: 700
|
||||
};
|
||||
|
||||
const blockedPersonaPatterns = [
|
||||
/\b(child|children|kid|kids|minor|underage|under-aged|teenager|young girl|young boy|loli|shota)\b/i,
|
||||
/\b(nazi|hitler|third reich|kkk|ku klux klan|white power|nigger|nigga)\b/i,
|
||||
@@ -59,13 +68,19 @@ export function clampText(value, limit = 260) {
|
||||
return `${normalized.slice(0, limit - 3).trimEnd()}...`;
|
||||
}
|
||||
|
||||
function clampPromptText(value, limit) {
|
||||
const normalized = String(value || "").trim();
|
||||
if (normalized.length <= limit) return normalized;
|
||||
return `${normalized.slice(0, limit - 3).trimEnd()}...`;
|
||||
}
|
||||
|
||||
export function summarizePersona(persona) {
|
||||
if (!persona) return "";
|
||||
return [
|
||||
persona.name ? `Name: ${persona.name}` : null,
|
||||
persona.pronouns ? `Pronouns: ${persona.pronouns}` : null,
|
||||
persona.background ? `Background: ${persona.background}` : null,
|
||||
persona.details ? `Traits: ${persona.details}` : null
|
||||
persona.background ? `Background: ${clampText(persona.background, PERSONA_PROMPT_FIELD_LIMITS.summaryField)}` : null,
|
||||
persona.details ? `Traits: ${clampText(persona.details, PERSONA_PROMPT_FIELD_LIMITS.summaryField)}` : null
|
||||
].filter(Boolean).join(" | ");
|
||||
}
|
||||
|
||||
@@ -85,13 +100,13 @@ export function buildPersonaPrompt(persona) {
|
||||
];
|
||||
if (persona.name) lines.push(`Name: ${persona.name}`);
|
||||
if (persona.pronouns) lines.push(`Pronouns: ${persona.pronouns}`);
|
||||
if (persona.appearance) lines.push(`Appearance: ${persona.appearance}`);
|
||||
if (persona.background) lines.push(`Background: ${persona.background}`);
|
||||
if (persona.details) lines.push(`Additional Traits: ${persona.details}`);
|
||||
if (persona.appearance) lines.push(`Appearance: ${clampPromptText(persona.appearance, PERSONA_PROMPT_FIELD_LIMITS.appearance)}`);
|
||||
if (persona.background) lines.push(`Background: ${clampPromptText(persona.background, PERSONA_PROMPT_FIELD_LIMITS.background)}`);
|
||||
if (persona.details) lines.push(`Additional Traits: ${clampPromptText(persona.details, PERSONA_PROMPT_FIELD_LIMITS.details)}`);
|
||||
if (persona.example_dialogues) {
|
||||
lines.push("", "EXAMPLE DIALOGUES:");
|
||||
lines.push("Use these examples to mirror tone, cadence, and phrasing without rigidly repeating them.");
|
||||
lines.push(persona.example_dialogues);
|
||||
lines.push(clampPromptText(persona.example_dialogues, PERSONA_PROMPT_FIELD_LIMITS.exampleDialogues));
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -110,9 +125,9 @@ export function buildUserPersonaPrompt(persona) {
|
||||
];
|
||||
if (persona.name) lines.push(`Name: ${persona.name}`);
|
||||
if (persona.pronouns) lines.push(`Pronouns: ${persona.pronouns}`);
|
||||
if (persona.appearance) lines.push(`Appearance: ${persona.appearance}`);
|
||||
if (persona.background) lines.push(`Background: ${persona.background}`);
|
||||
if (persona.details) lines.push(`Additional Traits: ${persona.details}`);
|
||||
if (persona.appearance) lines.push(`Appearance: ${clampPromptText(persona.appearance, PERSONA_PROMPT_FIELD_LIMITS.appearance)}`);
|
||||
if (persona.background) lines.push(`Background: ${clampPromptText(persona.background, PERSONA_PROMPT_FIELD_LIMITS.background)}`);
|
||||
if (persona.details) lines.push(`Additional Traits: ${clampPromptText(persona.details, PERSONA_PROMPT_FIELD_LIMITS.details)}`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
@@ -128,6 +143,7 @@ export function buildRoleplaySceneSummary(assistantPersona, userPersona, scenari
|
||||
}
|
||||
|
||||
export function buildRoleplayDirectionPrompt({assistantPersona, userPersona, scenarioPrompt, sceneSummary}) {
|
||||
const scenarioSeed = clampPromptText(scenarioPrompt || "", PERSONA_PROMPT_FIELD_LIMITS.scenarioPrompt);
|
||||
const lines = [
|
||||
"ROLEPLAY DIRECTION:",
|
||||
"Create an immersive one-on-one roleplay between the assistant persona and the user.",
|
||||
@@ -149,7 +165,7 @@ export function buildRoleplayDirectionPrompt({assistantPersona, userPersona, sce
|
||||
"",
|
||||
`SCENE SUMMARY: ${sceneSummary}`
|
||||
];
|
||||
if (scenarioPrompt) lines.push(`USER SCENARIO SEED: ${scenarioPrompt}`);
|
||||
if (scenarioSeed) lines.push(`USER SCENARIO SEED: ${scenarioSeed}`);
|
||||
if (assistantPersona) lines.push("", `ASSISTANT SNAPSHOT: ${summarizePersona(assistantPersona)}`);
|
||||
if (userPersona) lines.push(`USER SNAPSHOT: ${summarizePersona(userPersona)}`);
|
||||
return lines.join("\n");
|
||||
@@ -158,8 +174,9 @@ export function buildRoleplayDirectionPrompt({assistantPersona, userPersona, sce
|
||||
export function buildRoleplayOpenerPrompt({assistantPersona, userPersona, scenarioPrompt, sceneSummary}) {
|
||||
const assistantName = assistantPersona?.name || "the assistant";
|
||||
const userName = userPersona?.name || "the user";
|
||||
const scenarioSeedText = clampPromptText(scenarioPrompt || "", PERSONA_PROMPT_FIELD_LIMITS.scenarioPrompt);
|
||||
const sceneSeed = scenarioPrompt
|
||||
? `Use this scenario seed: ${scenarioPrompt}`
|
||||
? `Use this scenario seed: ${scenarioSeedText}`
|
||||
: `Invent a fresh scenario that suits ${assistantName} and how they would realistically meet or confront ${userName}.`;
|
||||
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user