From 6bfb2bf3f7f65f48ba48494999c2612e0e1d652f Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 3 May 2026 01:04:43 -0700 Subject: [PATCH] feat: add Training Export and From Applied Jobs sections to FineTuneView (a11y-correct) --- web/src/views/settings/FineTuneView.vue | 144 +++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/web/src/views/settings/FineTuneView.vue b/web/src/views/settings/FineTuneView.vue index e4f0b0e..0086f1e 100644 --- a/web/src/views/settings/FineTuneView.vue +++ b/web/src/views/settings/FineTuneView.vue @@ -3,16 +3,31 @@ import { ref, onMounted, onUnmounted } from 'vue' import { storeToRefs } from 'pinia' import { useFineTuneStore } from '../../stores/settings/fineTune' import { useAppConfigStore } from '../../stores/appConfig' +import { showToast } from '../../composables/useToast' const store = useFineTuneStore() const config = useAppConfigStore() -const { step, inFlightJob, jobStatus, pairsCount, quotaRemaining, pairs, pairsLoading } = storeToRefs(store) +const { step, inFlightJob, jobStatus, pairsCount, quotaRemaining, pairs, pairsLoading, + optedIn, dbPairs, dbPairsLoading, dbExcludedCount } = storeToRefs(store) const fileInput = ref(null) const selectedFiles = ref([]) const uploadResult = ref<{ file_count: number } | null>(null) const extractError = ref(null) const modelReady = ref(null) +const toggling = ref(false) +const toggleSaved = ref(false) + +async function handleOptInChange(e: Event) { + const enabled = (e.target as HTMLInputElement).checked + toggling.value = true + toggleSaved.value = false + await store.toggleOptIn(enabled) + await store.loadDbPairs() + toggling.value = false + toggleSaved.value = true + setTimeout(() => { toggleSaved.value = false }, 2000) +} async function handleUpload() { if (!selectedFiles.value.length) return @@ -46,6 +61,7 @@ async function checkLocalModel() { onMounted(async () => { store.startPolling() await store.loadPairs() + await store.loadDbPairs() if (store.step === 3 && !config.isCloud) await checkLocalModel() }) onUnmounted(() => { store.stopPolling(); store.resetStep() }) @@ -55,6 +71,115 @@ onUnmounted(() => { store.stopPolling(); store.resetStep() })

Fine-Tune Model

+ + + + +
+

From Applied Jobs

+
+ + {{ + dbPairs.filter(p => !p.excluded).length === 1 + ? '1 pair available' + : `${dbPairs.filter(p => !p.excluded).length} pairs available` + }} + {{ dbExcludedCount }} excluded + +
+ +
+ +

+ Available on Premium. + Upgrade your plan → +

+
+
+
+

+ The downloaded file contains your cover letters in plain text (JSONL format). + Store it in a secure location. +

+ +
+
Loading…
+
    +
  • +
    + {{ pair.title }} · {{ pair.company }} + {{ pair.status }} +
    + + +
  • +
+

No applied jobs with cover letters found.

+
+
+
1. Upload @@ -189,4 +314,21 @@ onUnmounted(() => { store.stopPolling(); store.resetStep() }) .pair-source { font-size: 0.75rem; color: var(--color-text-muted); } .pair-delete { flex-shrink: 0; background: none; border: none; color: var(--color-error); cursor: pointer; font-size: 0.9rem; padding: 2px 4px; border-radius: var(--radius-sm); transition: background 150ms; } .pair-delete:hover { background: var(--color-error); color: #fff; } +.training-export-consent { border: 1px solid var(--color-border-light); border-radius: var(--radius-md); padding: var(--space-4, 1rem); margin-bottom: var(--space-6, 1.5rem); } +.toggle-label { display: flex; align-items: center; gap: var(--space-2, 0.5rem); font-size: 0.9rem; font-weight: 500; cursor: pointer; flex-wrap: wrap; } +.toggle-label.toggle-saving { opacity: 0.7; } +.toggle-label input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--color-primary); cursor: pointer; flex-shrink: 0; } +.toggle-status { font-size: 0.8rem; color: var(--color-text-muted); margin-left: var(--space-1, 0.25rem); } +.opt-out-receipt { display: block; margin-top: var(--space-1, 0.25rem); color: var(--color-text-muted); font-size: 0.8rem; } +.db-pairs-header { display: flex; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; gap: var(--space-3, 0.75rem); margin-bottom: var(--space-4, 1rem); } +.db-pairs-actions { display: flex; align-items: flex-start; gap: var(--space-2, 0.5rem); flex-wrap: wrap; } +.cloud-finetune-wrap { display: flex; flex-direction: column; gap: var(--space-1, 0.25rem); } +.tier-gate-note { font-size: 0.8rem; color: var(--color-text-muted); margin: 0; } +.upgrade-link { color: var(--color-primary); text-decoration: underline; } +.excluded-badge { margin-left: var(--space-2, 0.5rem); background: var(--color-warning-bg, #fef3c7); color: var(--color-warning-fg, #92400e); font-size: 0.75rem; padding: 1px 6px; border-radius: var(--radius-full, 9999px); } +.db-pairs-items { max-height: 320px; } +.pair-excluded { opacity: 0.5; } +.pair-restore { flex-shrink: 0; background: none; border: 1px solid var(--color-border); color: var(--color-text-muted); cursor: pointer; font-size: 0.8rem; padding: 2px 8px; border-radius: var(--radius-sm); } +.pair-restore:hover { background: var(--color-surface-alt); } +.download-advisory { margin-top: var(--space-2, 0.5rem); font-style: italic; }