mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
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:
@@ -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 && (
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
@@ -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}}",
|
||||
|
||||
Reference in New Issue
Block a user