import { buildDefaultHermesEmployeeForm, isHermesEmployeeSettingsReady, mergeHermesEmployeeForm } from './hermesEmployeeSettingsModel.js' export const SETTINGS_STORAGE_KEY = 'x-financial-settings-draft' export const CURRENT_YEAR = new Date().getFullYear() export const CUSTOM_OPENAI_PROVIDER = 'Custom OpenAI Compatible' export const MODEL_SECRET_MASK = '********' export const RENDER_SECRET_MASK = '********' export const SECTION_DEFINITIONS = [ { id: 'profile', label: '企业信息', title: '系统基本信息', desc: '公司名称、品牌与版权', longDesc: '统一维护企业名称、系统显示名称和版权信息,保存后会同步更新当前系统品牌展示。', actionLabel: '保存企业信息' }, { id: 'appearance', label: '界面皮肤', title: '界面皮肤与主色', desc: '整体主色与控件观感', longDesc: '设置当前浏览器的界面主色。默认使用浅蓝企业主题,后续可扩展为企业级统一下发。', actionLabel: '保存皮肤设置' }, { id: 'admin', label: '管理员安全', title: '管理员账号与安全策略', desc: '账号、密码与登录安全', longDesc: '集中管理管理员账号、邮箱和登录安全策略。密码仅在当前输入时可见,不会写入浏览器草稿。', actionLabel: '保存安全设置' }, { id: 'session', label: '会话设置', title: '会话留存设置', desc: '会话保留天数', longDesc: '统一配置智能体会话的保留天数。超过保留期的历史会话会在后端清理,避免上下文和管理成本无限增长。', actionLabel: '保存会话设置' }, { id: 'hermes', label: '数字员工设置', title: '数字员工设置', desc: '自动任务', longDesc: '选择需要自动执行的任务,并设置每天的执行时间。无需了解 Cron 或复杂调度规则。', actionLabel: '保存数字员工设置' }, { id: 'llm', label: '大语言模型', title: '模型接入配置', desc: '主模型、备份模型与检索模型', longDesc: '集中维护主模型、备份模型、Embedding 模型和 Reranker 模型的接入参数,供 AI 助手和检索链路调用。', actionLabel: '保存模型配置' }, { id: 'rendering', label: '文件渲染', title: '文件渲染', desc: '文档预览服务与访问密钥', longDesc: '维护文件渲染开关、文档服务对外地址和 JWT 密钥,后端回调地址继续由部署配置管理。', actionLabel: '保存文件渲染配置' }, { id: 'logs', label: '日志策略', title: '日志与审计策略', desc: '日志级别、留存与脱敏', longDesc: '定义系统日志级别、留存周期和审计策略,保证问题排查和合规审计可追溯。', actionLabel: '保存日志策略' }, { id: 'mail', label: '邮箱设置', title: '邮箱通知配置', desc: 'SMTP 与通知投递策略', longDesc: '维护系统邮件发送配置和通知投递策略,审批、告警和摘要邮件都会依赖这里的设置。', actionLabel: '保存邮箱配置' } ] export const LOG_LEVELS = ['DEBUG', 'INFO', 'WARN', 'ERROR'] export const PROVIDER_OPTIONS = [ 'MiniMax', 'GLM', 'Kimi', 'Ali', 'Codex', 'Claude', 'Gemini', CUSTOM_OPENAI_PROVIDER ] export const PROVIDER_ENDPOINTS = { MiniMax: 'https://api.minimaxi.com/v1', GLM: 'https://open.bigmodel.cn/api/paas/v4/', Kimi: 'https://api.moonshot.ai/v1', Ali: 'https://dashscope.aliyuncs.com/compatible-mode/v1', Codex: 'https://api.openai.com/v1', Claude: 'https://api.anthropic.com/v1/', Gemini: 'https://generativelanguage.googleapis.com/v1beta/openai/', [CUSTOM_OPENAI_PROVIDER]: '' } export const RERANKER_PROVIDER_ENDPOINTS = { Ali: 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank', [CUSTOM_OPENAI_PROVIDER]: '' } export const LEGACY_PROVIDER_MAP = { 'OpenAI Compatible': 'Codex', 'Azure OpenAI': CUSTOM_OPENAI_PROVIDER, Ollama: CUSTOM_OPENAI_PROVIDER, '自定义网关': CUSTOM_OPENAI_PROVIDER } export const MODEL_TEST_CONFIGS = { main: { label: '主模型', providerKey: 'mainProvider', modelKey: 'mainModel', endpointKey: 'mainEndpoint', apiKeyKey: 'mainApiKey', capability: 'chat' }, backup: { label: '备份模型', providerKey: 'backupProvider', modelKey: 'backupModel', endpointKey: 'backupEndpoint', apiKeyKey: 'backupApiKey', capability: 'chat' }, embedding: { label: 'Embedding 模型', providerKey: 'embeddingProvider', modelKey: 'embeddingModel', endpointKey: 'embeddingEndpoint', apiKeyKey: 'embeddingApiKey', capability: 'embedding' }, reranker: { label: 'Reranker 模型', providerKey: 'rerankerProvider', modelKey: 'rerankerModel', endpointKey: 'rerankerEndpoint', apiKeyKey: 'rerankerApiKey', capability: 'reranker' } } export const MODEL_API_KEY_CONFIGS = Object.values(MODEL_TEST_CONFIGS) export const SESSION_RETENTION_OPTIONS = Array.from({ length: 10 }, (_item, index) => ({ value: index + 1, label: `${index + 1} 天` })) export function normalizeValue(value) { return String(value ?? '').trim() } export function normalizeProviderValue(value, fallback = 'Codex') { const normalized = normalizeValue(value) if (PROVIDER_OPTIONS.includes(normalized)) { return normalized } if (LEGACY_PROVIDER_MAP[normalized]) { return LEGACY_PROVIDER_MAP[normalized] } return fallback } export function getProviderEndpoint(provider) { return PROVIDER_ENDPOINTS[provider] ?? '' } export function getRerankerEndpoint(provider) { return RERANKER_PROVIDER_ENDPOINTS[provider] ?? getProviderEndpoint(provider) } export function buildDefaultState(companyProfile, currentUser) { const companyName = normalizeValue(companyProfile?.name) || 'X-Financial' const companyCode = normalizeValue(companyProfile?.code) || 'XF-001' const adminEmail = normalizeValue(companyProfile?.adminEmail) || normalizeValue(currentUser?.email) || 'admin@example.com' const adminAccount = normalizeValue(currentUser?.username) || 'superadmin' return { companyForm: { companyName, displayName: companyName, companyCode, logo: normalizeValue(companyProfile?.logo) || '', recordNumber: '', copyright: `Copyright © 2024-${CURRENT_YEAR} ${companyName}. All Rights Reserved.` }, appearanceForm: { themeSkin: 'sky' }, adminForm: { adminAccount, adminEmail, newPassword: '', confirmPassword: '', sessionTimeout: Number(import.meta.env.VITE_AUTH_IDLE_TIMEOUT_MINUTES || 30), noticeEmail: adminEmail, mfaEnabled: true, strongPassword: true, loginAlertEnabled: true, adminPasswordConfigured: false }, sessionForm: { conversationRetentionDays: 3 }, llmForm: { mainProvider: 'Codex', mainModel: 'codex-mini-latest', mainEndpoint: getProviderEndpoint('Codex'), mainApiKey: '', mainApiKeyConfigured: false, backupProvider: 'GLM', backupModel: 'glm-5.1', backupEndpoint: getProviderEndpoint('GLM'), backupApiKey: '', backupApiKeyConfigured: false, embeddingProvider: 'GLM', embeddingModel: 'Embedding-3', embeddingEndpoint: getProviderEndpoint('GLM'), embeddingApiKey: '', embeddingApiKeyConfigured: false, rerankerProvider: 'Ali', rerankerModel: 'gte-rerank-v2', rerankerEndpoint: getRerankerEndpoint('Ali'), rerankerApiKey: '', rerankerApiKeyConfigured: false }, renderForm: { enabled: false, publicUrl: '', jwtSecret: '', jwtSecretConfigured: false }, logForm: { level: 'INFO', retentionDays: 180, archiveCycle: 'weekly', logPath: 'server/logs/app.log', alertEmail: adminEmail, operationAudit: true, loginAudit: true, maskSensitive: true }, hermesForm: buildDefaultHermesEmployeeForm(), mailForm: { smtpHost: 'smtp.exmail.qq.com', port: 465, encryption: 'SSL/TLS', senderName: companyName, senderAddress: adminEmail, username: adminEmail, password: '', passwordConfigured: false, alertEnabled: true, digestEnabled: false, digestTime: '09:00', defaultReceiver: adminEmail } } } export function readStoredSettings() { if (typeof window === 'undefined') { return null } const raw = window.sessionStorage.getItem(SETTINGS_STORAGE_KEY) if (!raw) { return null } try { return JSON.parse(raw) } catch { return null } } export function mergeState(baseState, overrideState) { const mergedLlmForm = { ...baseState.llmForm, ...(overrideState?.llmForm || {}) } mergedLlmForm.mainProvider = normalizeProviderValue(mergedLlmForm.mainProvider, baseState.llmForm.mainProvider) mergedLlmForm.backupProvider = normalizeProviderValue(mergedLlmForm.backupProvider, baseState.llmForm.backupProvider) mergedLlmForm.embeddingProvider = normalizeProviderValue( mergedLlmForm.embeddingProvider, baseState.llmForm.embeddingProvider ) mergedLlmForm.rerankerProvider = normalizeProviderValue( mergedLlmForm.rerankerProvider, baseState.llmForm.rerankerProvider ) return { companyForm: { ...baseState.companyForm, ...(overrideState?.companyForm || {}) }, appearanceForm: { ...baseState.appearanceForm, ...(overrideState?.appearanceForm || {}) }, adminForm: { ...baseState.adminForm, ...(overrideState?.adminForm || {}) }, sessionForm: { ...baseState.sessionForm, ...(overrideState?.sessionForm || {}) }, hermesForm: mergeHermesEmployeeForm({ ...baseState.hermesForm, ...(overrideState?.hermesForm || {}) }), llmForm: mergedLlmForm, renderForm: { ...baseState.renderForm, ...(overrideState?.renderForm || {}) }, logForm: { ...baseState.logForm, ...(overrideState?.logForm || {}) }, mailForm: { ...baseState.mailForm, ...(overrideState?.mailForm || {}) } } } export function sanitizeForStorage(state) { return { companyForm: { ...state.companyForm }, appearanceForm: { ...state.appearanceForm }, adminForm: { ...state.adminForm, newPassword: '', confirmPassword: '' }, sessionForm: { ...state.sessionForm }, hermesForm: mergeHermesEmployeeForm(state.hermesForm), llmForm: { ...state.llmForm, mainApiKey: '', backupApiKey: '', embeddingApiKey: '', rerankerApiKey: '' }, renderForm: { ...state.renderForm, jwtSecret: '' }, logForm: { ...state.logForm }, mailForm: { ...state.mailForm, password: '' } } } export function getModelConfiguredKey(apiKeyKey) { return `${apiKeyKey}Configured` } export function isModelSecretMask(value) { return value === MODEL_SECRET_MASK } export function maskConfiguredModelSecrets(state) { for (const config of MODEL_API_KEY_CONFIGS) { const configuredKey = getModelConfiguredKey(config.apiKeyKey) if (state.llmForm[configuredKey] && !normalizeValue(state.llmForm[config.apiKeyKey])) { state.llmForm[config.apiKeyKey] = MODEL_SECRET_MASK } } return state } export function buildLlmPayload(llmForm) { const payload = { ...llmForm } for (const config of MODEL_API_KEY_CONFIGS) { if (isModelSecretMask(payload[config.apiKeyKey])) { payload[config.apiKeyKey] = '' } } return payload } export function isRenderSecretMask(value) { return value === RENDER_SECRET_MASK } export function maskConfiguredRenderSecret(state) { if (state.renderForm.jwtSecretConfigured && !normalizeValue(state.renderForm.jwtSecret)) { state.renderForm.jwtSecret = RENDER_SECRET_MASK } return state } export function buildRenderPayload(renderForm) { const payload = { ...renderForm } if (isRenderSecretMask(payload.jwtSecret)) { payload.jwtSecret = '' } return payload } export function persistSettings(state) { if (typeof window === 'undefined') { return } window.sessionStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(sanitizeForStorage(state))) } export function isModelConfigReady(provider, model, endpoint) { return Boolean(normalizeValue(provider) && normalizeValue(model) && normalizeValue(endpoint)) } export function computeSectionStatus(state) { return { profile: Boolean( normalizeValue(state.companyForm.companyName) && normalizeValue(state.companyForm.displayName) && normalizeValue(state.companyForm.copyright) ), appearance: true, admin: Boolean( normalizeValue(state.adminForm.adminAccount) && normalizeValue(state.adminForm.adminEmail) && Number(state.adminForm.sessionTimeout) >= 5 ), session: Boolean( Number(state.sessionForm.conversationRetentionDays) >= 1 && Number(state.sessionForm.conversationRetentionDays) <= 10 ), hermes: isHermesEmployeeSettingsReady(state.hermesForm), llm: Boolean( isModelConfigReady(state.llmForm.mainProvider, state.llmForm.mainModel, state.llmForm.mainEndpoint) && isModelConfigReady(state.llmForm.backupProvider, state.llmForm.backupModel, state.llmForm.backupEndpoint) && isModelConfigReady( state.llmForm.embeddingProvider, state.llmForm.embeddingModel, state.llmForm.embeddingEndpoint ) && isModelConfigReady( state.llmForm.rerankerProvider, state.llmForm.rerankerModel, state.llmForm.rerankerEndpoint ) ), rendering: Boolean( !state.renderForm.enabled || (normalizeValue(state.renderForm.publicUrl) && (normalizeValue(state.renderForm.jwtSecret) || state.renderForm.jwtSecretConfigured)) ), logs: Boolean( normalizeValue(state.logForm.level) && Number(state.logForm.retentionDays) > 0 && normalizeValue(state.logForm.logPath) ), mail: Boolean( normalizeValue(state.mailForm.smtpHost) && Number(state.mailForm.port) > 0 && normalizeValue(state.mailForm.senderAddress) && normalizeValue(state.mailForm.username) ) } }