feat: add training export state and actions to fineTune store
This commit is contained in:
parent
25473aef77
commit
8e6cc02295
2 changed files with 125 additions and 0 deletions
|
|
@ -1,8 +1,11 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useFineTuneStore } from './fineTune'
|
||||
import type { DbPair } from './fineTune'
|
||||
|
||||
vi.mock('../../composables/useApi', () => ({ useApiFetch: vi.fn() }))
|
||||
vi.mock('../appConfig', () => ({ useAppConfigStore: vi.fn(() => ({ isDemo: false })) }))
|
||||
vi.mock('../../composables/useToast', () => ({ showToast: vi.fn() }))
|
||||
import { useApiFetch } from '../../composables/useApi'
|
||||
const mockFetch = vi.mocked(useApiFetch)
|
||||
|
||||
|
|
@ -36,4 +39,47 @@ describe('useFineTuneStore', () => {
|
|||
expect(mockFetch).toHaveBeenCalledWith('/api/settings/fine-tune/status')
|
||||
store.stopPolling()
|
||||
})
|
||||
|
||||
it('toggleOptIn updates optedIn state', async () => {
|
||||
mockFetch.mockResolvedValue({ data: { ok: true, enabled: true }, error: null })
|
||||
const store = useFineTuneStore()
|
||||
await store.toggleOptIn(true)
|
||||
expect(store.optedIn).toBe(true)
|
||||
})
|
||||
|
||||
it('loadDbPairs no-ops when not opted in', async () => {
|
||||
const store = useFineTuneStore()
|
||||
store.optedIn = false
|
||||
await store.loadDbPairs()
|
||||
expect(store.dbPairs).toEqual([])
|
||||
expect(mockFetch).not.toHaveBeenCalledWith('/api/settings/fine-tune/db-pairs')
|
||||
})
|
||||
|
||||
it('loadDbPairs fetches when opted in', async () => {
|
||||
const pairs: DbPair[] = [{ job_id: 1, title: 'Eng', company: 'Acme', status: 'applied', instruction: 'Write...', input_preview: 'Build', excluded: false }]
|
||||
mockFetch.mockResolvedValue({ data: { pairs, total: 1, excluded_count: 0 }, error: null })
|
||||
const store = useFineTuneStore()
|
||||
store.optedIn = true
|
||||
await store.loadDbPairs()
|
||||
expect(store.dbPairs).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('excludeDbPair marks pair excluded and increments count', async () => {
|
||||
mockFetch.mockResolvedValue({ data: { ok: true }, error: null })
|
||||
const store = useFineTuneStore()
|
||||
store.dbPairs = [{ job_id: 1, title: 'Eng', company: 'Acme', status: 'applied', instruction: 'Write...', input_preview: 'Build', excluded: false }]
|
||||
await store.excludeDbPair(1)
|
||||
expect(store.dbPairs[0].excluded).toBe(true)
|
||||
expect(store.dbExcludedCount).toBe(1)
|
||||
})
|
||||
|
||||
it('includeDbPair marks pair included and decrements excludedCount', async () => {
|
||||
mockFetch.mockResolvedValue({ data: { ok: true }, error: null })
|
||||
const store = useFineTuneStore()
|
||||
store.dbPairs = [{ job_id: 1, title: 'Eng', company: 'Acme', status: 'applied', instruction: 'Write...', input_preview: 'Build', excluded: true }]
|
||||
store.dbExcludedCount = 1
|
||||
await store.includeDbPair(1)
|
||||
expect(store.dbPairs[0].excluded).toBe(false)
|
||||
expect(store.dbExcludedCount).toBe(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,6 +10,16 @@ export interface TrainingPair {
|
|||
source_file: string
|
||||
}
|
||||
|
||||
export interface DbPair {
|
||||
job_id: number
|
||||
title: string
|
||||
company: string
|
||||
status: string
|
||||
instruction: string
|
||||
input_preview: string
|
||||
excluded: boolean
|
||||
}
|
||||
|
||||
export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
||||
const step = ref(1)
|
||||
const inFlightJob = ref(false)
|
||||
|
|
@ -22,6 +32,11 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
|||
const pairsLoading = ref(false)
|
||||
let _pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const optedIn = ref(false)
|
||||
const dbPairs = ref<DbPair[]>([])
|
||||
const dbPairsLoading = ref(false)
|
||||
const dbExcludedCount = ref(0)
|
||||
|
||||
function resetStep() { step.value = 1 }
|
||||
|
||||
async function loadStatus() {
|
||||
|
|
@ -31,6 +46,7 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
|||
pairsCount.value = data.pairs_count ?? 0
|
||||
quotaRemaining.value = data.quota_remaining ?? null
|
||||
inFlightJob.value = ['queued', 'running'].includes(data.status)
|
||||
optedIn.value = (data as any).opted_in ?? false
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
|
|
@ -68,6 +84,60 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
|||
}
|
||||
}
|
||||
|
||||
async function toggleOptIn(enabled: boolean) {
|
||||
const { data } = await useApiFetch<{ ok: boolean; enabled: boolean }>(
|
||||
'/api/settings/fine-tune/opt-in',
|
||||
{ method: 'PATCH', body: JSON.stringify({ enabled }), headers: { 'Content-Type': 'application/json' } },
|
||||
)
|
||||
if (data) optedIn.value = data.enabled
|
||||
}
|
||||
|
||||
async function loadDbPairs() {
|
||||
if (!optedIn.value) { dbPairs.value = []; return }
|
||||
dbPairsLoading.value = true
|
||||
const { data } = await useApiFetch<{ pairs: DbPair[]; total: number; excluded_count: number }>(
|
||||
'/api/settings/fine-tune/db-pairs',
|
||||
)
|
||||
dbPairsLoading.value = false
|
||||
if (data) {
|
||||
dbPairs.value = data.pairs
|
||||
dbExcludedCount.value = data.excluded_count
|
||||
}
|
||||
}
|
||||
|
||||
async function excludeDbPair(jobId: number) {
|
||||
const { data } = await useApiFetch<{ ok: boolean }>(
|
||||
`/api/settings/fine-tune/db-pairs/${jobId}/exclude`,
|
||||
{ method: 'PATCH' },
|
||||
)
|
||||
if (data?.ok) {
|
||||
dbPairs.value = dbPairs.value.map(p =>
|
||||
p.job_id === jobId ? { ...p, excluded: true } : p,
|
||||
)
|
||||
dbExcludedCount.value += 1
|
||||
}
|
||||
}
|
||||
|
||||
async function includeDbPair(jobId: number) {
|
||||
const { data } = await useApiFetch<{ ok: boolean }>(
|
||||
`/api/settings/fine-tune/db-pairs/${jobId}/include`,
|
||||
{ method: 'PATCH' },
|
||||
)
|
||||
if (data?.ok) {
|
||||
dbPairs.value = dbPairs.value.map(p =>
|
||||
p.job_id === jobId ? { ...p, excluded: false } : p,
|
||||
)
|
||||
dbExcludedCount.value = Math.max(0, dbExcludedCount.value - 1)
|
||||
}
|
||||
}
|
||||
|
||||
function downloadExport() {
|
||||
const a = document.createElement('a')
|
||||
a.href = '/api/settings/fine-tune/export'
|
||||
a.download = 'peregrine_training_pairs.jsonl'
|
||||
a.click()
|
||||
}
|
||||
|
||||
return {
|
||||
step,
|
||||
inFlightJob,
|
||||
|
|
@ -85,5 +155,14 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
|||
submitJob,
|
||||
loadPairs,
|
||||
deletePair,
|
||||
optedIn,
|
||||
dbPairs,
|
||||
dbPairsLoading,
|
||||
dbExcludedCount,
|
||||
toggleOptIn,
|
||||
loadDbPairs,
|
||||
excludeDbPair,
|
||||
includeDbPair,
|
||||
downloadExport,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue