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 { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import { setActivePinia, createPinia } from 'pinia'
|
import { setActivePinia, createPinia } from 'pinia'
|
||||||
import { useFineTuneStore } from './fineTune'
|
import { useFineTuneStore } from './fineTune'
|
||||||
|
import type { DbPair } from './fineTune'
|
||||||
|
|
||||||
vi.mock('../../composables/useApi', () => ({ useApiFetch: vi.fn() }))
|
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'
|
import { useApiFetch } from '../../composables/useApi'
|
||||||
const mockFetch = vi.mocked(useApiFetch)
|
const mockFetch = vi.mocked(useApiFetch)
|
||||||
|
|
||||||
|
|
@ -36,4 +39,47 @@ describe('useFineTuneStore', () => {
|
||||||
expect(mockFetch).toHaveBeenCalledWith('/api/settings/fine-tune/status')
|
expect(mockFetch).toHaveBeenCalledWith('/api/settings/fine-tune/status')
|
||||||
store.stopPolling()
|
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
|
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', () => {
|
export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
||||||
const step = ref(1)
|
const step = ref(1)
|
||||||
const inFlightJob = ref(false)
|
const inFlightJob = ref(false)
|
||||||
|
|
@ -22,6 +32,11 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
||||||
const pairsLoading = ref(false)
|
const pairsLoading = ref(false)
|
||||||
let _pollTimer: ReturnType<typeof setInterval> | null = null
|
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 }
|
function resetStep() { step.value = 1 }
|
||||||
|
|
||||||
async function loadStatus() {
|
async function loadStatus() {
|
||||||
|
|
@ -31,6 +46,7 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
||||||
pairsCount.value = data.pairs_count ?? 0
|
pairsCount.value = data.pairs_count ?? 0
|
||||||
quotaRemaining.value = data.quota_remaining ?? null
|
quotaRemaining.value = data.quota_remaining ?? null
|
||||||
inFlightJob.value = ['queued', 'running'].includes(data.status)
|
inFlightJob.value = ['queued', 'running'].includes(data.status)
|
||||||
|
optedIn.value = (data as any).opted_in ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
function startPolling() {
|
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 {
|
return {
|
||||||
step,
|
step,
|
||||||
inFlightJob,
|
inFlightJob,
|
||||||
|
|
@ -85,5 +155,14 @@ export const useFineTuneStore = defineStore('settings/fineTune', () => {
|
||||||
submitJob,
|
submitJob,
|
||||||
loadPairs,
|
loadPairs,
|
||||||
deletePair,
|
deletePair,
|
||||||
|
optedIn,
|
||||||
|
dbPairs,
|
||||||
|
dbPairsLoading,
|
||||||
|
dbExcludedCount,
|
||||||
|
toggleOptIn,
|
||||||
|
loadDbPairs,
|
||||||
|
excludeDbPair,
|
||||||
|
includeDbPair,
|
||||||
|
downloadExport,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue