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 {}
|
raw = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
return {"bench_results_dir": ""}
|
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):
|
class SftConfigPayload(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -116,61 +116,84 @@ function startCorrection() {
|
||||||
async function handleCorrect(text: string) {
|
async function handleCorrect(text: string) {
|
||||||
if (!store.current) return
|
if (!store.current) return
|
||||||
const item = store.current
|
const item = store.current
|
||||||
store.setLastAction('correct', item)
|
correcting.value = false
|
||||||
store.removeCurrentFromQueue()
|
try {
|
||||||
correcting.value = false
|
const res = await fetch('/api/sft/submit', {
|
||||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
method: 'POST',
|
||||||
await fetch('/api/sft/submit', {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
method: 'POST',
|
body: JSON.stringify({ id: item.id, action: 'correct', corrected_response: text }),
|
||||||
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()
|
||||||
fetchStats()
|
store.setLastAction('correct', item)
|
||||||
if (store.queue.length < 5) fetchBatch()
|
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() {
|
async function handleDiscard() {
|
||||||
if (!store.current) return
|
if (!store.current) return
|
||||||
const item = store.current
|
const item = store.current
|
||||||
store.setLastAction('discard', item)
|
try {
|
||||||
store.removeCurrentFromQueue()
|
const res = await fetch('/api/sft/submit', {
|
||||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
method: 'POST',
|
||||||
await fetch('/api/sft/submit', {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
method: 'POST',
|
body: JSON.stringify({ id: item.id, action: 'discard' }),
|
||||||
headers: { 'Content-Type': 'application/json' },
|
})
|
||||||
body: JSON.stringify({ id: item.id, action: 'discard' }),
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
})
|
store.removeCurrentFromQueue()
|
||||||
fetchStats()
|
store.setLastAction('discard', item)
|
||||||
if (store.queue.length < 5) fetchBatch()
|
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() {
|
async function handleFlag() {
|
||||||
if (!store.current) return
|
if (!store.current) return
|
||||||
const item = store.current
|
const item = store.current
|
||||||
store.setLastAction('flag', item)
|
try {
|
||||||
store.removeCurrentFromQueue()
|
const res = await fetch('/api/sft/submit', {
|
||||||
store.totalRemaining = Math.max(0, store.totalRemaining - 1)
|
method: 'POST',
|
||||||
await fetch('/api/sft/submit', {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
method: 'POST',
|
body: JSON.stringify({ id: item.id, action: 'flag' }),
|
||||||
headers: { 'Content-Type': 'application/json' },
|
})
|
||||||
body: JSON.stringify({ id: item.id, action: 'flag' }),
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
})
|
store.removeCurrentFromQueue()
|
||||||
fetchStats()
|
store.setLastAction('flag', item)
|
||||||
if (store.queue.length < 5) fetchBatch()
|
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() {
|
async function handleUndo() {
|
||||||
if (!store.lastAction) return
|
if (!store.lastAction) return
|
||||||
const { item } = store.lastAction
|
const action = store.lastAction
|
||||||
store.restoreItem(item)
|
const { item } = action
|
||||||
store.totalRemaining++
|
try {
|
||||||
store.clearLastAction()
|
const res = await fetch('/api/sft/undo', {
|
||||||
await fetch('/api/sft/undo', {
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: { 'Content-Type': 'application/json' },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: JSON.stringify({ id: item.id }),
|
||||||
body: JSON.stringify({ id: item.id }),
|
})
|
||||||
})
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
fetchStats()
|
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(() => {
|
onMounted(() => {
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,18 @@ const importingRunId = ref<string | null>(null)
|
||||||
const importResult = ref<{ imported: number; skipped: number } | null>(null)
|
const importResult = ref<{ imported: number; skipped: number } | null>(null)
|
||||||
const saveStatus = ref('')
|
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() {
|
async function saveSftConfig() {
|
||||||
saveStatus.value = 'Saving…'
|
saveStatus.value = 'Saving…'
|
||||||
try {
|
try {
|
||||||
|
|
@ -322,7 +334,10 @@ function onKeyHintsChange() {
|
||||||
document.documentElement.classList.toggle('hide-key-hints', !keyHints.value)
|
document.documentElement.classList.toggle('hide-key-hints', !keyHints.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(reload)
|
onMounted(() => {
|
||||||
|
reload()
|
||||||
|
loadSftConfig()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue