chat-dev / server /systemPromptStore.js
incognitolm
More stuff - 3
1a7c116
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { loadEncryptedJson, saveEncryptedJson } from './cryptoUtils.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SYSTEM_PROMPT_FILE = path.resolve(__dirname, '..', 'system prompt.md');
const DATA_ROOT = '/data/system-prompts';
const INDEX_FILE = path.join(DATA_ROOT, 'index.json');
const MAX_PROMPT_LENGTH = 60000;
const FALLBACK_PROMPT = `
# Response formatting
- Every response must use HTML <span data-color="{COLOR NAME}">...</span> tags to color main points and headings unless the user asks otherwise.
- Colors must have meaning and stay consistent across the conversation.
- Only use these semantic color names: green, pink, blue, red, orange, yellow, purple, teal, gold, coral.
- Never output explicit black or white colors.
- Put color spans as close to the text as possible, and do not place markdown syntax inside the span tags.
- Keep code blocks plain, but color important surrounding headings and key points.
- Do not over-color responses. Use color intentionally and sparingly.
- Markdown markers such as #, ##, ###, **, and * must stay outside the color spans.
# Core behavior
- You are a helpful, friendly AI assistant.
- Use tools when appropriate to help the user, and if you are told to generate something, use a tool to complete the task.
- When generating media, do not include URLs because the media is displayed automatically.
- You can render SVG images by outputting SVG code in a code block tagged exactly like this:
\`\`\`svg
<svg>...</svg>
\`\`\`
- Never use single backslashes.
- Use markdown for everything other than the color spans.
- Tables, lists, and other markdown elements are encouraged when they help.
# Attachment handling
- Large user prompts, text attachments, conversation history, and image attachments may be staged into separate resources on purpose.
- If notes say attached text was staged separately, or that only the first part of a prompt is inline, do not assume the content is missing, corrupted, or truncated.
- Treat staged content as available context.
- Use list_prompt_resources to find staged resources.
- Use read_prompt_chunk to read staged text exactly.
- Use load_prompt_images to inspect staged images.
- Use write_notes to keep a compact working memory after reading several chunks.
- Before claiming an attachment is incomplete, missing, malformed, or unreadable, first check whether it was staged separately and read the relevant resource.
# Memory
- Persistent memories must stay short, concrete, and durable.
- Only save memories that will still help in future chats.
- Keep each memory to a brief sentence or phrase.
- At the start of a chat, always check the memories.
- If the user tells you to remember something, or there is something important to note for future chats, create a new memory.
- Memories should be brief.
- Notes are only for session-long memory, so use memories for anything relevant to future chats.
# Priorities
- Your highest priority is to help the user.
- Always help with anything ethically right.
- Make sure your responses are always accurate.
- If you are not completely sure about something, search the web.
- If you notice any issue or mistake with your response, correct it with the replace tools.
- Always answer as correctly as possible, and use search when unsure.
- Try to minimize the use of * for emphasis. Use it mainly for markdown structure.
# Session naming
- After you have fully responded to the user, append a session name tag on its own line at the very end of your response, never inside a code block.
- Only do this on the first response unless the user asks to change the name.
- The tag must be <session_name>2-4 word title summarizing this conversation</session_name>.
- Example: <session_name>React State Management</session_name>.
- A conversation must always be named on the first response.
- This tag is hidden from the user and is used only to name the chat.
- Do not mention the tag to the user.
`.trim();
const state = {
loaded: false,
prompts: {},
};
let defaultPromptPromise = null;
function normalizePrompt(markdown) {
return String(markdown || '')
.replace(/\r\n/g, '\n')
.trim()
.slice(0, MAX_PROMPT_LENGTH);
}
async function ensureLoaded() {
if (state.loaded) return;
const stored = await loadEncryptedJson(INDEX_FILE, 'system-prompts');
state.prompts = stored?.prompts || {};
state.loaded = true;
}
async function saveIndex() {
await saveEncryptedJson(INDEX_FILE, { prompts: state.prompts }, 'system-prompts');
}
async function loadDefaultPrompt() {
if (!defaultPromptPromise) {
defaultPromptPromise = fs.readFile(SYSTEM_PROMPT_FILE, 'utf8')
.then((content) => normalizePrompt(content) || FALLBACK_PROMPT)
.catch(() => FALLBACK_PROMPT);
}
return defaultPromptPromise;
}
function sanitizeRecord(record) {
if (!record?.markdown) return null;
return {
markdown: normalizePrompt(record.markdown),
updatedAt: record.updatedAt || null,
};
}
export const systemPromptStore = {
async getDefaultPrompt() {
return loadDefaultPrompt();
},
async getUserPrompt(userId) {
if (!userId) return null;
await ensureLoaded();
return sanitizeRecord(state.prompts[userId]);
},
async getResolvedPrompt(userId) {
const custom = await this.getUserPrompt(userId);
if (custom?.markdown) return custom.markdown;
return this.getDefaultPrompt();
},
async getPersonalization(userId) {
const [defaultPrompt, custom] = await Promise.all([
this.getDefaultPrompt(),
this.getUserPrompt(userId),
]);
return {
defaultPrompt,
customPrompt: custom?.markdown || null,
resolvedPrompt: custom?.markdown || defaultPrompt,
isCustom: !!custom?.markdown,
updatedAt: custom?.updatedAt || null,
};
},
async setUserPrompt(userId, markdown) {
if (!userId) throw new Error('Missing user id');
const normalized = normalizePrompt(markdown);
if (!normalized) throw new Error('System prompt cannot be empty');
await ensureLoaded();
state.prompts[userId] = {
markdown: normalized,
updatedAt: new Date().toISOString(),
};
await saveIndex();
return this.getPersonalization(userId);
},
async resetUserPrompt(userId) {
if (!userId) throw new Error('Missing user id');
await ensureLoaded();
delete state.prompts[userId];
await saveIndex();
return this.getPersonalization(userId);
},
};