diff --git a/configs/config.json.example b/configs/config.json.example index a3f5904..6a4bf1d 100644 --- a/configs/config.json.example +++ b/configs/config.json.example @@ -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, diff --git a/src/convert/convert.js b/src/convert/convert.js index bb74633..9467d69 100644 --- a/src/convert/convert.js +++ b/src/convert/convert.js @@ -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) { diff --git a/src/converters/utils.js b/src/converters/utils.js index 1b5933c..96b2a78 100644 --- a/src/converters/utils.js +++ b/src/converters/utils.js @@ -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); } diff --git a/src/core/config-manager.js b/src/core/config-manager.js index 0af5b88..60f9bc8 100644 --- a/src/core/config-manager.js +++ b/src/core/config-manager.js @@ -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, diff --git a/src/handlers/request-handler.js b/src/handlers/request-handler.js index 57e830b..c9c1f2a 100644 --- a/src/handlers/request-handler.js +++ b/src/handlers/request-handler.js @@ -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'; diff --git a/src/providers/adapter.js b/src/providers/adapter.js index 7f9436c..de12675 100644 --- a/src/providers/adapter.js +++ b/src/providers/adapter.js @@ -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'; // 适配器注册表 diff --git a/src/providers/claude/claude-strategy.js b/src/providers/claude/claude-strategy.js index 1de7d01..c3e14a6 100644 --- a/src/providers/claude/claude-strategy.js +++ b/src/providers/claude/claude-strategy.js @@ -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; diff --git a/src/providers/forward/forward-strategy.js b/src/providers/forward/forward-strategy.js index b118741..10c2df4 100644 --- a/src/providers/forward/forward-strategy.js +++ b/src/providers/forward/forward-strategy.js @@ -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; } diff --git a/src/providers/gemini/gemini-strategy.js b/src/providers/gemini/gemini-strategy.js index 6b2071d..1d30da4 100644 --- a/src/providers/gemini/gemini-strategy.js +++ b/src/providers/gemini/gemini-strategy.js @@ -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; } diff --git a/src/providers/grok/grok-strategy.js b/src/providers/grok/grok-strategy.js index 623100a..e03702d 100644 --- a/src/providers/grok/grok-strategy.js +++ b/src/providers/grok/grok-strategy.js @@ -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.`); diff --git a/src/providers/openai/codex-responses-strategy.js b/src/providers/openai/codex-responses-strategy.js index 21cacbb..662bbe3 100644 --- a/src/providers/openai/codex-responses-strategy.js +++ b/src/providers/openai/codex-responses-strategy.js @@ -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'.`); diff --git a/src/providers/openai/openai-responses-strategy.js b/src/providers/openai/openai-responses-strategy.js index 27f9080..910d823 100644 --- a/src/providers/openai/openai-responses-strategy.js +++ b/src/providers/openai/openai-responses-strategy.js @@ -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'.`); diff --git a/src/providers/openai/openai-strategy.js b/src/providers/openai/openai-strategy.js index 801b3d4..098ed68 100644 --- a/src/providers/openai/openai-strategy.js +++ b/src/providers/openai/openai-strategy.js @@ -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'.`); diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index d5abbab..6397e61 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -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' ] }; diff --git a/src/services/service-manager.js b/src/services/service-manager.js index e574b54..27ce3b7 100644 --- a/src/services/service-manager.js +++ b/src/services/service-manager.js @@ -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; diff --git a/src/ui-modules/config-api.js b/src/ui-modules/config-api.js index 0460b96..536380a 100644 --- a/src/ui-modules/config-api.js +++ b/src/ui-modules/config-api.js @@ -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, diff --git a/src/utils/common.js b/src/utils/common.js index 6c85584..e0375b0 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -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 diff --git a/src/utils/constants.js b/src/utils/constants.js index 2ff652c..0429b6a 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -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', +}; diff --git a/static/app/config-manager.js b/static/app/config-manager.js index 581eaae..6d2b7c6 100644 --- a/static/app/config-manager.js +++ b/static/app/config-manager.js @@ -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 = ` + + + + `; + + // 绑定删除按钮事件 + 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); diff --git a/static/app/i18n.js b/static/app/i18n.js index 5c7f6a6..ad6cceb 100644 --- a/static/app/i18n.js +++ b/static/app/i18n.js @@ -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', diff --git a/static/components/section-config.css b/static/components/section-config.css index 42992d9..e503c66 100644 --- a/static/components/section-config.css +++ b/static/components/section-config.css @@ -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; diff --git a/static/components/section-config.html b/static/components/section-config.html index 8308017..7bba76d 100644 --- a/static/components/section-config.html +++ b/static/components/section-config.html @@ -365,6 +365,17 @@ + +