feat: 新增系统提示词内容替换功能并重构常量定义

- 新增 SYSTEM_PROMPT_REPLACEMENTS 配置项,支持在系统提示词中执行顺序内容替换
- 将 MODEL_PROVIDER 等常量从 common.js 迁移到独立的 constants.js 文件
- 为所有提供商策略(OpenAI、Claude、Gemini、Grok、Forward、Codex Responses)添加系统提示词替换支持
- 更新 UI 配置界面,添加替换规则管理功能
- 更新 Grok 提供商模型列表至 4.20 版本
This commit is contained in:
hex2077 2026-04-07 00:04:00 +08:00
parent 22ce2440da
commit a069feea71
22 changed files with 296 additions and 68 deletions

View file

@ -5,6 +5,10 @@
"MODEL_PROVIDER": "gemini-cli-oauth",
"SYSTEM_PROMPT_FILE_PATH": "configs/input_system_prompt.txt",
"SYSTEM_PROMPT_MODE": "overwrite",
"SYSTEM_PROMPT_REPLACEMENTS": [
{ "old": "Old text to replace", "new": "New replacement text" },
{ "old": "AI", "new": "Smart Bot" }
],
"PROMPT_LOG_BASE_NAME": "prompt_log",
"PROMPT_LOG_MODE": "none",
"REQUEST_MAX_RETRIES": 3,

View file

@ -10,6 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
import logger from '../utils/logger.js';
import { MODEL_PROTOCOL_PREFIX, getProtocolPrefix } from '../utils/common.js';
import { ConverterFactory } from '../converters/ConverterFactory.js';
import { CONFIG } from '../core/config-manager.js';
import {
generateResponseCreated,
generateResponseInProgress,
@ -237,8 +238,8 @@ export function toOpenAIStreamChunkFromOpenAIResponses(responsesChunk, model) {
// 辅助函数导出
export async function extractAndProcessSystemMessages(messages) {
const { Utils } = await import('../converters/utils.js');
return Utils.extractSystemMessages(messages);
const { extractAndProcessSystemMessages: extract } = await import('../converters/utils.js');
return extract(messages, CONFIG?.SYSTEM_PROMPT_REPLACEMENTS);
}
export async function extractTextFromMessageContent(content) {

View file

@ -119,18 +119,49 @@ export function extractTextFromMessageContent(content) {
return '';
}
/**
* 应用系统提示词内容替换
* @param {string} content - 原始内容
* @param {Array} replacements - 替换规则数组
* @returns {string} 替换后的内容
*/
export function applySystemPromptReplacements(content, replacements = []) {
if (!content || !replacements || !Array.isArray(replacements) || replacements.length === 0) {
return content;
}
let newContent = content;
for (const replacement of replacements) {
if (replacement.old !== undefined && replacement.new !== undefined) {
if (typeof replacement.old === 'string') {
// 简单字符串全量替换
newContent = newContent.split(replacement.old).join(replacement.new);
} else if (replacement.old instanceof RegExp || (typeof replacement.old === 'object' && replacement.old !== null)) {
// 正则表达式替换
newContent = newContent.replace(replacement.old, replacement.new);
}
}
}
return newContent;
}
/**
* 提取并处理系统消息
* @param {Array} messages - 消息数组
* @param {Array} replacements - 替换规则数组可选
* @returns {{systemInstruction: Object|null, nonSystemMessages: Array}}
*/
export function extractAndProcessSystemMessages(messages) {
export function extractAndProcessSystemMessages(messages, replacements = []) {
const systemContents = [];
const nonSystemMessages = [];
for (const message of messages) {
if (message.role === 'system') {
systemContents.push(extractTextFromMessageContent(message.content));
let content = extractTextFromMessageContent(message.content);
// 应用系统提示词内容替换
content = applySystemPromptReplacements(content, replacements);
systemContents.push(content);
} else {
nonSystemMessages.push(message);
}

View file

@ -1,6 +1,7 @@
import * as fs from 'fs';
import { promises as pfs } from 'fs';
import { INPUT_SYSTEM_PROMPT_FILE, MODEL_PROVIDER } from '../utils/common.js';
import { INPUT_SYSTEM_PROMPT_FILE } from '../utils/common.js';
import { MODEL_PROVIDER } from '../utils/constants.js';
import logger from '../utils/logger.js';
export let CONFIG = {}; // Make CONFIG exportable
@ -76,6 +77,7 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP
LOGIN_MIN_INTERVAL: 5000, // 两次尝试之间的最小间隔毫秒默认1秒
PROVIDER_POOLS_FILE_PATH: null, // 新增号池配置文件路径
MAX_ERROR_COUNT: 10, // 提供商最大错误次数
SYSTEM_PROMPT_REPLACEMENTS: [], // 系统提示词内容替换规则,例如: [{"old": "AI", "new": "Bot"}, {"old": "OpenAI", "new": "Gemini"}]
SCHEDULED_HEALTH_CHECK: {
enabled: false,
interval: 600000,

View file

@ -5,7 +5,7 @@ import { handleUIApiRequests, serveStaticFiles } from '../services/ui-manager.js
import { handleAPIRequests } from '../services/api-manager.js';
import { getApiService, getProviderStatus } from '../services/service-manager.js';
import { getProviderPoolManager } from '../services/service-manager.js';
import { MODEL_PROVIDER } from '../utils/common.js';
import { MODEL_PROVIDER } from '../utils/constants.js';
import { getRegisteredProviders, isRegisteredProvider } from '../providers/adapter.js';
import { countTokensAnthropic } from '../utils/token-utils.js';
import { PROMPT_LOG_FILENAME } from '../core/config-manager.js';

View file

@ -9,7 +9,7 @@ import { IFlowApiService } from './openai/iflow-core.js';
import { CodexApiService } from './openai/codex-core.js';
import { ForwardApiService } from './forward/forward-core.js';
import { GrokApiService } from './grok/grok-core.js';
import { MODEL_PROVIDER } from '../utils/common.js';
import { MODEL_PROVIDER } from '../utils/constants.js';
import logger from '../utils/logger.js';
// 适配器注册表

View file

@ -1,6 +1,7 @@
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import logger from '../../utils/logger.js';
import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* Claude provider strategy implementation.
@ -59,7 +60,8 @@ class ClaudeStrategy extends ProviderStrategy {
? `${existingSystemText}\n${filePromptContent}`
: filePromptContent;
requestBody.system = newSystemText;
// Apply system prompt replacements
requestBody.system = applySystemPromptReplacements(newSystemText, config.SYSTEM_PROMPT_REPLACEMENTS);
logger.info(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'claude'.`);
return requestBody;

View file

@ -1,4 +1,7 @@
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import logger from '../../utils/logger.js';
import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* Forward provider strategy implementation.
@ -40,8 +43,35 @@ class ForwardStrategy extends ProviderStrategy {
}
async applySystemPromptFromFile(config, requestBody) {
// For forwarder, we might want to skip automatic system prompt application
// to keep it transparent, but let's follow the base implementation just in case.
if (!config.SYSTEM_PROMPT_FILE_PATH) {
return requestBody;
}
const filePromptContent = config.SYSTEM_PROMPT_CONTENT;
if (filePromptContent === null) {
return requestBody;
}
const existingSystemText = extractSystemPromptFromRequestBody(requestBody, MODEL_PROTOCOL_PREFIX.OPENAI);
const newSystemText = config.SYSTEM_PROMPT_MODE === 'append' && existingSystemText
? `${existingSystemText}\n${filePromptContent}`
: filePromptContent;
// Apply system prompt replacements
const finalSystemText = applySystemPromptReplacements(newSystemText, config.SYSTEM_PROMPT_REPLACEMENTS);
if (!requestBody.messages) {
requestBody.messages = [];
}
const systemMessageIndex = requestBody.messages.findIndex(m => m.role === 'system');
if (systemMessageIndex !== -1) {
requestBody.messages[systemMessageIndex].content = finalSystemText;
} else {
requestBody.messages.unshift({ role: 'system', content: finalSystemText });
}
logger.info(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'forward'.`);
return requestBody;
}

View file

@ -1,6 +1,7 @@
import { API_ACTIONS, extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import logger from '../../utils/logger.js';
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* Gemini provider strategy implementation.
@ -52,7 +53,10 @@ class GeminiStrategy extends ProviderStrategy {
? `${existingSystemText}\n${filePromptContent}`
: filePromptContent;
requestBody.systemInstruction = { parts: [{ text: newSystemText }] };
// Apply system prompt replacements
const finalSystemText = applySystemPromptReplacements(newSystemText, config.SYSTEM_PROMPT_REPLACEMENTS);
requestBody.systemInstruction = { parts: [{ text: finalSystemText }] };
if (requestBody.system_instruction) {
delete requestBody.system_instruction;
}

View file

@ -1,6 +1,7 @@
import { API_ACTIONS, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import logger from '../../utils/logger.js';
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* Grok provider strategy implementation.
@ -37,10 +38,13 @@ class GrokStrategy extends ProviderStrategy {
// Here we can prepend it if needed, or handle it during request conversion.
// Since requestBody already contains the converted message, we might need to prepend it here.
// Apply system prompt replacements to file prompt content
const finalFilePrompt = applySystemPromptReplacements(filePromptContent, config.SYSTEM_PROMPT_REPLACEMENTS);
const existingMessage = requestBody.message || "";
const newSystemText = config.SYSTEM_PROMPT_MODE === 'append'
? `${existingMessage}\n\nSystem: ${filePromptContent}`
: `System: ${filePromptContent}\n\n${existingMessage}`;
? `${existingMessage}\n\nSystem: ${finalFilePrompt}`
: `System: ${finalFilePrompt}\n\n${existingMessage}`;
requestBody.message = newSystemText;
logger.info(`[System Prompt] Applied system prompt for Grok in '${config.SYSTEM_PROMPT_MODE}' mode.`);

View file

@ -1,6 +1,7 @@
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import logger from '../../utils/logger.js';
import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* OpenAI Responses API strategy implementation.
@ -65,16 +66,19 @@ class CodexResponsesAPIStrategy extends ProviderStrategy {
return requestBody;
}
// Apply system prompt replacements
const finalSystemText = applySystemPromptReplacements(filePromptContent, config.SYSTEM_PROMPT_REPLACEMENTS);
// In Responses API, system instructions are typically passed in 'instructions' field
// or in the input array with role: 'developer'
requestBody.instructions = requestBody.instructions || filePromptContent;
requestBody.instructions = requestBody.instructions || finalSystemText;
// If using instructions field is not desired, append to input array instead
if (!requestBody.instructions || config.SYSTEM_PROMPT_MODE === 'append') {
if (typeof requestBody.input === 'string') {
// Convert to array format to add system message
requestBody.input = [
{ type: 'message', role: 'developer', content: filePromptContent },
{ type: 'message', role: 'developer', content: finalSystemText },
{ type: 'message', role: 'user', content: requestBody.input }
];
} else if (Array.isArray(requestBody.input)) {
@ -84,17 +88,17 @@ class CodexResponsesAPIStrategy extends ProviderStrategy {
);
if (systemMessageIndex !== -1) {
requestBody.input[systemMessageIndex].content = filePromptContent;
requestBody.input[systemMessageIndex].content = finalSystemText;
} else {
requestBody.input.unshift({ type: 'message', role: 'developer', content: filePromptContent });
requestBody.input.unshift({ type: 'message', role: 'developer', content: finalSystemText });
}
} else {
// If input is not defined, initialize with system message
requestBody.input = [{ type: 'message', role: 'developer', content: filePromptContent }];
requestBody.input = [{ type: 'message', role: 'developer', content: finalSystemText }];
}
} else if (requestBody.instructions) {
// If system prompt mode is not append, then replace the instructions
requestBody.instructions = filePromptContent;
requestBody.instructions = finalSystemText;
}
logger.info(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'responses'.`);

View file

@ -1,6 +1,7 @@
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import logger from '../../utils/logger.js';
import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* OpenAI Responses API strategy implementation.
@ -65,16 +66,23 @@ class ResponsesAPIStrategy extends ProviderStrategy {
return requestBody;
}
const newSystemText = config.SYSTEM_PROMPT_MODE === 'append' && existingSystemText
? `${existingSystemText}\n${filePromptContent}`
: filePromptContent;
// Apply system prompt replacements
const finalSystemText = applySystemPromptReplacements(newSystemText, config.SYSTEM_PROMPT_REPLACEMENTS);
// In Responses API, system instructions are typically passed in 'instructions' field
// or in the input array with role: 'system'
requestBody.instructions = requestBody.instructions || filePromptContent;
requestBody.instructions = requestBody.instructions || finalSystemText;
// If using instructions field is not desired, append to input array instead
if (!requestBody.instructions || config.SYSTEM_PROMPT_MODE === 'append') {
if (typeof requestBody.input === 'string') {
// Convert to array format to add system message
requestBody.input = [
{ role: 'system', content: filePromptContent },
{ role: 'system', content: finalSystemText },
{ role: 'user', content: requestBody.input }
];
} else if (Array.isArray(requestBody.input)) {
@ -84,17 +92,17 @@ class ResponsesAPIStrategy extends ProviderStrategy {
);
if (systemMessageIndex !== -1) {
requestBody.input[systemMessageIndex].content = filePromptContent;
requestBody.input[systemMessageIndex].content = finalSystemText;
} else {
requestBody.input.unshift({ role: 'system', content: filePromptContent });
requestBody.input.unshift({ role: 'system', content: finalSystemText });
}
} else {
// If input is not defined, initialize with system message
requestBody.input = [{ role: 'system', content: filePromptContent }];
requestBody.input = [{ role: 'system', content: finalSystemText }];
}
} else if (requestBody.instructions) {
// If system prompt mode is not append, then replace the instructions
requestBody.instructions = filePromptContent;
requestBody.instructions = finalSystemText;
}
logger.info(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'responses'.`);

View file

@ -1,6 +1,7 @@
import { ProviderStrategy } from '../../utils/provider-strategy.js';
import logger from '../../utils/logger.js';
import { extractSystemPromptFromRequestBody, MODEL_PROTOCOL_PREFIX } from '../../utils/common.js';
import { applySystemPromptReplacements } from '../../converters/utils.js';
/**
* OpenAI provider strategy implementation.
@ -61,14 +62,17 @@ class OpenAIStrategy extends ProviderStrategy {
? `${existingSystemText}\n${filePromptContent}`
: filePromptContent;
// Apply system prompt replacements
const finalSystemText = applySystemPromptReplacements(newSystemText, config.SYSTEM_PROMPT_REPLACEMENTS);
if (!requestBody.messages) {
requestBody.messages = [];
}
const systemMessageIndex = requestBody.messages.findIndex(m => m.role === 'system');
if (systemMessageIndex !== -1) {
requestBody.messages[systemMessageIndex].content = newSystemText;
requestBody.messages[systemMessageIndex].content = finalSystemText;
} else {
requestBody.messages.unshift({ role: 'system', content: newSystemText });
requestBody.messages.unshift({ role: 'system', content: finalSystemText });
}
logger.info(`[System Prompt] Applied system prompt from ${config.SYSTEM_PROMPT_FILE_PATH} in '${config.SYSTEM_PROMPT_MODE}' mode for provider 'openai'.`);

View file

@ -94,21 +94,20 @@ export const PROVIDER_MODELS = {
],
'forward-api': [],
'grok-custom': [
'grok-3',
'grok-3-mini',
'grok-3-thinking',
'grok-4',
'grok-4-mini',
'grok-4-thinking',
'grok-4-heavy',
'grok-4.1-mini',
'grok-4.1-fast',
'grok-4.1-expert',
'grok-4.1-thinking',
'grok-4.20-beta',
'grok-4.20',
'grok-4.20-auto',
'grok-4.20-fast',
'grok-4.20-expert',
'grok-4.20-heavy',
'grok-imagine-1.0',
'grok-imagine-1.0-edit',
'grok-imagine-1.0-video'
'grok-4.20-nsfw',
'grok-4.20-auto-nsfw',
'grok-4.20-fast-nsfw',
'grok-4.20-expert-nsfw',
'grok-4.20-heavy-nsfw',
'grok-imagine-1.0-nsfw',
'grok-imagine-1.0-edit-nsfw'
]
};

View file

@ -13,7 +13,7 @@ import {
getFileName,
formatSystemPath
} from '../utils/provider-utils.js';
import { MODEL_PROVIDER } from '../utils/common.js';
import { MODEL_PROVIDER } from '../utils/constants.js';
// 存储 ProviderPoolManager 实例
let providerPoolManager = null;

View file

@ -77,6 +77,7 @@ export async function handleGetConfig(req, res, currentConfig) {
LOGIN_EXPIRY: currentConfig.LOGIN_EXPIRY,
PROVIDER_POOLS_FILE_PATH: currentConfig.PROVIDER_POOLS_FILE_PATH,
MAX_ERROR_COUNT: currentConfig.MAX_ERROR_COUNT,
SYSTEM_PROMPT_REPLACEMENTS: currentConfig.SYSTEM_PROMPT_REPLACEMENTS,
WARMUP_TARGET: currentConfig.WARMUP_TARGET,
REFRESH_CONCURRENCY_PER_PROVIDER: currentConfig.REFRESH_CONCURRENCY_PER_PROVIDER,
providerFallbackChain: currentConfig.providerFallbackChain,
@ -153,6 +154,11 @@ export async function handleUpdateConfig(req, res, currentConfig) {
}
}
if (newConfig.SYSTEM_PROMPT_MODE !== undefined) currentConfig.SYSTEM_PROMPT_MODE = newConfig.SYSTEM_PROMPT_MODE;
if (newConfig.SYSTEM_PROMPT_REPLACEMENTS !== undefined) {
if (Array.isArray(newConfig.SYSTEM_PROMPT_REPLACEMENTS)) {
currentConfig.SYSTEM_PROMPT_REPLACEMENTS = newConfig.SYSTEM_PROMPT_REPLACEMENTS;
}
}
if (newConfig.PROMPT_LOG_BASE_NAME !== undefined) currentConfig.PROMPT_LOG_BASE_NAME = newConfig.PROMPT_LOG_BASE_NAME;
if (newConfig.PROMPT_LOG_MODE !== undefined) currentConfig.PROMPT_LOG_MODE = newConfig.PROMPT_LOG_MODE;
if (newConfig.REQUEST_MAX_RETRIES !== undefined) {
@ -288,6 +294,7 @@ export async function handleUpdateConfig(req, res, currentConfig) {
MODEL_PROVIDER: currentConfig.MODEL_PROVIDER,
SYSTEM_PROMPT_FILE_PATH: currentConfig.SYSTEM_PROMPT_FILE_PATH,
SYSTEM_PROMPT_MODE: currentConfig.SYSTEM_PROMPT_MODE,
SYSTEM_PROMPT_REPLACEMENTS: currentConfig.SYSTEM_PROMPT_REPLACEMENTS,
PROMPT_LOG_BASE_NAME: currentConfig.PROMPT_LOG_BASE_NAME,
PROMPT_LOG_MODE: currentConfig.PROMPT_LOG_MODE,
REQUEST_MAX_RETRIES: currentConfig.REQUEST_MAX_RETRIES,

View file

@ -1,3 +1,4 @@
export { MODEL_PROTOCOL_PREFIX, MODEL_PROVIDER } from './constants.js';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as http from 'http'; // Add http for IncomingMessage and ServerResponse types
@ -6,6 +7,7 @@ import logger from './logger.js';
import { convertData, getOpenAIStreamChunkStop } from '../convert/convert.js';
import { ProviderStrategyFactory } from './provider-strategies.js';
import { getPluginManager } from '../core/plugin-manager.js';
import { MODEL_PROTOCOL_PREFIX, MODEL_PROVIDER } from './constants.js';
// ==================== 网络错误处理 ====================
@ -49,33 +51,6 @@ export const API_ACTIONS = {
STREAM_GENERATE_CONTENT: 'streamGenerateContent',
};
export const MODEL_PROTOCOL_PREFIX = {
// Model provider constants
GEMINI: 'gemini',
OPENAI: 'openai',
OPENAI_RESPONSES: 'openaiResponses',
CLAUDE: 'claude',
CODEX: 'codex',
FORWARD: 'forward',
GROK: 'grok',
}
export const MODEL_PROVIDER = {
// Model provider constants
GEMINI_CLI: 'gemini-cli-oauth',
ANTIGRAVITY: 'gemini-antigravity',
OPENAI_CUSTOM: 'openai-custom',
OPENAI_CUSTOM_RESPONSES: 'openaiResponses-custom',
CLAUDE_CUSTOM: 'claude-custom',
KIRO_API: 'claude-kiro-oauth',
QWEN_API: 'openai-qwen-oauth',
IFLOW_API: 'openai-iflow',
CODEX_API: 'openai-codex-oauth',
FORWARD_API: 'forward-api',
GROK_CUSTOM: 'grok-custom',
AUTO: 'auto',
}
import {
usesManagedModelList,
getConfiguredSupportedModels

View file

@ -40,3 +40,30 @@ export const RETRY = {
// 最大重试次数
MAX_RETRIES: 100
};
// 协议前缀常量
export const MODEL_PROTOCOL_PREFIX = {
GEMINI: 'gemini',
OPENAI: 'openai',
OPENAI_RESPONSES: 'openaiResponses',
CLAUDE: 'claude',
CODEX: 'codex',
FORWARD: 'forward',
GROK: 'grok',
};
// 提供商标识符常量
export const MODEL_PROVIDER = {
GEMINI_CLI: 'gemini-cli-oauth',
ANTIGRAVITY: 'gemini-antigravity',
OPENAI_CUSTOM: 'openai-custom',
OPENAI_CUSTOM_RESPONSES: 'openaiResponses-custom',
CLAUDE_CUSTOM: 'claude-custom',
KIRO_API: 'claude-kiro-oauth',
QWEN_API: 'openai-qwen-oauth',
IFLOW_API: 'openai-iflow',
CODEX_API: 'openai-codex-oauth',
FORWARD_API: 'forward-api',
GROK_CUSTOM: 'grok-custom',
AUTO: 'auto',
};

View file

@ -126,6 +126,47 @@ function updatePinnedStatus(container) {
});
}
/**
* 初始化系统提示词替换规则 UI
*/
function initReplacementsUI() {
const addBtn = document.getElementById('addReplacementBtn');
if (addBtn && !addBtn.dataset.listenerAttached) {
addBtn.addEventListener('click', () => {
addReplacementRow('', '');
});
addBtn.dataset.listenerAttached = 'true';
}
}
/**
* 添加一条替换规则行
* @param {string} oldVal - 查找内容
* @param {string} newVal - 替换内容
*/
function addReplacementRow(oldVal = '', newVal = '') {
const container = document.getElementById('systemPromptReplacementsContainer');
if (!container) return;
const row = document.createElement('div');
row.className = 'replacement-row';
row.innerHTML = `
<input type="text" class="form-control replacement-old" placeholder="${t('config.advanced.replacement.old')}" value="${oldVal}">
<input type="text" class="form-control replacement-new" placeholder="${t('config.advanced.replacement.new')}" value="${newVal}">
<button type="button" class="remove-replacement-btn" title="${t('config.advanced.replacement.remove')}">
<i class="fas fa-trash-alt"></i>
</button>
`;
// 绑定删除按钮事件
const removeBtn = row.querySelector('.remove-replacement-btn');
removeBtn.addEventListener('click', () => {
row.remove();
});
container.appendChild(row);
}
/**
* 加载配置
*/
@ -133,6 +174,18 @@ async function loadConfiguration() {
try {
const data = await window.apiClient.get('/config');
// 初始化替换规则 UI
initReplacementsUI();
const replacementsContainer = document.getElementById('systemPromptReplacementsContainer');
if (replacementsContainer) {
replacementsContainer.innerHTML = '';
if (data.SYSTEM_PROMPT_REPLACEMENTS && Array.isArray(data.SYSTEM_PROMPT_REPLACEMENTS)) {
data.SYSTEM_PROMPT_REPLACEMENTS.forEach(r => {
addReplacementRow(r.old || '', r.new || '');
});
}
}
// 基础配置
const apiKeyEl = document.getElementById('apiKey');
const hostEl = document.getElementById('host');
@ -380,6 +433,19 @@ async function saveConfiguration() {
// 保存高级配置参数
config.SYSTEM_PROMPT_FILE_PATH = document.getElementById('systemPromptFilePath')?.value || 'configs/input_system_prompt.txt';
config.SYSTEM_PROMPT_MODE = document.getElementById('systemPromptMode')?.value || 'append';
// 收集系统提示词内容替换规则
const replacements = [];
const replacementRows = document.querySelectorAll('.replacement-row');
replacementRows.forEach(row => {
const oldVal = row.querySelector('.replacement-old')?.value || '';
const newVal = row.querySelector('.replacement-new')?.value || '';
if (oldVal) {
replacements.push({ old: oldVal, new: newVal });
}
});
config.SYSTEM_PROMPT_REPLACEMENTS = replacements;
config.PROMPT_LOG_BASE_NAME = document.getElementById('promptLogBaseName')?.value || '';
config.PROMPT_LOG_MODE = document.getElementById('promptLogMode')?.value || '';
config.REQUEST_MAX_RETRIES = parseInt(document.getElementById('requestMaxRetries')?.value || 3);

View file

@ -332,6 +332,12 @@ const translations = {
'config.advanced.modelFallbackMappingInvalid': 'Model Fallback 映射配置格式无效,请输入有效的 JSON',
'config.advanced.systemPrompt': '系统提示',
'config.advanced.systemPromptPlaceholder': '输入系统提示...',
'config.advanced.systemPromptReplacements': '系统提示词内容替换',
'config.advanced.systemPromptReplacementsNote': '按顺序逐条对系统提示词(包括请求中的和文件中的)执行内容替换。',
'config.advanced.replacement.old': '查找内容 (旧)',
'config.advanced.replacement.new': '替换内容 (新)',
'config.advanced.replacement.add': '添加替换规则',
'config.advanced.replacement.remove': '删除',
'config.advanced.adminPassword': '后台登录密码',
'config.advanced.adminPasswordPlaceholder': '设置后台登录密码(留空则不修改)',
'config.advanced.adminPasswordNote': '用于保护管理控制台的访问,修改后需要重新登录',
@ -1220,6 +1226,12 @@ const translations = {
'config.advanced.modelFallbackMappingInvalid': 'Invalid Model Fallback mapping config format, please enter valid JSON',
'config.advanced.systemPrompt': 'System Prompt',
'config.advanced.systemPromptPlaceholder': 'Enter system prompt...',
'config.advanced.systemPromptReplacements': 'System Prompt Replacements',
'config.advanced.systemPromptReplacementsNote': 'Executes sequential content replacements for the system prompt (both from requests and files).',
'config.advanced.replacement.old': 'Find (Old)',
'config.advanced.replacement.new': 'Replace (New)',
'config.advanced.replacement.add': 'Add Rule',
'config.advanced.replacement.remove': 'Remove',
'config.advanced.adminPassword': 'Admin Password',
'config.advanced.adminPasswordPlaceholder': 'Set admin password (leave empty to keep unchanged)',
'config.advanced.adminPasswordNote': 'Used to protect management console access, requires re-login after modification',

View file

@ -300,6 +300,43 @@ input:checked + .toggle-slider:before {
box-shadow: 0 4px 12px var(--primary-40);
}
/* 系统提示词替换规则样式 */
.replacements-container {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.replacement-row {
display: grid;
grid-template-columns: 1fr 1fr auto;
gap: 0.75rem;
align-items: center;
background: var(--bg-tertiary);
padding: 0.75rem;
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
}
.replacement-row .form-control {
margin-bottom: 0;
}
.remove-replacement-btn {
color: var(--danger-color);
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
transition: var(--transition);
}
.remove-replacement-btn:hover {
color: #dc2626;
transform: scale(1.1);
}
/* 提供商标签选择器样式 */
.provider-tags {
display: flex;

View file

@ -365,6 +365,17 @@
<label for="systemPrompt" data-i18n="config.advanced.systemPrompt">系统提示内容</label>
<textarea id="systemPrompt" class="form-control" rows="4"></textarea>
</div>
<!-- 系统提示词替换规则 -->
<div class="form-group">
<label data-i18n="config.advanced.systemPromptReplacements">系统提示词内容替换</label>
<div id="systemPromptReplacementsContainer" class="replacements-container">
<!-- 动态添加替换规则行 -->
</div>
<button type="button" id="addReplacementBtn" class="btn btn-sm btn-outline-primary mt-2">
<i class="fas fa-plus"></i> <span data-i18n="config.advanced.replacement.add">添加替换规则</span>
</button>
<small class="form-text" data-i18n="config.advanced.systemPromptReplacementsNote">按顺序逐条对系统提示词(包括请求中的和文件中的)执行内容替换。</small>
</div>
<div class="form-group">
<label for="adminPassword" data-i18n="config.advanced.adminPassword">后台登录密码</label>
<div class="password-input-wrapper">