mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +08:00
fix(settings): stop bulk save from clobbering multimodal sidecar config
This commit is contained in:
parent
4e1afa6b6f
commit
63f14acb72
@ -41,8 +41,39 @@ public class SystemSettingController {
|
|||||||
return R.ok(systemSettingService.saveLanguage(request.getLanguage()));
|
return R.ok(systemSettingService.saveLanguage(request.getLanguage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dedicated endpoint for the multimodal sidecar configuration.
|
||||||
|
* <p>
|
||||||
|
* Separated from the bulk {@code PUT /settings} because the bulk endpoint
|
||||||
|
* now guards sidecar keys with null checks (so unrelated settings pages
|
||||||
|
* can't clobber them via partial payloads). This endpoint always writes
|
||||||
|
* both fields, so passing {@code null} for either explicitly clears that
|
||||||
|
* sidecar — preserving the "clear via UI" UX without leaking the
|
||||||
|
* write-on-null semantics into every other settings save.
|
||||||
|
*/
|
||||||
|
@Operation(summary = "更新多模态 sidecar 配置")
|
||||||
|
@PutMapping("/sidecar")
|
||||||
|
public R<SystemSettingsDTO> saveSidecar(@RequestBody SidecarRequest request) {
|
||||||
|
return R.ok(systemSettingService.updateSidecarSettings(
|
||||||
|
request.getDefaultVisionModelId(),
|
||||||
|
request.getDefaultVideoModelId()));
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class LanguageRequest {
|
public static class LanguageRequest {
|
||||||
private String language;
|
private String language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body for {@code PUT /settings/sidecar}. Both fields are nullable;
|
||||||
|
* {@code null} means "explicit clear". Field absence in the JSON
|
||||||
|
* payload also deserializes to null, which is the same outcome — the
|
||||||
|
* sidecar UI is the only caller of this endpoint and always sends both
|
||||||
|
* fields, so the absent-vs-null distinction doesn't matter here.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class SidecarRequest {
|
||||||
|
private Long defaultVisionModelId;
|
||||||
|
private Long defaultVideoModelId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -351,13 +351,47 @@ public class SystemSettingService {
|
|||||||
saveValue(MODEL3D_FALLBACK_ENABLED_KEY, String.valueOf(dto.getModel3dFallbackEnabled()), "3D Provider 级 Fallback");
|
saveValue(MODEL3D_FALLBACK_ENABLED_KEY, String.valueOf(dto.getModel3dFallbackEnabled()), "3D Provider 级 Fallback");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multimodal sidecar routing — write empty string to clear (parse-back returns null)
|
// Multimodal sidecar routing — guarded with null check, matching the
|
||||||
// Always written so users can revert to "not configured" via the UI.
|
// pattern used for music / 3D / image / video / tts / stt blocks
|
||||||
|
// above. The bulk PUT /settings is used by every settings page (System,
|
||||||
|
// Music, Video, Image, Stt, Tts, Model3D), each sending a partial
|
||||||
|
// payload that omits sidecar fields. Without this guard, saving any
|
||||||
|
// unrelated setting would silently write "" into the sidecar keys
|
||||||
|
// (Long? defaultVisionModelId deserializes to null when absent), which
|
||||||
|
// wiped users' configured vision/video models the moment they touched
|
||||||
|
// an unrelated settings page. Explicit clearing via the sidecar UI now
|
||||||
|
// routes through {@link #updateSidecarSettings} instead.
|
||||||
|
if (dto.getDefaultVisionModelId() != null) {
|
||||||
|
saveValue(DEFAULT_VISION_MODEL_KEY,
|
||||||
|
String.valueOf(dto.getDefaultVisionModelId()),
|
||||||
|
"Default vision-capable model id (mate_model_config.id) for sidecar routing");
|
||||||
|
}
|
||||||
|
if (dto.getDefaultVideoModelId() != null) {
|
||||||
|
saveValue(DEFAULT_VIDEO_MODEL_KEY,
|
||||||
|
String.valueOf(dto.getDefaultVideoModelId()),
|
||||||
|
"Default video-capable model id (mate_model_config.id) for sidecar routing");
|
||||||
|
}
|
||||||
|
return getSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dedicated update path for the multimodal sidecar configuration.
|
||||||
|
* <p>
|
||||||
|
* This endpoint is the ONLY place vision/video model ids can be written
|
||||||
|
* unconditionally — null is treated as an explicit "clear" and writes
|
||||||
|
* an empty string (parse-back returns null). The bulk
|
||||||
|
* {@link #saveSettings} now guards both keys with non-null checks so
|
||||||
|
* unrelated settings pages can't accidentally clobber sidecar config.
|
||||||
|
* <p>
|
||||||
|
* Both fields are always written so a single API call can independently
|
||||||
|
* assign / clear either modality.
|
||||||
|
*/
|
||||||
|
public SystemSettingsDTO updateSidecarSettings(Long visionModelId, Long videoModelId) {
|
||||||
saveValue(DEFAULT_VISION_MODEL_KEY,
|
saveValue(DEFAULT_VISION_MODEL_KEY,
|
||||||
dto.getDefaultVisionModelId() == null ? "" : String.valueOf(dto.getDefaultVisionModelId()),
|
visionModelId == null ? "" : String.valueOf(visionModelId),
|
||||||
"Default vision-capable model id (mate_model_config.id) for sidecar routing");
|
"Default vision-capable model id (mate_model_config.id) for sidecar routing");
|
||||||
saveValue(DEFAULT_VIDEO_MODEL_KEY,
|
saveValue(DEFAULT_VIDEO_MODEL_KEY,
|
||||||
dto.getDefaultVideoModelId() == null ? "" : String.valueOf(dto.getDefaultVideoModelId()),
|
videoModelId == null ? "" : String.valueOf(videoModelId),
|
||||||
"Default video-capable model id (mate_model_config.id) for sidecar routing");
|
"Default video-capable model id (mate_model_config.id) for sidecar routing");
|
||||||
return getSettings();
|
return getSettings();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -518,6 +518,13 @@ export const settingsApi = {
|
|||||||
update: (data: any) => http.put('/settings', data),
|
update: (data: any) => http.put('/settings', data),
|
||||||
getLanguage: () => http.get('/settings/language'),
|
getLanguage: () => http.get('/settings/language'),
|
||||||
updateLanguage: (language: string) => http.put('/settings/language', { language }),
|
updateLanguage: (language: string) => http.put('/settings/language', { language }),
|
||||||
|
// Dedicated endpoint for the multimodal sidecar configuration. The bulk
|
||||||
|
// /settings PUT now guards vision/video model ids with non-null checks so
|
||||||
|
// unrelated settings pages can't clobber them via partial payloads. This
|
||||||
|
// endpoint is the only path that writes those fields unconditionally —
|
||||||
|
// pass {defaultVisionModelId: null} here to explicitly clear a sidecar.
|
||||||
|
updateSidecar: (data: { defaultVisionModelId: number | null; defaultVideoModelId: number | null }) =>
|
||||||
|
http.put('/settings/sidecar', data),
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Workspace ====================
|
// ==================== Workspace ====================
|
||||||
|
|||||||
@ -144,7 +144,12 @@ async function loadAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function persistSettings(payload: { defaultVisionModelId: number | null; defaultVideoModelId: number | null }) {
|
async function persistSettings(payload: { defaultVisionModelId: number | null; defaultVideoModelId: number | null }) {
|
||||||
await settingsApi.update(payload)
|
// Use the dedicated sidecar endpoint so the bulk /settings PUT can keep
|
||||||
|
// guarding vision/video keys with non-null checks (preventing unrelated
|
||||||
|
// settings pages from clobbering this configuration via partial payloads).
|
||||||
|
// This endpoint always writes both keys, so passing null here means
|
||||||
|
// "explicit clear" which is the original UX of this card.
|
||||||
|
await settingsApi.updateSidecar(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSaveVision() {
|
async function onSaveVision() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user