fix(settings): system tab review fixes
- guard confirmByok() against byok-ack POST failure (leave modal open on error) - fix drag reorder to use ID-based index lookup (not filtered-list index) - guard cancelByok() against empty snapshot - add LlmConfigPayload Pydantic model for PUT endpoint - add test for confirmByok() failure path
This commit is contained in:
parent
7af0366330
commit
5afb752be6
4 changed files with 37 additions and 13 deletions
|
|
@ -1149,6 +1149,9 @@ def suggest_search(body: dict):
|
|||
class ByokAckPayload(BaseModel):
|
||||
backends: List[str] = []
|
||||
|
||||
class LlmConfigPayload(BaseModel):
|
||||
backends: List[dict] = []
|
||||
|
||||
LLM_CONFIG_PATH = Path("config/llm.yaml")
|
||||
|
||||
@app.get("/api/settings/system/llm")
|
||||
|
|
@ -1165,13 +1168,13 @@ def get_llm_config():
|
|||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.put("/api/settings/system/llm")
|
||||
def save_llm_config(payload: dict):
|
||||
def save_llm_config(payload: LlmConfigPayload):
|
||||
try:
|
||||
data = {}
|
||||
if LLM_CONFIG_PATH.exists():
|
||||
with open(LLM_CONFIG_PATH) as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
data["backends"] = payload.get("backends", [])
|
||||
data["backends"] = payload.backends
|
||||
LLM_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(LLM_CONFIG_PATH, "w") as f:
|
||||
yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,17 @@ describe('useSystemStore — BYOK gate', () => {
|
|||
expect(mockFetch).toHaveBeenCalledWith('/api/settings/system/llm', expect.anything())
|
||||
})
|
||||
|
||||
it('confirmByok() sets saveError and leaves modal open when ack POST fails', async () => {
|
||||
mockFetch.mockResolvedValue({ data: null, error: 'Network error' })
|
||||
const store = useSystemStore()
|
||||
store.byokPending = ['anthropic']
|
||||
store.backends = [{ id: 'anthropic', enabled: true, priority: 1 }]
|
||||
await store.confirmByok()
|
||||
expect(store.saveError).toBeTruthy()
|
||||
expect(store.byokPending).toContain('anthropic') // modal stays open
|
||||
expect(mockFetch).not.toHaveBeenCalledWith('/api/settings/system/llm', expect.anything())
|
||||
})
|
||||
|
||||
it('cancelByok() clears pending and restores backends to pre-save state', async () => {
|
||||
mockFetch.mockResolvedValue({ data: { ok: true }, error: null })
|
||||
const store = useSystemStore()
|
||||
|
|
|
|||
|
|
@ -40,18 +40,26 @@ export const useSystemStore = defineStore('settings/system', () => {
|
|||
|
||||
async function confirmByok() {
|
||||
saving.value = true
|
||||
saveError.value = null
|
||||
const { error } = await useApiFetch('/api/settings/system/llm/byok-ack', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ backends: byokPending.value }),
|
||||
})
|
||||
if (!error) byokAcknowledged.value = [...byokAcknowledged.value, ...byokPending.value]
|
||||
if (error) {
|
||||
saving.value = false
|
||||
saveError.value = 'Failed to save acknowledgment — please try again.'
|
||||
return // leave modal open, byokPending intact
|
||||
}
|
||||
byokAcknowledged.value = [...byokAcknowledged.value, ...byokPending.value]
|
||||
byokPending.value = []
|
||||
await _commitSave()
|
||||
}
|
||||
|
||||
function cancelByok() {
|
||||
if (_preSaveSnapshot.length > 0) {
|
||||
backends.value = JSON.parse(JSON.stringify(_preSaveSnapshot))
|
||||
}
|
||||
byokPending.value = []
|
||||
_preSaveSnapshot = []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,16 +98,18 @@ function dragStart(idx: number) {
|
|||
dragIdx.value = idx
|
||||
}
|
||||
|
||||
function dragOver(idx: number) {
|
||||
if (dragIdx.value === null || dragIdx.value === idx) return
|
||||
// Reorder store.backends (immutable swap)
|
||||
function dragOver(toFilteredIdx: number) {
|
||||
if (dragIdx.value === null || dragIdx.value === toFilteredIdx) return
|
||||
const fromId = visibleBackends.value[dragIdx.value].id
|
||||
const toId = visibleBackends.value[toFilteredIdx].id
|
||||
const arr = [...store.backends]
|
||||
const [moved] = arr.splice(dragIdx.value, 1)
|
||||
arr.splice(idx, 0, moved)
|
||||
store.backends = arr
|
||||
// Update priorities
|
||||
store.backends = store.backends.map((b, i) => ({ ...b, priority: i + 1 }))
|
||||
dragIdx.value = idx
|
||||
const fromFull = arr.findIndex(b => b.id === fromId)
|
||||
const toFull = arr.findIndex(b => b.id === toId)
|
||||
if (fromFull === -1 || toFull === -1) return
|
||||
const [moved] = arr.splice(fromFull, 1)
|
||||
arr.splice(toFull, 0, moved)
|
||||
store.backends = arr.map((b, i) => ({ ...b, priority: i + 1 }))
|
||||
dragIdx.value = toFilteredIdx
|
||||
}
|
||||
|
||||
function drop() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue