chore: opus-submitter -> polylan-submitter
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
export const pinia = createPinia()
|
||||
@@ -0,0 +1,78 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { SteamCollectionItem } from '@/types'
|
||||
import { apiService } from '@/services/apiService'
|
||||
|
||||
export const usePuzzlesStore = defineStore('puzzles', () => {
|
||||
// State
|
||||
const puzzles = ref<SteamCollectionItem[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string>('')
|
||||
|
||||
// Getters
|
||||
const puzzleNames = computed(() => puzzles.value.map(puzzle => puzzle.title))
|
||||
|
||||
const findPuzzleByName = computed(() => (name: string): SteamCollectionItem | null => {
|
||||
if (!name) return null
|
||||
|
||||
// First try exact match (case insensitive)
|
||||
const exactMatch = puzzles.value.find(
|
||||
puzzle => puzzle.title.toLowerCase() === name.toLowerCase()
|
||||
)
|
||||
if (exactMatch) return exactMatch
|
||||
|
||||
// Then try partial match
|
||||
const partialMatch = puzzles.value.find(
|
||||
puzzle => puzzle.title.toLowerCase().includes(name.toLowerCase()) ||
|
||||
name.toLowerCase().includes(puzzle.title.toLowerCase())
|
||||
)
|
||||
|
||||
return partialMatch || null
|
||||
})
|
||||
|
||||
// Actions
|
||||
const loadPuzzles = async () => {
|
||||
if (puzzles.value.length > 0) return // Already loaded
|
||||
|
||||
try {
|
||||
isLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
const response = await apiService.getPuzzles()
|
||||
if (response.error) {
|
||||
error.value = response.error
|
||||
console.error('Failed to load puzzles:', response.error)
|
||||
return
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
puzzles.value = response.data
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = 'Failed to load puzzles'
|
||||
console.error('Error loading puzzles:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshPuzzles = async () => {
|
||||
puzzles.value = []
|
||||
await loadPuzzles()
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
puzzles,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Getters
|
||||
puzzleNames,
|
||||
findPuzzleByName,
|
||||
|
||||
// Actions
|
||||
loadPuzzles,
|
||||
refreshPuzzles
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,146 @@
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { Submission, SubmissionFile } from '@/types'
|
||||
import { submissionHelpers } from '@/services/apiService'
|
||||
import { usePuzzlesStore } from '@/stores/puzzles'
|
||||
import { errorHelpers } from "@/services/apiService";
|
||||
|
||||
export const useSubmissionsStore = defineStore('submissions', () => {
|
||||
// State
|
||||
const submissions = ref<Submission[]>([])
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string>('')
|
||||
const isSubmissionModalOpen = ref(false)
|
||||
|
||||
const puzzlesStore = usePuzzlesStore()
|
||||
const { puzzles } = storeToRefs(puzzlesStore)
|
||||
|
||||
// Actions
|
||||
const loadSubmissions = async (limit = 20, offset = 0) => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
const loadedSubmissions = await submissionHelpers.loadSubmissions(limit, offset)
|
||||
|
||||
if (offset === 0) {
|
||||
submissions.value = loadedSubmissions
|
||||
} else {
|
||||
submissions.value.push(...loadedSubmissions)
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = 'Failed to load submissions'
|
||||
console.error('Error loading submissions:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const createSubmission = async (
|
||||
files: SubmissionFile[],
|
||||
notes?: string,
|
||||
manualValidationRequested?: boolean
|
||||
): Promise<Submission | undefined> => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
const response = await submissionHelpers.createFromFiles(
|
||||
files,
|
||||
puzzles.value,
|
||||
notes,
|
||||
manualValidationRequested
|
||||
)
|
||||
|
||||
if (response.error) {
|
||||
error.value = response.error
|
||||
throw new Error(response.error)
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
// Add to local submissions list
|
||||
submissions.value.unshift(response.data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
return undefined
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to create submission'
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openSubmissionModal = () => {
|
||||
isSubmissionModalOpen.value = true
|
||||
}
|
||||
|
||||
const closeSubmissionModal = () => {
|
||||
isSubmissionModalOpen.value = false
|
||||
}
|
||||
|
||||
const refreshSubmissions = async () => {
|
||||
submissions.value = []
|
||||
await loadSubmissions()
|
||||
}
|
||||
|
||||
const handleSubmission = async (submissionData: {
|
||||
files: any[];
|
||||
notes?: string;
|
||||
manualValidationRequested?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
error.value = "";
|
||||
|
||||
// Create submission via store
|
||||
const submission = await createSubmission(
|
||||
submissionData.files,
|
||||
submissionData.notes,
|
||||
submissionData.manualValidationRequested,
|
||||
);
|
||||
|
||||
// Show success message
|
||||
if (submission) {
|
||||
const puzzleNames = submission.responses
|
||||
.map((r) => r.puzzle_name)
|
||||
.join(", ");
|
||||
|
||||
alert(`Solutions submitted successfully for puzzles: ${puzzleNames}`);
|
||||
|
||||
} else {
|
||||
alert("Submission created successfully!");
|
||||
}
|
||||
|
||||
// Close modal
|
||||
closeSubmissionModal();
|
||||
|
||||
} catch (err) {
|
||||
|
||||
const errorMessage = errorHelpers.getErrorMessage(err);
|
||||
error.value = errorMessage;
|
||||
alert(`Submission failed: ${errorMessage}`);
|
||||
|
||||
console.error("Submission error:", err);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
submissions,
|
||||
isLoading,
|
||||
error,
|
||||
isSubmissionModalOpen,
|
||||
|
||||
// Actions
|
||||
loadSubmissions,
|
||||
createSubmission,
|
||||
openSubmissionModal,
|
||||
closeSubmissionModal,
|
||||
refreshSubmissions,
|
||||
handleSubmission
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,103 @@
|
||||
import { SubmissionFile } from '@/types'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, nextTick, computed } from "vue";
|
||||
import { ocrService } from "@/services/ocrService";
|
||||
|
||||
const CONFIDENCE_VALUE = 0.8;
|
||||
|
||||
export const useUploadsStore = defineStore('uploads', () => {
|
||||
const submissionFiles = ref<SubmissionFile[]>([])
|
||||
|
||||
const isProcessingOCR = computed(() =>
|
||||
submissionFiles.value.some(item => item.ocrProcessing)
|
||||
);
|
||||
|
||||
const hasLowConfidence = computed(() =>
|
||||
submissionFiles.value.some(file => {
|
||||
return isLowConfidence(file)
|
||||
})
|
||||
)
|
||||
|
||||
const submissionFilesNeedingManualSelection = computed(() => {
|
||||
return submissionFiles.value.filter(file => file.needsManualPuzzleSelection)
|
||||
})
|
||||
|
||||
const isLowConfidence = (file: SubmissionFile) => {
|
||||
if (!file.ocrData?.confidence) return false;
|
||||
return (
|
||||
file.ocrData.confidence.cost < CONFIDENCE_VALUE ||
|
||||
file.ocrData.confidence.cycles < CONFIDENCE_VALUE ||
|
||||
file.ocrData.confidence.area < CONFIDENCE_VALUE
|
||||
)
|
||||
}
|
||||
|
||||
const processOCR = async (submissionFile: SubmissionFile) => {
|
||||
while (isProcessingOCR.value) {
|
||||
const waitingTimeMs = Math.floor(Math.random() * 400) + 100;
|
||||
console.log(`OCR is already processing, waiting ${waitingTimeMs}ms...`);
|
||||
await new Promise((res) => setTimeout(res, waitingTimeMs));
|
||||
}
|
||||
|
||||
const index = submissionFiles.value.indexOf(submissionFile)
|
||||
|
||||
// Update the reactive array directly
|
||||
submissionFiles.value[index].ocrProcessing = true;
|
||||
submissionFiles.value[index].ocrError = undefined;
|
||||
submissionFiles.value[index].ocrData = undefined;
|
||||
|
||||
try {
|
||||
console.log("Starting OCR processing for:", submissionFile.file.name);
|
||||
await ocrService.initialize();
|
||||
const ocrData = await ocrService.extractOpusMagnumData(submissionFile.file);
|
||||
console.log("OCR completed:", ocrData);
|
||||
|
||||
// Force reactivity update
|
||||
await nextTick();
|
||||
submissionFiles.value[index].ocrData = ocrData;
|
||||
|
||||
// Check if puzzle confidence is below CONFIDENCE_VALUE and needs manual selection
|
||||
if (ocrData.confidence.puzzle < CONFIDENCE_VALUE) {
|
||||
submissionFiles.value[index].needsManualPuzzleSelection = true;
|
||||
console.log(
|
||||
`Low puzzle confidence (${Math.round(ocrData.confidence.puzzle * 100)}%) for ${submissionFile.file.name}, requiring manual selection`,
|
||||
);
|
||||
} else {
|
||||
submissionFiles.value[index].needsManualPuzzleSelection = false;
|
||||
}
|
||||
|
||||
await nextTick();
|
||||
} catch (error) {
|
||||
console.error("OCR processing failed:", error);
|
||||
submissionFiles.value[index].ocrError = "Failed to extract puzzle data";
|
||||
|
||||
} finally {
|
||||
submissionFiles.value[index].ocrProcessing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const processLowConfidenceOCRFiles = async () => {
|
||||
const files = submissionFiles.value.filter(file => isLowConfidence(file))
|
||||
|
||||
for (const file of files) {
|
||||
processOCR(file)
|
||||
}
|
||||
}
|
||||
|
||||
const clearFiles = () => {
|
||||
submissionFiles.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
submissionFiles,
|
||||
submissionFilesNeedingManualSelection,
|
||||
processOCR,
|
||||
processLowConfidenceOCRFiles,
|
||||
clearFiles,
|
||||
|
||||
// computed
|
||||
isProcessingOCR,
|
||||
hasLowConfidence,
|
||||
|
||||
CONFIDENCE_VALUE
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user