feat(models): add extra_body config field in model add/edit UI (#1969)

* Add extraBody field to model configuration forms

This adds a new field allowing users to specify additional JSON fields
to inject into the request body when configuring models.

* Handle ExtraBody clearing when frontend sends empty object

The backend now interprets an empty object sent from the frontend as a
signal to clear the ExtraBody field, while nil/undefined preserves the
existing value. Frontend changed to send {} instead of undefined when
the field is empty.
This commit is contained in:
柚子
2026-03-25 11:11:02 +08:00
committed by GitHub
parent adf1a5749d
commit 3b3062abe8
5 changed files with 52 additions and 4 deletions
+5
View File
@@ -204,8 +204,13 @@ func (h *Handler) handleUpdateModel(w http.ResponseWriter, r *http.Request) {
} else {
mc.ModelConfig.SetAPIKey(mc.APIKey)
}
// Preserve existing ExtraBody when omitted (nil), but clear it when
// the frontend sends an empty object {} to indicate the field should
// be removed.
if mc.ExtraBody == nil {
mc.ExtraBody = cfg.ModelList[idx].ExtraBody
} else if len(mc.ExtraBody) == 0 {
mc.ExtraBody = nil
}
cfg.ModelList[idx] = &mc.ModelConfig
@@ -12,6 +12,7 @@ import {
} from "@/components/shared-form"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Sheet,
SheetContent,
@@ -34,6 +35,7 @@ interface AddForm {
maxTokensField: string
requestTimeout: string
thinkingLevel: string
extraBody: string
}
const EMPTY_ADD_FORM: AddForm = {
@@ -49,6 +51,7 @@ const EMPTY_ADD_FORM: AddForm = {
maxTokensField: "",
requestTimeout: "",
thinkingLevel: "",
extraBody: "",
}
interface AddModelSheetProps {
@@ -100,7 +103,7 @@ export function AddModelSheet({
}
const setField =
(key: keyof AddForm) => (e: React.ChangeEvent<HTMLInputElement>) => {
(key: keyof AddForm) => (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setForm((f) => ({ ...f, [key]: e.target.value }))
if (fieldErrors[key]) {
setFieldErrors((prev) => ({ ...prev, [key]: undefined }))
@@ -129,6 +132,9 @@ export function AddModelSheet({
? Number(form.requestTimeout)
: undefined,
thinking_level: form.thinkingLevel.trim() || undefined,
extra_body: form.extraBody.trim()
? JSON.parse(form.extraBody.trim())
: undefined,
})
if (setAsDefault) {
await setDefaultModel(modelName)
@@ -305,6 +311,18 @@ export function AddModelSheet({
placeholder="max_completion_tokens"
/>
</Field>
<Field
label={t("models.field.extraBody")}
hint={t("models.field.extraBodyHint")}
>
<Textarea
value={form.extraBody}
onChange={setField("extraBody")}
placeholder='{"key": "value"}'
rows={3}
/>
</Field>
</AdvancedSection>
{serverError && (
@@ -12,6 +12,7 @@ import {
} from "@/components/shared-form"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Sheet,
SheetContent,
@@ -32,6 +33,7 @@ interface EditForm {
maxTokensField: string
requestTimeout: string
thinkingLevel: string
extraBody: string
}
interface EditModelSheetProps {
@@ -59,6 +61,7 @@ export function EditModelSheet({
maxTokensField: "",
requestTimeout: "",
thinkingLevel: "",
extraBody: "",
})
const [saving, setSaving] = useState(false)
const [setAsDefault, setSetAsDefault] = useState(false)
@@ -79,6 +82,9 @@ export function EditModelSheet({
? String(model.request_timeout)
: "",
thinkingLevel: model.thinking_level ?? "",
extraBody: model.extra_body
? JSON.stringify(model.extra_body, null, 2)
: "",
})
setSetAsDefault(model.is_default)
setError("")
@@ -86,7 +92,7 @@ export function EditModelSheet({
}, [model])
const setField =
(key: keyof EditForm) => (e: React.ChangeEvent<HTMLInputElement>) =>
(key: keyof EditForm) => (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
setForm((f) => ({ ...f, [key]: e.target.value }))
const handleSave = async () => {
@@ -109,6 +115,9 @@ export function EditModelSheet({
? Number(form.requestTimeout)
: undefined,
thinking_level: form.thinkingLevel || undefined,
extra_body: form.extraBody.trim()
? JSON.parse(form.extraBody.trim())
: {},
})
if (setAsDefault && !model.is_default) {
await setDefaultModel(model.model_name)
@@ -273,6 +282,18 @@ export function EditModelSheet({
placeholder="max_completion_tokens"
/>
</Field>
<Field
label={t("models.field.extraBody")}
hint={t("models.field.extraBodyHint")}
>
<Textarea
value={form.extraBody}
onChange={setField("extraBody")}
placeholder='{"key": "value"}'
rows={3}
/>
</Field>
</AdvancedSection>
{error && (
+3 -1
View File
@@ -209,7 +209,9 @@
"thinkingLevel": "Thinking Level",
"thinkingLevelHint": "Extended thinking budget: off, low, medium, high, xhigh, adaptive.",
"maxTokensField": "Max Tokens Field",
"maxTokensFieldHint": "Override the request field name for max tokens, e.g. max_completion_tokens."
"maxTokensFieldHint": "Override the request field name for max tokens, e.g. max_completion_tokens.",
"extraBody": "Extra Body",
"extraBodyHint": "Additional JSON fields to inject into the request body, e.g. {\"reasoning_split\": true}."
},
"edit": {
"title": "Configure {{name}}",
+3 -1
View File
@@ -209,7 +209,9 @@
"thinkingLevel": "思考级别",
"thinkingLevelHint": "扩展思考预算:off、low、medium、high、xhigh、adaptive。",
"maxTokensField": "Max Tokens 字段名",
"maxTokensFieldHint": "覆盖请求中 max_tokens 的字段名,例如 max_completion_tokens。"
"maxTokensFieldHint": "覆盖请求中 max_tokens 的字段名,例如 max_completion_tokens。",
"extraBody": "Extra Body",
"extraBodyHint": "要注入到请求体中的额外 JSON 字段,例如 {\"reasoning_split\": true}。"
},
"edit": {
"title": "配置 {{name}}",