fix: pessimistic submit/undo, config null-safe, load config on mount
- sft.py GET /config: use `or {}` guard so `sft: ~` (null YAML) doesn't
return None instead of the default empty config
- CorrectionsView: convert handleCorrect/Discard/Flag and handleUndo from
optimistic to pessimistic — queue mutation only happens after server
confirms; failures leave item in queue so user can retry cleanly
- SettingsView: call loadSftConfig() on mount so saved bench_results_dir
is populated instead of always starting empty
This commit is contained in:
parent
353d0a47a0
commit
09e334359f
3 changed files with 82 additions and 43 deletions
|
|
@ -285,7 +285,8 @@ def get_sft_config() -> dict:
|
|||
raw = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
|
||||
except yaml.YAMLError:
|
||||
return {"bench_results_dir": ""}
|
||||
return raw.get("sft", {"bench_results_dir": ""})
|
||||
sft_section = raw.get("sft") or {}
|
||||
return {"bench_results_dir": sft_section.get("bench_results_dir", "")}
|
||||
|
||||
|
||||
class SftConfigPayload(BaseModel):
|
||||
|
|
|
|||
|
|
@ -116,61 +116,84 @@ function startCorrection() {
|
|||
async function handleCorrect(text: string) {
|
||||
if (!store.current) return
|
||||
const item = store.current
|
||||
store.setLastAction('correct', item)
|
||||
store.removeCurrentFromQueue()
|
||||
correcting.value = false
|
||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
||||
await fetch('/api/sft/submit', {
|
||||
try {
|
||||
const res = await fetch('/api/sft/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: item.id, action: 'correct', corrected_response: text }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
store.removeCurrentFromQueue()
|
||||
store.setLastAction('correct', item)
|
||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
||||
fetchStats()
|
||||
if (store.queue.length < 5) fetchBatch()
|
||||
} catch (err) {
|
||||
console.error('handleCorrect failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDiscard() {
|
||||
if (!store.current) return
|
||||
const item = store.current
|
||||
store.setLastAction('discard', item)
|
||||
store.removeCurrentFromQueue()
|
||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
||||
await fetch('/api/sft/submit', {
|
||||
try {
|
||||
const res = await fetch('/api/sft/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: item.id, action: 'discard' }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
store.removeCurrentFromQueue()
|
||||
store.setLastAction('discard', item)
|
||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
||||
fetchStats()
|
||||
if (store.queue.length < 5) fetchBatch()
|
||||
} catch (err) {
|
||||
console.error('handleDiscard failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFlag() {
|
||||
if (!store.current) return
|
||||
const item = store.current
|
||||
store.setLastAction('flag', item)
|
||||
store.removeCurrentFromQueue()
|
||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
||||
await fetch('/api/sft/submit', {
|
||||
try {
|
||||
const res = await fetch('/api/sft/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: item.id, action: 'flag' }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
store.removeCurrentFromQueue()
|
||||
store.setLastAction('flag', item)
|
||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
||||
fetchStats()
|
||||
if (store.queue.length < 5) fetchBatch()
|
||||
} catch (err) {
|
||||
console.error('handleFlag failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUndo() {
|
||||
if (!store.lastAction) return
|
||||
const { item } = store.lastAction
|
||||
store.restoreItem(item)
|
||||
store.totalRemaining++
|
||||
store.clearLastAction()
|
||||
await fetch('/api/sft/undo', {
|
||||
const action = store.lastAction
|
||||
const { item } = action
|
||||
try {
|
||||
const res = await fetch('/api/sft/undo', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: item.id }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
store.restoreItem(item)
|
||||
store.totalRemaining++
|
||||
store.clearLastAction()
|
||||
fetchStats()
|
||||
} catch (err) {
|
||||
// Backend did not restore — clear the undo UI without restoring queue state
|
||||
console.error('handleUndo failed:', err)
|
||||
store.clearLastAction()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
|||
|
|
@ -206,6 +206,18 @@ const importingRunId = ref<string | null>(null)
|
|||
const importResult = ref<{ imported: number; skipped: number } | null>(null)
|
||||
const saveStatus = ref('')
|
||||
|
||||
async function loadSftConfig() {
|
||||
try {
|
||||
const res = await fetch('/api/sft/config')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
benchResultsDir.value = data.bench_results_dir ?? ''
|
||||
}
|
||||
} catch {
|
||||
// non-fatal — leave field empty
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSftConfig() {
|
||||
saveStatus.value = 'Saving…'
|
||||
try {
|
||||
|
|
@ -322,7 +334,10 @@ function onKeyHintsChange() {
|
|||
document.documentElement.classList.toggle('hide-key-hints', !keyHints.value)
|
||||
}
|
||||
|
||||
onMounted(reload)
|
||||
onMounted(() => {
|
||||
reload()
|
||||
loadSftConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
Loading…
Reference in a new issue