File size: 6,547 Bytes
0f84d64 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | 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);
},
};
|