Fix web search API key config preservation
This commit is contained in:
parent
a4574f72a3
commit
60499def99
7 changed files with 321 additions and 7 deletions
|
|
@ -57,6 +57,7 @@ func (h *Handler) handleUpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
webAPIKeyPatch := parseWebSearchAPIKeyPatch(body)
|
||||||
if execAllowRemoteOmitted(body) {
|
if execAllowRemoteOmitted(body) {
|
||||||
cfg.Tools.Exec.AllowRemote = config.DefaultConfig().Tools.Exec.AllowRemote
|
cfg.Tools.Exec.AllowRemote = config.DefaultConfig().Tools.Exec.AllowRemote
|
||||||
}
|
}
|
||||||
|
|
@ -69,6 +70,7 @@ func (h *Handler) handleUpdateConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
applyConfigSecretsFromMap(&cfg, raw)
|
applyConfigSecretsFromMap(&cfg, raw)
|
||||||
|
applyWebSearchAPIKeyPatch(&cfg, webAPIKeyPatch)
|
||||||
|
|
||||||
if errs := validateConfig(&cfg); len(errs) > 0 {
|
if errs := validateConfig(&cfg); len(errs) > 0 {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -123,6 +125,7 @@ func (h *Handler) handlePatchConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
webAPIKeyPatch := parseWebSearchAPIKeyPatch(patchBody)
|
||||||
|
|
||||||
// Load existing config and marshal to a map for merging
|
// Load existing config and marshal to a map for merging
|
||||||
cfg, err := config.LoadConfig(h.configPath)
|
cfg, err := config.LoadConfig(h.configPath)
|
||||||
|
|
@ -166,6 +169,7 @@ func (h *Handler) handlePatchConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
applyConfigSecretsFromMap(&newCfg, base)
|
applyConfigSecretsFromMap(&newCfg, base)
|
||||||
|
applyWebSearchAPIKeyPatch(&newCfg, webAPIKeyPatch)
|
||||||
|
|
||||||
if errs := validateConfig(&newCfg); len(errs) > 0 {
|
if errs := validateConfig(&newCfg); len(errs) > 0 {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
@ -186,6 +190,49 @@ func (h *Handler) handlePatchConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type webSearchAPIKeyPatch struct {
|
||||||
|
glmSearchAPIKey *string
|
||||||
|
baiduSearchAPIKey *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseWebSearchAPIKeyPatch(body []byte) webSearchAPIKeyPatch {
|
||||||
|
var raw struct {
|
||||||
|
Tools *struct {
|
||||||
|
Web *struct {
|
||||||
|
GLMSearch *struct {
|
||||||
|
APIKey *string `json:"api_key"`
|
||||||
|
} `json:"glm_search"`
|
||||||
|
BaiduSearch *struct {
|
||||||
|
APIKey *string `json:"api_key"`
|
||||||
|
} `json:"baidu_search"`
|
||||||
|
} `json:"web"`
|
||||||
|
} `json:"tools"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &raw); err != nil {
|
||||||
|
return webSearchAPIKeyPatch{}
|
||||||
|
}
|
||||||
|
|
||||||
|
patch := webSearchAPIKeyPatch{}
|
||||||
|
if raw.Tools != nil && raw.Tools.Web != nil {
|
||||||
|
if raw.Tools.Web.GLMSearch != nil && raw.Tools.Web.GLMSearch.APIKey != nil {
|
||||||
|
patch.glmSearchAPIKey = raw.Tools.Web.GLMSearch.APIKey
|
||||||
|
}
|
||||||
|
if raw.Tools.Web.BaiduSearch != nil && raw.Tools.Web.BaiduSearch.APIKey != nil {
|
||||||
|
patch.baiduSearchAPIKey = raw.Tools.Web.BaiduSearch.APIKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patch
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyWebSearchAPIKeyPatch(cfg *config.Config, patch webSearchAPIKeyPatch) {
|
||||||
|
if patch.glmSearchAPIKey != nil {
|
||||||
|
cfg.Tools.Web.GLMSearch.SetAPIKey(strings.TrimSpace(*patch.glmSearchAPIKey))
|
||||||
|
}
|
||||||
|
if patch.baiduSearchAPIKey != nil {
|
||||||
|
cfg.Tools.Web.BaiduSearch.SetAPIKey(strings.TrimSpace(*patch.baiduSearchAPIKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handleTestCommandPatterns tests a command against whitelist and blacklist patterns.
|
// handleTestCommandPatterns tests a command against whitelist and blacklist patterns.
|
||||||
//
|
//
|
||||||
// POST /api/config/test-command-patterns
|
// POST /api/config/test-command-patterns
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,187 @@ func TestHandlePatchConfig_AllowsInvalidExecRegexPatternsWhenExecDisabled(t *tes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlePatchConfig_PreservesWebSearchAPIKeysWhenOmitted(t *testing.T) {
|
||||||
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
|
defer cleanup()
|
||||||
|
seedWebSearchAPIKeys(t, configPath, "glm-existing-key", "baidu-existing-key")
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{
|
||||||
|
"gateway": {
|
||||||
|
"log_level": "info"
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.GLMSearch.APIKey(); got != "glm-existing-key" {
|
||||||
|
t.Fatalf("tools.web.glm_search.api_key = %q, want %q", got, "glm-existing-key")
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.BaiduSearch.APIKey(); got != "baidu-existing-key" {
|
||||||
|
t.Fatalf("tools.web.baidu_search.api_key = %q, want %q", got, "baidu-existing-key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandlePatchConfig_UpdatesWebSearchAPIKeys(t *testing.T) {
|
||||||
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/config", bytes.NewBufferString(`{
|
||||||
|
"tools": {
|
||||||
|
"web": {
|
||||||
|
"glm_search": { "api_key": "glm-updated-key" },
|
||||||
|
"baidu_search": { "api_key": "baidu-updated-key" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.GLMSearch.APIKey(); got != "glm-updated-key" {
|
||||||
|
t.Fatalf("tools.web.glm_search.api_key = %q, want %q", got, "glm-updated-key")
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.BaiduSearch.APIKey(); got != "baidu-updated-key" {
|
||||||
|
t.Fatalf("tools.web.baidu_search.api_key = %q, want %q", got, "baidu-updated-key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleUpdateConfig_PreservesWebSearchAPIKeysWhenOmitted(t *testing.T) {
|
||||||
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
|
defer cleanup()
|
||||||
|
seedWebSearchAPIKeys(t, configPath, "glm-existing-key-via-put", "baidu-existing-key-via-put")
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPut, "/api/config", bytes.NewBufferString(`{
|
||||||
|
"version": 1,
|
||||||
|
"agents": {
|
||||||
|
"defaults": {
|
||||||
|
"workspace": "~/.picoclaw/workspace",
|
||||||
|
"model_name": "custom-default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_list": [
|
||||||
|
{
|
||||||
|
"model_name": "custom-default",
|
||||||
|
"model": "openai/gpt-4o",
|
||||||
|
"api_keys": ["sk-default"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.GLMSearch.APIKey(); got != "glm-existing-key-via-put" {
|
||||||
|
t.Fatalf("tools.web.glm_search.api_key = %q, want %q", got, "glm-existing-key-via-put")
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.BaiduSearch.APIKey(); got != "baidu-existing-key-via-put" {
|
||||||
|
t.Fatalf("tools.web.baidu_search.api_key = %q, want %q", got, "baidu-existing-key-via-put")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleUpdateConfig_UpdatesWebSearchAPIKeys(t *testing.T) {
|
||||||
|
configPath, cleanup := setupOAuthTestEnv(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
h.RegisterRoutes(mux)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPut, "/api/config", bytes.NewBufferString(`{
|
||||||
|
"version": 1,
|
||||||
|
"agents": {
|
||||||
|
"defaults": {
|
||||||
|
"workspace": "~/.picoclaw/workspace",
|
||||||
|
"model_name": "custom-default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model_list": [
|
||||||
|
{
|
||||||
|
"model_name": "custom-default",
|
||||||
|
"model": "openai/gpt-4o",
|
||||||
|
"api_keys": ["sk-default"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tools": {
|
||||||
|
"web": {
|
||||||
|
"glm_search": { "api_key": "glm-updated-key-via-put" },
|
||||||
|
"baidu_search": { "api_key": "baidu-updated-key-via-put" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d, body=%s", rec.Code, http.StatusOK, rec.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.GLMSearch.APIKey(); got != "glm-updated-key-via-put" {
|
||||||
|
t.Fatalf("tools.web.glm_search.api_key = %q, want %q", got, "glm-updated-key-via-put")
|
||||||
|
}
|
||||||
|
if got := cfg.Tools.Web.BaiduSearch.APIKey(); got != "baidu-updated-key-via-put" {
|
||||||
|
t.Fatalf("tools.web.baidu_search.api_key = %q, want %q", got, "baidu-updated-key-via-put")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func seedWebSearchAPIKeys(t *testing.T, configPath, glmAPIKey, baiduAPIKey string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cfg, err := config.LoadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Tools.Web.GLMSearch.SetAPIKey(glmAPIKey)
|
||||||
|
cfg.Tools.Web.BaiduSearch.SetAPIKey(baiduAPIKey)
|
||||||
|
|
||||||
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// setupPicoEnabledEnv creates a test environment with Pico channel enabled and
|
// setupPicoEnabledEnv creates a test environment with Pico channel enabled and
|
||||||
// its token stored only in .security.yml (not in the JSON payload).
|
// its token stored only in .security.yml (not in the JSON payload).
|
||||||
func setupPicoEnabledEnv(t *testing.T) (string, func()) {
|
func setupPicoEnabledEnv(t *testing.T) (string, func()) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import {
|
||||||
ExecSection,
|
ExecSection,
|
||||||
LauncherSection,
|
LauncherSection,
|
||||||
RuntimeSection,
|
RuntimeSection,
|
||||||
|
WebSearchSection,
|
||||||
} from "@/components/config/config-sections"
|
} from "@/components/config/config-sections"
|
||||||
import {
|
import {
|
||||||
type CoreConfigForm,
|
type CoreConfigForm,
|
||||||
|
|
@ -182,9 +183,12 @@ export function ConfigPage() {
|
||||||
"Cron exec timeout",
|
"Cron exec timeout",
|
||||||
{ min: 0 },
|
{ min: 0 },
|
||||||
)
|
)
|
||||||
|
const glmSearchAPIKey = form.glmSearchAPIKey.trim()
|
||||||
|
const baiduSearchAPIKey = form.baiduSearchAPIKey.trim()
|
||||||
const execConfigPatch: Record<string, unknown> = {
|
const execConfigPatch: Record<string, unknown> = {
|
||||||
enabled: form.execEnabled,
|
enabled: form.execEnabled,
|
||||||
}
|
}
|
||||||
|
const webConfigPatch: Record<string, unknown> = {}
|
||||||
|
|
||||||
if (form.execEnabled) {
|
if (form.execEnabled) {
|
||||||
execConfigPatch.allow_remote = form.allowRemote
|
execConfigPatch.allow_remote = form.allowRemote
|
||||||
|
|
@ -205,6 +209,24 @@ export function ConfigPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (glmSearchAPIKey !== "") {
|
||||||
|
webConfigPatch.glm_search = { api_key: glmSearchAPIKey }
|
||||||
|
}
|
||||||
|
if (baiduSearchAPIKey !== "") {
|
||||||
|
webConfigPatch.baidu_search = { api_key: baiduSearchAPIKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolsPatch: Record<string, unknown> = {
|
||||||
|
cron: {
|
||||||
|
allow_command: form.allowCommand,
|
||||||
|
exec_timeout_minutes: cronExecTimeoutMinutes,
|
||||||
|
},
|
||||||
|
exec: execConfigPatch,
|
||||||
|
}
|
||||||
|
if (Object.keys(webConfigPatch).length > 0) {
|
||||||
|
toolsPatch.web = webConfigPatch
|
||||||
|
}
|
||||||
|
|
||||||
await patchAppConfig({
|
await patchAppConfig({
|
||||||
agents: {
|
agents: {
|
||||||
defaults: {
|
defaults: {
|
||||||
|
|
@ -225,13 +247,7 @@ export function ConfigPage() {
|
||||||
session: {
|
session: {
|
||||||
dm_scope: dmScope,
|
dm_scope: dmScope,
|
||||||
},
|
},
|
||||||
tools: {
|
tools: toolsPatch,
|
||||||
cron: {
|
|
||||||
allow_command: form.allowCommand,
|
|
||||||
exec_timeout_minutes: cronExecTimeoutMinutes,
|
|
||||||
},
|
|
||||||
exec: execConfigPatch,
|
|
||||||
},
|
|
||||||
heartbeat: {
|
heartbeat: {
|
||||||
enabled: form.heartbeatEnabled,
|
enabled: form.heartbeatEnabled,
|
||||||
interval: heartbeatInterval,
|
interval: heartbeatInterval,
|
||||||
|
|
@ -332,6 +348,8 @@ export function ConfigPage() {
|
||||||
|
|
||||||
<CronSection form={form} onFieldChange={updateField} />
|
<CronSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
|
<WebSearchSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
<LauncherSection
|
<LauncherSection
|
||||||
launcherForm={launcherForm}
|
launcherForm={launcherForm}
|
||||||
onFieldChange={updateLauncherField}
|
onFieldChange={updateLauncherField}
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,51 @@ export function CronSection({ form, onFieldChange }: CronSectionProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WebSearchSectionProps {
|
||||||
|
form: CoreConfigForm
|
||||||
|
onFieldChange: UpdateCoreField
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WebSearchSection({
|
||||||
|
form,
|
||||||
|
onFieldChange,
|
||||||
|
}: WebSearchSectionProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigSectionCard
|
||||||
|
title={t("pages.config.sections.web_search")}
|
||||||
|
description={t("pages.config.web_search_section_hint")}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
label={t("pages.config.glm_search_api_key")}
|
||||||
|
hint={t("pages.config.glm_search_api_key_hint")}
|
||||||
|
layout="setting-row"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={form.glmSearchAPIKey}
|
||||||
|
placeholder={t("pages.config.secret_placeholder")}
|
||||||
|
onChange={(e) => onFieldChange("glmSearchAPIKey", e.target.value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
label={t("pages.config.baidu_search_api_key")}
|
||||||
|
hint={t("pages.config.baidu_search_api_key_hint")}
|
||||||
|
layout="setting-row"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={form.baiduSearchAPIKey}
|
||||||
|
placeholder={t("pages.config.secret_placeholder")}
|
||||||
|
onChange={(e) => onFieldChange("baiduSearchAPIKey", e.target.value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</ConfigSectionCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface LauncherSectionProps {
|
interface LauncherSectionProps {
|
||||||
launcherForm: LauncherForm
|
launcherForm: LauncherForm
|
||||||
onFieldChange: UpdateLauncherField
|
onFieldChange: UpdateLauncherField
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ export interface CoreConfigForm {
|
||||||
execTimeoutSeconds: string
|
execTimeoutSeconds: string
|
||||||
allowCommand: boolean
|
allowCommand: boolean
|
||||||
cronExecTimeoutMinutes: string
|
cronExecTimeoutMinutes: string
|
||||||
|
glmSearchAPIKey: string
|
||||||
|
baiduSearchAPIKey: string
|
||||||
maxTokens: string
|
maxTokens: string
|
||||||
contextWindow: string
|
contextWindow: string
|
||||||
maxToolIterations: string
|
maxToolIterations: string
|
||||||
|
|
@ -77,6 +79,8 @@ export const EMPTY_FORM: CoreConfigForm = {
|
||||||
execTimeoutSeconds: "0",
|
execTimeoutSeconds: "0",
|
||||||
allowCommand: true,
|
allowCommand: true,
|
||||||
cronExecTimeoutMinutes: "5",
|
cronExecTimeoutMinutes: "5",
|
||||||
|
glmSearchAPIKey: "",
|
||||||
|
baiduSearchAPIKey: "",
|
||||||
maxTokens: "32768",
|
maxTokens: "32768",
|
||||||
contextWindow: "",
|
contextWindow: "",
|
||||||
maxToolIterations: "50",
|
maxToolIterations: "50",
|
||||||
|
|
@ -128,6 +132,9 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
||||||
const heartbeat = asRecord(root.heartbeat)
|
const heartbeat = asRecord(root.heartbeat)
|
||||||
const devices = asRecord(root.devices)
|
const devices = asRecord(root.devices)
|
||||||
const tools = asRecord(root.tools)
|
const tools = asRecord(root.tools)
|
||||||
|
const web = asRecord(tools.web)
|
||||||
|
const glmSearch = asRecord(web.glm_search)
|
||||||
|
const baiduSearch = asRecord(web.baidu_search)
|
||||||
const cron = asRecord(tools.cron)
|
const cron = asRecord(tools.cron)
|
||||||
const exec = asRecord(tools.exec)
|
const exec = asRecord(tools.exec)
|
||||||
const toolFeedback = asRecord(defaults.tool_feedback)
|
const toolFeedback = asRecord(defaults.tool_feedback)
|
||||||
|
|
@ -184,6 +191,8 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
||||||
cron.exec_timeout_minutes,
|
cron.exec_timeout_minutes,
|
||||||
EMPTY_FORM.cronExecTimeoutMinutes,
|
EMPTY_FORM.cronExecTimeoutMinutes,
|
||||||
),
|
),
|
||||||
|
glmSearchAPIKey: asString(glmSearch.api_key),
|
||||||
|
baiduSearchAPIKey: asString(baiduSearch.api_key),
|
||||||
maxTokens: asNumberString(defaults.max_tokens, EMPTY_FORM.maxTokens),
|
maxTokens: asNumberString(defaults.max_tokens, EMPTY_FORM.maxTokens),
|
||||||
contextWindow: asNumberString(
|
contextWindow: asNumberString(
|
||||||
defaults.context_window,
|
defaults.context_window,
|
||||||
|
|
|
||||||
|
|
@ -478,6 +478,12 @@
|
||||||
"allow_shell_execution_hint": "Allow scheduled tasks to run commands by default. When disabled, users must pass command_confirm=true to schedule a command task.",
|
"allow_shell_execution_hint": "Allow scheduled tasks to run commands by default. When disabled, users must pass command_confirm=true to schedule a command task.",
|
||||||
"cron_exec_timeout": "Scheduled Command Timeout (minutes)",
|
"cron_exec_timeout": "Scheduled Command Timeout (minutes)",
|
||||||
"cron_exec_timeout_hint": "Maximum runtime for scheduled commands. Set to 0 to disable the timeout.",
|
"cron_exec_timeout_hint": "Maximum runtime for scheduled commands. Set to 0 to disable the timeout.",
|
||||||
|
"web_search_section_hint": "Manage API keys for built-in web search providers. Leave a field blank to keep the current key unchanged.",
|
||||||
|
"glm_search_api_key": "GLM Search API Key",
|
||||||
|
"glm_search_api_key_hint": "Used by the built-in GLM web search provider.",
|
||||||
|
"baidu_search_api_key": "Baidu Search API Key",
|
||||||
|
"baidu_search_api_key_hint": "Used by the built-in Baidu web search provider.",
|
||||||
|
"secret_placeholder": "Leave blank to keep the current key",
|
||||||
"max_tokens": "Max Tokens",
|
"max_tokens": "Max Tokens",
|
||||||
"max_tokens_hint": "Upper token limit per model response.",
|
"max_tokens_hint": "Upper token limit per model response.",
|
||||||
"context_window": "Context Window",
|
"context_window": "Context Window",
|
||||||
|
|
@ -522,6 +528,7 @@
|
||||||
"runtime": "Runtime",
|
"runtime": "Runtime",
|
||||||
"exec": "Run Commands",
|
"exec": "Run Commands",
|
||||||
"cron": "Cron Tasks",
|
"cron": "Cron Tasks",
|
||||||
|
"web_search": "Web Search",
|
||||||
"launcher": "Service",
|
"launcher": "Service",
|
||||||
"devices": "Devices"
|
"devices": "Devices"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -478,6 +478,12 @@
|
||||||
"allow_shell_execution_hint": "开启后,定时任务默认允许运行命令。关闭后,必须显式传入 command_confirm=true 才能创建运行命令的定时任务。",
|
"allow_shell_execution_hint": "开启后,定时任务默认允许运行命令。关闭后,必须显式传入 command_confirm=true 才能创建运行命令的定时任务。",
|
||||||
"cron_exec_timeout": "定时命令超时(分钟)",
|
"cron_exec_timeout": "定时命令超时(分钟)",
|
||||||
"cron_exec_timeout_hint": "定时任务中命令的最长运行时间。设置为 0 表示不限制超时。",
|
"cron_exec_timeout_hint": "定时任务中命令的最长运行时间。设置为 0 表示不限制超时。",
|
||||||
|
"web_search_section_hint": "管理内置网页搜索服务的 API Key。留空表示保持当前 Key 不变。",
|
||||||
|
"glm_search_api_key": "GLM 搜索 API Key",
|
||||||
|
"glm_search_api_key_hint": "用于内置的 GLM 网页搜索服务。",
|
||||||
|
"baidu_search_api_key": "百度搜索 API Key",
|
||||||
|
"baidu_search_api_key_hint": "用于内置的百度网页搜索服务。",
|
||||||
|
"secret_placeholder": "留空保持当前 Key 不变",
|
||||||
"max_tokens": "最大 Token 数",
|
"max_tokens": "最大 Token 数",
|
||||||
"max_tokens_hint": "单次模型响应允许的最大 Token 数。",
|
"max_tokens_hint": "单次模型响应允许的最大 Token 数。",
|
||||||
"context_window": "上下文窗口",
|
"context_window": "上下文窗口",
|
||||||
|
|
@ -522,6 +528,7 @@
|
||||||
"runtime": "运行时",
|
"runtime": "运行时",
|
||||||
"exec": "运行命令",
|
"exec": "运行命令",
|
||||||
"cron": "定时任务",
|
"cron": "定时任务",
|
||||||
|
"web_search": "网页搜索",
|
||||||
"launcher": "服务参数",
|
"launcher": "服务参数",
|
||||||
"devices": "设备"
|
"devices": "设备"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue