Spaces:
Runtime error
Runtime error
File size: 8,038 Bytes
1a7c116 bff1056 1a7c116 bff1056 1a7c116 bff1056 1a7c116 bff1056 1a7c116 bff1056 1a7c116 bff1056 1a7c116 bff1056 1a7c116 | 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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import { loadEncryptedJson, saveEncryptedJson } from './cryptoUtils.js';
import { isPostgresStorageMode } from './dataPaths.js';
import {
decryptJsonPayload,
encryptJsonPayload,
makeLookupToken,
pgQuery,
} from './postgres.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);
}
function promptLookup(userId) {
return makeLookupToken('system-prompt', userId);
}
function promptAad(userId) {
return `system-prompt:${userId}`;
}
async function ensureLoaded() {
if (state.loaded || isPostgresStorageMode()) 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,
};
}
async function getSqlPrompt(userId) {
const { rows } = await pgQuery(
'SELECT payload FROM system_prompts WHERE owner_lookup = $1',
[promptLookup(userId)]
);
return rows[0] ? sanitizeRecord(decryptJsonPayload(rows[0].payload, promptAad(userId))) : null;
}
export const systemPromptStore = {
async getDefaultPrompt() {
return loadDefaultPrompt();
},
async getUserPrompt(userId) {
if (!userId) return null;
if (isPostgresStorageMode()) return getSqlPrompt(userId);
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');
if (isPostgresStorageMode()) {
const record = {
userId,
markdown: normalized,
updatedAt: new Date().toISOString(),
};
await pgQuery(
`INSERT INTO system_prompts (owner_lookup, updated_at, payload)
VALUES ($1, $2, $3::jsonb)
ON CONFLICT (owner_lookup)
DO UPDATE SET updated_at = EXCLUDED.updated_at, payload = EXCLUDED.payload`,
[
promptLookup(userId),
record.updatedAt,
JSON.stringify(encryptJsonPayload(record, promptAad(userId))),
]
);
return this.getPersonalization(userId);
}
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');
if (isPostgresStorageMode()) {
await pgQuery('DELETE FROM system_prompts WHERE owner_lookup = $1', [promptLookup(userId)]);
return this.getPersonalization(userId);
}
await ensureLoaded();
delete state.prompts[userId];
await saveIndex();
return this.getPersonalization(userId);
},
};
|