From e88d0321f74a11800a82977dbc5d1bf2d626c6e1 Mon Sep 17 00:00:00 2001 From: Louis <8515500@gmail.com> Date: Wed, 13 May 2026 14:52:37 +0800 Subject: [PATCH] feat: add context settings controls to webui --- API.en.md | 1 + API.md | 1 + .../httpapi/admin/handler_settings_test.go | 72 +++++++++++++++++++ .../admin/settings/handler_settings_parse.go | 65 ++++++++++------- .../admin/settings/handler_settings_write.go | 12 +++- .../settings/CurrentInputFileSection.jsx | 37 ++++++++++ .../features/settings/FeatureFlagsSection.jsx | 67 ++++++++++++++++- .../features/settings/SettingsContainer.jsx | 2 +- .../src/features/settings/useSettingsForm.js | 27 +++++-- webui/src/locales/en.json | 32 +++++++-- webui/src/locales/zh.json | 32 +++++++-- 11 files changed, 305 insertions(+), 43 deletions(-) diff --git a/API.en.md b/API.en.md index 14b87de02..78802a7bf 100644 --- a/API.en.md +++ b/API.en.md @@ -789,6 +789,7 @@ Hot-updates runtime settings. Supported fields: - `auto_delete.mode` - `current_input_file.enabled` / `current_input_file.min_chars` / `current_input_file.inline_max_tokens` / `current_input_file.filename_policy` - `thinking_injection.enabled` / `thinking_injection.prompt` +- `context_engine.mode` / `context_engine.strategy` - `model_aliases` - `toolcall` policy is fixed and is no longer writable through settings diff --git a/API.md b/API.md index c8d1436d3..f05a01d5a 100644 --- a/API.md +++ b/API.md @@ -795,6 +795,7 @@ data: {"type":"message_stop"} - `auto_delete.mode` - `current_input_file.enabled` / `current_input_file.min_chars` / `current_input_file.inline_max_tokens` / `current_input_file.filename_policy` - `thinking_injection.enabled` / `thinking_injection.prompt` +- `context_engine.mode` / `context_engine.strategy` - `model_aliases` - `toolcall` 策略已固定,不再作为可写入字段 diff --git a/internal/httpapi/admin/handler_settings_test.go b/internal/httpapi/admin/handler_settings_test.go index 1e1471227..1e31c8684 100644 --- a/internal/httpapi/admin/handler_settings_test.go +++ b/internal/httpapi/admin/handler_settings_test.go @@ -430,6 +430,78 @@ func TestUpdateSettingsThinkingInjectionPartialEnabledPreservesPrompt(t *testing } } +func TestUpdateSettingsContextEngine(t *testing.T) { + h := newAdminTestHandler(t, `{"keys":["k1"]}`) + payload := map[string]any{ + "context_engine": map[string]any{ + "mode": "shadow", + "strategy": "context_capsule", + }, + } + b, _ := json.Marshal(payload) + req := httptest.NewRequest(http.MethodPut, "/admin/settings", bytes.NewReader(b)) + rec := httptest.NewRecorder() + h.updateSettings(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) + } + snap := h.Store.Snapshot() + if snap.ContextEngine.Mode != "shadow" { + t.Fatalf("expected context_engine.mode=shadow, got %#v", snap.ContextEngine) + } + if snap.ContextEngine.Strategy != "context_capsule" { + t.Fatalf("expected context_engine.strategy=context_capsule, got %#v", snap.ContextEngine) + } + if got := h.Store.ContextEngineMode(); got != "shadow" { + t.Fatalf("ContextEngineMode()=%q want=shadow", got) + } + if got := h.Store.ContextEngineStrategy(); got != "context_capsule" { + t.Fatalf("ContextEngineStrategy()=%q want=context_capsule", got) + } +} + +func TestUpdateSettingsContextEnginePartialUpdatePreservesStrategy(t *testing.T) { + h := newAdminTestHandler(t, `{"keys":["k1"],"context_engine":{"mode":"enforce","strategy":"natural_context"}}`) + payload := map[string]any{ + "context_engine": map[string]any{ + "mode": "off", + }, + } + b, _ := json.Marshal(payload) + req := httptest.NewRequest(http.MethodPut, "/admin/settings", bytes.NewReader(b)) + rec := httptest.NewRecorder() + h.updateSettings(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String()) + } + snap := h.Store.Snapshot() + if snap.ContextEngine.Mode != "off" { + t.Fatalf("expected context_engine.mode=off, got %#v", snap.ContextEngine) + } + if snap.ContextEngine.Strategy != "natural_context" { + t.Fatalf("expected context_engine.strategy to be preserved, got %#v", snap.ContextEngine) + } +} + +func TestUpdateSettingsContextEngineRejectsInvalidMode(t *testing.T) { + h := newAdminTestHandler(t, `{"keys":["k1"]}`) + payload := map[string]any{ + "context_engine": map[string]any{ + "mode": "maybe", + }, + } + b, _ := json.Marshal(payload) + req := httptest.NewRequest(http.MethodPut, "/admin/settings", bytes.NewReader(b)) + rec := httptest.NewRecorder() + h.updateSettings(rec, req) + if rec.Code != http.StatusBadRequest { + t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String()) + } + if !bytes.Contains(rec.Body.Bytes(), []byte("context_engine.mode")) { + t.Fatalf("expected context engine validation detail, got %s", rec.Body.String()) + } +} + func TestUpdateSettingsAutoDeleteMode(t *testing.T) { h := newAdminTestHandler(t, `{"keys":["k1"],"auto_delete":{"sessions":true}}`) diff --git a/internal/httpapi/admin/settings/handler_settings_parse.go b/internal/httpapi/admin/settings/handler_settings_parse.go index f3ab30cf5..63457be45 100644 --- a/internal/httpapi/admin/settings/handler_settings_parse.go +++ b/internal/httpapi/admin/settings/handler_settings_parse.go @@ -33,17 +33,18 @@ func stringFrom(v any) string { } } -func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *config.RuntimeConfig, *config.ResponsesConfig, *config.EmbeddingsConfig, *config.AutoDeleteConfig, *config.CurrentInputFileConfig, *config.ThinkingInjectionConfig, map[string]string, *config.LogConfig, error) { +func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *config.RuntimeConfig, *config.ResponsesConfig, *config.EmbeddingsConfig, *config.AutoDeleteConfig, *config.CurrentInputFileConfig, *config.ThinkingInjectionConfig, *config.ContextEngineConfig, map[string]string, *config.LogConfig, error) { var ( - adminCfg *config.AdminConfig - runtimeCfg *config.RuntimeConfig - respCfg *config.ResponsesConfig - embCfg *config.EmbeddingsConfig - autoDeleteCfg *config.AutoDeleteConfig - currentInputCfg *config.CurrentInputFileConfig - thinkingInjCfg *config.ThinkingInjectionConfig - aliasMap map[string]string - logCfg *config.LogConfig + adminCfg *config.AdminConfig + runtimeCfg *config.RuntimeConfig + respCfg *config.ResponsesConfig + embCfg *config.EmbeddingsConfig + autoDeleteCfg *config.AutoDeleteConfig + currentInputCfg *config.CurrentInputFileConfig + thinkingInjCfg *config.ThinkingInjectionConfig + contextEngineCfg *config.ContextEngineConfig + aliasMap map[string]string + logCfg *config.LogConfig ) if raw, ok := req["admin"].(map[string]any); ok { @@ -51,7 +52,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["jwt_expire_hours"]; exists { n := intFrom(v) if err := config.ValidateIntRange("admin.jwt_expire_hours", n, 1, 720, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.JWTExpireHours = n } @@ -63,33 +64,33 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["account_max_inflight"]; exists { n := intFrom(v) if err := config.ValidateIntRange("runtime.account_max_inflight", n, 1, 256, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.AccountMaxInflight = n } if v, exists := raw["account_max_queue"]; exists { n := intFrom(v) if err := config.ValidateIntRange("runtime.account_max_queue", n, 1, 200000, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.AccountMaxQueue = n } if v, exists := raw["global_max_inflight"]; exists { n := intFrom(v) if err := config.ValidateIntRange("runtime.global_max_inflight", n, 1, 200000, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.GlobalMaxInflight = n } if v, exists := raw["token_refresh_interval_hours"]; exists { n := intFrom(v) if err := config.ValidateIntRange("runtime.token_refresh_interval_hours", n, 1, 720, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.TokenRefreshIntervalHours = n } if cfg.AccountMaxInflight > 0 && cfg.GlobalMaxInflight > 0 && cfg.GlobalMaxInflight < cfg.AccountMaxInflight { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("runtime.global_max_inflight must be >= runtime.account_max_inflight") + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("runtime.global_max_inflight must be >= runtime.account_max_inflight") } runtimeCfg = cfg } @@ -99,7 +100,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["store_ttl_seconds"]; exists { n := intFrom(v) if err := config.ValidateIntRange("responses.store_ttl_seconds", n, 30, 86400, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.StoreTTLSeconds = n } @@ -111,7 +112,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["provider"]; exists { p := strings.TrimSpace(fmt.Sprintf("%v", v)) if err := config.ValidateTrimmedString("embeddings.provider", p, false); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.Provider = p } @@ -137,7 +138,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["mode"]; exists { mode := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", v))) if err := config.ValidateAutoDeleteMode(mode); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } if mode == "" { mode = "none" @@ -159,14 +160,14 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["min_chars"]; exists { n := intFrom(v) if err := config.ValidateIntRange("current_input_file.min_chars", n, 0, 100000000, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.MinChars = n } if v, exists := raw["inline_max_tokens"]; exists { n := intFrom(v) if err := config.ValidateIntRange("current_input_file.inline_max_tokens", n, 0, 100000000, true); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } cfg.InlineMaxTokens = n } @@ -174,7 +175,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi cfg.FilenamePolicy = strings.TrimSpace(stringFrom(v)) } if err := config.ValidateCurrentInputFileConfig(*cfg); err != nil { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, err + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err } currentInputCfg = cfg } @@ -191,6 +192,20 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi thinkingInjCfg = cfg } + if raw, ok := req["context_engine"].(map[string]any); ok { + cfg := &config.ContextEngineConfig{} + if v, exists := raw["mode"]; exists { + cfg.Mode = strings.ToLower(strings.TrimSpace(stringFrom(v))) + } + if v, exists := raw["strategy"]; exists { + cfg.Strategy = config.NormalizeContextEngineStrategy(stringFrom(v)) + } + if err := config.ValidateContextEngineConfig(*cfg); err != nil { + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, err + } + contextEngineCfg = cfg + } + if raw, ok := req["log"].(map[string]any); ok { cfg := &config.LogConfig{} if v, exists := raw["level"]; exists { @@ -201,7 +216,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi case "debug", "info", "warn", "error": cfg.Level = level default: - return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("log.level must be one of: debug, info, warn, error") + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("log.level must be one of: debug, info, warn, error") } } if v, exists := raw["file"]; exists { @@ -213,7 +228,7 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi if v, exists := raw["max_size_mb"]; exists { n := intFrom(v) if n > 1024 { - return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("log.max_size_mb must be <= 1024") + return nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("log.max_size_mb must be <= 1024") } if n > 0 { cfg.MaxSizeMB = n @@ -228,5 +243,5 @@ func parseSettingsUpdateRequest(req map[string]any) (*config.AdminConfig, *confi logCfg = cfg } - return adminCfg, runtimeCfg, respCfg, embCfg, autoDeleteCfg, currentInputCfg, thinkingInjCfg, aliasMap, logCfg, nil + return adminCfg, runtimeCfg, respCfg, embCfg, autoDeleteCfg, currentInputCfg, thinkingInjCfg, contextEngineCfg, aliasMap, logCfg, nil } diff --git a/internal/httpapi/admin/settings/handler_settings_write.go b/internal/httpapi/admin/settings/handler_settings_write.go index a88967e08..499d49a0c 100644 --- a/internal/httpapi/admin/settings/handler_settings_write.go +++ b/internal/httpapi/admin/settings/handler_settings_write.go @@ -17,7 +17,7 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) { return } - adminCfg, runtimeCfg, responsesCfg, embeddingsCfg, autoDeleteCfg, currentInputCfg, thinkingInjCfg, aliasMap, logCfg, err := parseSettingsUpdateRequest(req) + adminCfg, runtimeCfg, responsesCfg, embeddingsCfg, autoDeleteCfg, currentInputCfg, thinkingInjCfg, contextEngineCfg, aliasMap, logCfg, err := parseSettingsUpdateRequest(req) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) return @@ -34,6 +34,8 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) { currentInputFilenamePolicySet := hasNestedSettingsKey(req, "current_input_file", "filename_policy") thinkingInjectionEnabledSet := hasNestedSettingsKey(req, "thinking_injection", "enabled") thinkingInjectionPromptSet := hasNestedSettingsKey(req, "thinking_injection", "prompt") + contextEngineModeSet := hasNestedSettingsKey(req, "context_engine", "mode") + contextEngineStrategySet := hasNestedSettingsKey(req, "context_engine", "strategy") logLevelSet := hasNestedSettingsKey(req, "log", "level") logFileSet := hasNestedSettingsKey(req, "log", "file") logFileEnabledSet := hasNestedSettingsKey(req, "log", "file_enabled") @@ -93,6 +95,14 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) { c.ThinkingInjection.Prompt = thinkingInjCfg.Prompt } } + if contextEngineCfg != nil { + if contextEngineModeSet { + c.ContextEngine.Mode = contextEngineCfg.Mode + } + if contextEngineStrategySet { + c.ContextEngine.Strategy = contextEngineCfg.Strategy + } + } if aliasMap != nil { c.ModelAliases = aliasMap } diff --git a/webui/src/features/settings/CurrentInputFileSection.jsx b/webui/src/features/settings/CurrentInputFileSection.jsx index d4e84afba..9127bca5a 100644 --- a/webui/src/features/settings/CurrentInputFileSection.jsx +++ b/webui/src/features/settings/CurrentInputFileSection.jsx @@ -42,6 +42,43 @@ export default function CurrentInputFileSection({ t, form, setForm }) { />

{t('settings.currentInputFileHelp')}

+ + ) diff --git a/webui/src/features/settings/FeatureFlagsSection.jsx b/webui/src/features/settings/FeatureFlagsSection.jsx index c3025c2bc..d12453ab7 100644 --- a/webui/src/features/settings/FeatureFlagsSection.jsx +++ b/webui/src/features/settings/FeatureFlagsSection.jsx @@ -13,13 +13,78 @@ function ModeBadge({ mode }) { ) } -export default function FeatureFlagsSection({ t, form }) { +export default function FeatureFlagsSection({ t, form, setForm }) { + const contextEngine = form.context_engine || { mode: 'enforce', strategy: 'hybrid_recent', env_override: false } const parserV2 = form.parser_v2 || { mode: 'off', env_override: false } + const contextDisabled = Boolean(contextEngine.env_override) return (

{t('settings.featureFlagsTitle')}

+
+
+
+ {t('settings.contextEngineTitle')} + {t('settings.contextEngineDesc')} +
+
+ {contextEngine.env_override && ( + + {t('settings.envOverride')} + + )} + +
+
+
+ + +
+ {contextDisabled && ( +

{t('settings.contextEngineEnvOverrideHelp')}

+ )} +
{t('settings.parserV2ModeLabel')} diff --git a/webui/src/features/settings/SettingsContainer.jsx b/webui/src/features/settings/SettingsContainer.jsx index 9fb1cc8e9..a358d8742 100644 --- a/webui/src/features/settings/SettingsContainer.jsx +++ b/webui/src/features/settings/SettingsContainer.jsx @@ -101,7 +101,7 @@ export default function SettingsContainer({ onRefresh, onMessage, authFetch, onF - + diff --git a/webui/src/features/settings/useSettingsForm.js b/webui/src/features/settings/useSettingsForm.js index c2721b16f..73712981f 100644 --- a/webui/src/features/settings/useSettingsForm.js +++ b/webui/src/features/settings/useSettingsForm.js @@ -16,8 +16,9 @@ const DEFAULT_FORM = { responses: { store_ttl_seconds: 900 }, embeddings: { provider: '' }, auto_delete: { mode: 'none' }, - current_input_file: { enabled: true, min_chars: 0 }, - thinking_injection: { enabled: true, prompt: '', default_prompt: '' }, + current_input_file: { enabled: true, min_chars: 0, inline_max_tokens: 30000, filename_policy: 'neutral_random' }, + thinking_injection: { enabled: false, prompt: '', default_prompt: '' }, + context_engine: { mode: 'enforce', strategy: 'hybrid_recent', env_override: false }, model_aliases_text: '{}', parser_v2: { mode: 'off', env_override: false }, log: { level: 'info', file: '', file_enabled: false, max_size_mb: 100, max_backups: 3 }, @@ -73,13 +74,20 @@ function fromServerForm(data) { current_input_file: { enabled: currentInputFileEnabled, min_chars: Number(data.current_input_file?.min_chars ?? 0), + inline_max_tokens: Number(data.current_input_file?.inline_max_tokens ?? 30000), + filename_policy: data.current_input_file?.filename_policy || 'neutral_random', }, thinking_injection: { - enabled: data.thinking_injection?.enabled ?? true, + enabled: data.thinking_injection?.enabled ?? false, prompt: data.thinking_injection?.prompt || '', default_prompt: data.thinking_injection?.default_prompt || '', }, model_aliases_text: JSON.stringify(data.model_aliases || {}, null, 2), + context_engine: { + mode: String(data.context_engine?.mode || 'enforce'), + strategy: String(data.context_engine?.strategy || 'hybrid_recent'), + env_override: Boolean(data.context_engine?.env_override), + }, parser_v2: { mode: String(data.parser_v2?.mode || 'off'), env_override: Boolean(data.parser_v2?.env_override), @@ -96,7 +104,7 @@ function fromServerForm(data) { function toServerPayload(form) { const currentInputFileEnabled = Boolean(form.current_input_file?.enabled) - return { + const payload = { admin: { jwt_expire_hours: Number(form.admin.jwt_expire_hours) }, runtime: { account_max_inflight: Number(form.runtime.account_max_inflight), @@ -110,9 +118,11 @@ function toServerPayload(form) { current_input_file: { enabled: currentInputFileEnabled, min_chars: Number(form.current_input_file?.min_chars ?? 0), + inline_max_tokens: Number(form.current_input_file?.inline_max_tokens ?? 30000), + filename_policy: String(form.current_input_file?.filename_policy || 'neutral_random'), }, thinking_injection: { - enabled: Boolean(form.thinking_injection?.enabled ?? true), + enabled: Boolean(form.thinking_injection?.enabled ?? false), prompt: String(form.thinking_injection?.prompt || '').trim(), }, log: { @@ -123,6 +133,13 @@ function toServerPayload(form) { max_backups: Number(form.log?.max_backups || 3), }, } + if (!form.context_engine?.env_override) { + payload.context_engine = { + mode: String(form.context_engine?.mode || 'enforce'), + strategy: String(form.context_engine?.strategy || 'hybrid_recent'), + } + } + return payload } export function useSettingsForm({ apiFetch, t, onMessage, onRefresh, onForceLogout, isVercel = false }) { diff --git a/webui/src/locales/en.json b/webui/src/locales/en.json index 3ffdb0818..f3b2a9250 100644 --- a/webui/src/locales/en.json +++ b/webui/src/locales/en.json @@ -389,15 +389,22 @@ "behaviorTitle": "Behavior", "responsesTTL": "Responses store TTL (seconds)", "embeddingsProvider": "Embeddings provider", - "thinkingInjectionEnabled": "Thinking format injection", - "thinkingInjectionDesc": "Append a structured checklist to the latest user message before prompt assembly.", + "thinkingInjectionEnabled": "Enhanced thinking prompt", + "thinkingInjectionDesc": "Disabled by default. When enabled, append the enhanced prompt to the latest user message.", "thinkingInjectionPrompt": "Thinking format prompt", "thinkingInjectionPromptHelp": "Leave empty to use the built-in default prompt shown as the input placeholder.", - "currentInputFileTitle": "Independent Split", + "currentInputFileTitle": "Context Split", "currentInputFileEnabled": "Independent split (by size)", - "currentInputFileDesc": "Enabled by default. Once the character threshold is reached, upload the full context as a DS2API_HISTORY.txt context file.", + "currentInputFileDesc": "Enabled by default with an inline-first path. Short contexts stay inline; larger contexts generate conversation context / tool reference files.", "currentInputFileMinChars": "Current input threshold (characters)", - "currentInputFileHelp": "Default is 0, which uses independent split for any non-empty input.", + "currentInputFileHelp": "Default is 0; split only happens after the full context exceeds the inline threshold.", + "currentInputFileInlineMaxTokens": "Inline context threshold (tokens)", + "currentInputFileInlineMaxTokensHelp": "Default is 30000; generated files are not uploaded while the full context stays below this value.", + "currentInputFileFilenamePolicy": "Generated filename policy", + "currentInputFileFilenamePolicyHelp": "The default uses neutral randomized filenames to reduce prompt-visible implementation detail.", + "filenamePolicyNeutralRandom": "Neutral random (recommended)", + "filenamePolicyNeutral": "Neutral fixed", + "filenamePolicyLegacy": "Legacy filenames", "modelTitle": "Model mapping", "modelAliases": "Global model aliases (JSON)", "autoDeleteTitle": "Session Cleanup Policy", @@ -435,6 +442,21 @@ "autoFetchPaused": "Auto loading paused after {count} failures: {error}", "retryLoad": "Retry now", "featureFlagsTitle": "Feature Flags", + "contextEngineTitle": "Context Engine", + "contextEngineDesc": "Controls context naturalization, semantic compression, and recent exact context retention.", + "contextEngineMode": "Run mode", + "contextEngineModeHelp": "Default is enforce. Shadow records plan summaries only; off falls back to legacy promptcompat rendering.", + "contextEngineStrategy": "Context strategy", + "contextEngineStrategyHelp": "Default is hybrid_recent. Auto is reserved; keep hybrid_recent for production.", + "contextEngineEnvOverrideHelp": "Environment variables currently override this value, so WebUI shows it but does not change the active runtime value.", + "modeEnforce": "Enforce", + "modeShadow": "Shadow", + "modeOff": "Off", + "strategyHybridRecent": "Hybrid recent (default)", + "strategyContextCapsule": "Context capsule", + "strategyNaturalContext": "Natural context", + "strategyRawTranscript": "Raw transcript", + "strategyAuto": "Auto (reserved)", "parserV2ModeLabel": "Tool parser v2 mode", "parserV2ModeDesc": "Read-only. Change via config.json parser_v2.mode or DS2API_PARSER_V2 env var.", "envOverride": "env override", diff --git a/webui/src/locales/zh.json b/webui/src/locales/zh.json index ca22e080a..814567677 100644 --- a/webui/src/locales/zh.json +++ b/webui/src/locales/zh.json @@ -389,15 +389,22 @@ "behaviorTitle": "行为设置", "responsesTTL": "Responses 缓存 TTL(秒)", "embeddingsProvider": "Embeddings Provider", - "thinkingInjectionEnabled": "思考格式注入", - "thinkingInjectionDesc": "在组装 prompt 前,将结构化 检查清单追加到最新用户消息末尾。", + "thinkingInjectionEnabled": "思考增强提示词", + "thinkingInjectionDesc": "默认关闭。开启后在最新用户消息末尾追加增强提示词。", "thinkingInjectionPrompt": "思考格式提示词", "thinkingInjectionPromptHelp": "留空时使用内置默认提示词;默认内容会显示在输入框占位文本中。", - "currentInputFileTitle": "独立拆分", + "currentInputFileTitle": "上下文拆分", "currentInputFileEnabled": "独立拆分(按量)", - "currentInputFileDesc": "默认开启。达到字符阈值后,将完整上下文上传为 DS2API_HISTORY.txt 上下文文件。", + "currentInputFileDesc": "默认开启并采用 inline-first。整体上下文较短时保持内联,超过阈值后生成 conversation context / tool reference 文件。", "currentInputFileMinChars": "当前输入阈值(字符数)", - "currentInputFileHelp": "默认 0,表示只要有输入就会使用独立拆分。", + "currentInputFileHelp": "默认 0;只有整体上下文超过 inline 阈值时才会触发拆分。", + "currentInputFileInlineMaxTokens": "Inline 上下文阈值(tokens)", + "currentInputFileInlineMaxTokensHelp": "默认 30000;整体上下文不超过该值时不会上传生成文件。", + "currentInputFileFilenamePolicy": "生成文件名策略", + "currentInputFileFilenamePolicyHelp": "默认使用中性随机文件名,降低模型可见的实现细节。", + "filenamePolicyNeutralRandom": "中性随机(推荐)", + "filenamePolicyNeutral": "中性固定", + "filenamePolicyLegacy": "Legacy 旧文件名", "modelTitle": "模型映射", "modelAliases": "全局模型映射(JSON)", "autoDeleteTitle": "会话删除策略", @@ -435,6 +442,21 @@ "autoFetchPaused": "自动加载已暂停:连续失败 {count} 次({error})", "retryLoad": "立即重试", "featureFlagsTitle": "功能标志", + "contextEngineTitle": "Context Engine", + "contextEngineDesc": "控制上下文自然化、语义压缩和最近上下文保留策略。", + "contextEngineMode": "运行模式", + "contextEngineModeHelp": "默认 enforce;shadow 只记录计划摘要,off 回退到旧式 promptcompat 渲染。", + "contextEngineStrategy": "上下文策略", + "contextEngineStrategyHelp": "默认 hybrid_recent;auto 当前为预留策略,生产建议保持 hybrid_recent。", + "contextEngineEnvOverrideHelp": "当前由环境变量覆盖,WebUI 会保留显示但不直接修改运行值。", + "modeEnforce": "Enforce(启用)", + "modeShadow": "Shadow(仅观测)", + "modeOff": "Off(关闭)", + "strategyHybridRecent": "Hybrid recent(默认)", + "strategyContextCapsule": "Context capsule", + "strategyNaturalContext": "Natural context", + "strategyRawTranscript": "Raw transcript", + "strategyAuto": "Auto(预留)", "parserV2ModeLabel": "工具解析器 v2 模式", "parserV2ModeDesc": "只读。通过 config.json parser_v2.mode 或 DS2API_PARSER_V2 环境变量修改。", "envOverride": "环境变量覆盖",