mutiple fixes
This commit is contained in:
@@ -3,6 +3,7 @@ import { ref, onMounted, computed } from "vue";
|
||||
import PuzzleCard from "@/components/PuzzleCard.vue";
|
||||
import SubmissionForm from "@/components/SubmissionForm.vue";
|
||||
import AdminPanel from "@/components/AdminPanel.vue";
|
||||
import Results from "@/components/Results.vue";
|
||||
import { apiService, errorHelpers } from "@/services/apiService";
|
||||
import { usePuzzlesStore } from "@/stores/puzzles";
|
||||
import { useSubmissionsStore } from "@/stores/submissions";
|
||||
@@ -39,10 +40,10 @@ const responsesByPuzzle = computed(() => {
|
||||
submissions.value.forEach((submission) => {
|
||||
submission.responses.forEach((response) => {
|
||||
// Handle both number and object types for puzzle field
|
||||
if (!grouped[response.puzzle]) {
|
||||
grouped[response.puzzle] = [];
|
||||
if (!grouped[response.puzzle_id]) {
|
||||
grouped[response.puzzle_id] = [];
|
||||
}
|
||||
grouped[response.puzzle].push(response);
|
||||
grouped[response.puzzle_id].push(response);
|
||||
});
|
||||
});
|
||||
return grouped;
|
||||
@@ -197,6 +198,8 @@ const reloadPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Results />
|
||||
|
||||
<!-- Admin Panel (only for superusers) -->
|
||||
<div v-if="isSuperuser">
|
||||
<AdminPanel />
|
||||
|
||||
@@ -151,10 +151,6 @@
|
||||
<img :src="file.file_url" />
|
||||
</div>
|
||||
|
||||
<div class="mockup-code w-full">
|
||||
<pre><code>{{ validationModal}}</code></pre>
|
||||
</div>
|
||||
|
||||
<div v-if="validationModal.response" class="space-y-4">
|
||||
<div class="alert alert-info">
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
@@ -194,7 +190,9 @@
|
||||
v-model="validationModal.data.validated_cost"
|
||||
type="text"
|
||||
class="input input-bordered input-sm"
|
||||
:placeholder="validationModal.response.cost || 'Enter cost'"
|
||||
:placeholder="
|
||||
validationModal.response.cost?.toString() || 'Enter cost'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -206,7 +204,9 @@
|
||||
v-model="validationModal.data.validated_cycles"
|
||||
type="text"
|
||||
class="input input-bordered input-sm"
|
||||
:placeholder="validationModal.response.cycles || 'Enter cycles'"
|
||||
:placeholder="
|
||||
validationModal.response.cycles?.toString() || 'Enter cycles'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -218,7 +218,9 @@
|
||||
v-model="validationModal.data.validated_area"
|
||||
type="text"
|
||||
class="input input-bordered input-sm"
|
||||
:placeholder="validationModal.response.area || 'Enter area'"
|
||||
:placeholder="
|
||||
validationModal.response.area?.toString() || 'Enter area'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,6 +241,10 @@
|
||||
{{ isValidating ? "Validating..." : "Validate" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mockup-code w-full">
|
||||
<pre><code>{{ validationModal}}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop" @click="closeValidationModal"></div>
|
||||
@@ -270,9 +276,9 @@ const validationModal = ref({
|
||||
response: null as PuzzleResponse | null,
|
||||
data: {
|
||||
puzzle: -1,
|
||||
validated_cost: "",
|
||||
validated_cycles: "",
|
||||
validated_area: "",
|
||||
validated_cost: 0,
|
||||
validated_cycles: 0,
|
||||
validated_area: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -332,10 +338,10 @@ const autoValidationResponse = async () => {
|
||||
const openValidationModal = (response: PuzzleResponse) => {
|
||||
validationModal.value.response = response;
|
||||
validationModal.value.data = {
|
||||
puzzle: response.puzzle || -1,
|
||||
validated_cost: response.cost || "",
|
||||
validated_cycles: response.cycles || "",
|
||||
validated_area: response.area || "",
|
||||
puzzle: response.puzzle_id || -1,
|
||||
validated_cost: response.cost || 0,
|
||||
validated_cycles: response.cycles || 0,
|
||||
validated_area: response.area || 0,
|
||||
};
|
||||
validationModal.value.show = true;
|
||||
};
|
||||
@@ -345,9 +351,9 @@ const closeValidationModal = () => {
|
||||
validationModal.value.response = null;
|
||||
validationModal.value.data = {
|
||||
puzzle: -1,
|
||||
validated_cost: "",
|
||||
validated_cycles: "",
|
||||
validated_area: "",
|
||||
validated_cost: 0,
|
||||
validated_cycles: 0,
|
||||
validated_area: 0,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow duration-300"
|
||||
class="card bg-base-100 shadow-lg hover:shadow-2xl transition-shadow duration-300"
|
||||
:class="responses?.length == 0 ? 'shadow-red-900' : 'shadow-primary-300'"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="flex items-start justify-between">
|
||||
@@ -14,9 +15,7 @@
|
||||
<div class="badge badge-primary badge-sm">
|
||||
{{ puzzle.steam_item_id }}
|
||||
</div>
|
||||
<div class="badge badge-ghost badge-sm">
|
||||
Order: {{ puzzle.order_index + 1 }}
|
||||
</div>
|
||||
<div class="badge badge-ghost badge-sm">ID: {{ puzzle.id }}</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="mb-8">
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl">General Results</h2>
|
||||
<div class="flex flex-wrap gap-4 mt-4">TODO :)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,9 +119,9 @@ export class ApiService {
|
||||
responses: Array<{
|
||||
puzzle_id: number
|
||||
puzzle_name: string
|
||||
cost?: string
|
||||
cycles?: string
|
||||
area?: string
|
||||
cost?: number
|
||||
cycles?: number
|
||||
area?: number
|
||||
needs_manual_validation?: boolean
|
||||
ocr_confidence_cost?: number
|
||||
ocr_confidence_cycles?: number
|
||||
@@ -147,9 +147,9 @@ export class ApiService {
|
||||
async validateResponse(
|
||||
responseId: number,
|
||||
validationData: {
|
||||
validated_cost?: string
|
||||
validated_cycles?: string
|
||||
validated_area?: string
|
||||
validated_cost?: number
|
||||
validated_cycles?: number
|
||||
validated_area?: number
|
||||
}
|
||||
): Promise<ApiResponse<PuzzleResponse>> {
|
||||
return this.request<PuzzleResponse>(`/submissions/responses/${responseId}/validate`, {
|
||||
@@ -237,61 +237,23 @@ export const submissionHelpers = {
|
||||
notes?: string,
|
||||
manualValidationRequested?: boolean
|
||||
): Promise<ApiResponse<Submission>> {
|
||||
// Group files by detected puzzle
|
||||
const responsesByPuzzle: Record<string, {
|
||||
puzzle: SteamCollectionItem | null,
|
||||
files: SubmissionFile[]
|
||||
}> = {}
|
||||
|
||||
files.forEach(file => {
|
||||
// Use manual puzzle selection if available, otherwise fall back to OCR
|
||||
const puzzleName = file.manualPuzzleSelection || file.ocrData?.puzzle
|
||||
const responses = files.map(item => {
|
||||
|
||||
if (puzzleName) {
|
||||
if (!responsesByPuzzle[puzzleName]) {
|
||||
responsesByPuzzle[puzzleName] = {
|
||||
puzzle: puzzleHelpers.findPuzzleByName(puzzles, puzzleName),
|
||||
files: []
|
||||
}
|
||||
}
|
||||
responsesByPuzzle[puzzleName].files.push(file)
|
||||
}
|
||||
})
|
||||
|
||||
// Create responses array
|
||||
const responses = Object.entries(responsesByPuzzle)
|
||||
.filter(([_, data]) => data.puzzle) // Only include matched puzzles
|
||||
.map(([puzzleName, data]) => {
|
||||
// Get OCR data from the first file with complete data
|
||||
const fileWithOCR = data.files.find(f =>
|
||||
f.ocrData?.cost || f.ocrData?.cycles || f.ocrData?.area
|
||||
)
|
||||
|
||||
// Check if manual validation is needed
|
||||
const needsValidation = !fileWithOCR?.ocrData ||
|
||||
!fileWithOCR.ocrData.cost ||
|
||||
!fileWithOCR.ocrData.cycles ||
|
||||
!fileWithOCR.ocrData.area
|
||||
|
||||
return {
|
||||
puzzle_id: data.puzzle!.id,
|
||||
puzzle_name: puzzleName,
|
||||
cost: fileWithOCR?.ocrData?.cost,
|
||||
cycles: fileWithOCR?.ocrData?.cycles,
|
||||
area: fileWithOCR?.ocrData?.area,
|
||||
needs_manual_validation: needsValidation,
|
||||
ocr_confidence_cost: fileWithOCR?.ocrData?.confidence?.cost || 0.0,
|
||||
ocr_confidence_cycles: fileWithOCR?.ocrData?.confidence?.cycles || 0.0,
|
||||
ocr_confidence_area: fileWithOCR?.ocrData?.confidence?.area || 0.0
|
||||
}
|
||||
})
|
||||
|
||||
if (responses.length === 0) {
|
||||
const puzzle = puzzleHelpers.findPuzzleByName(puzzles, item.ocrData?.puzzle || '')
|
||||
if (!puzzle) { return }
|
||||
return {
|
||||
error: 'No valid puzzle responses found',
|
||||
status: 400
|
||||
puzzle_id: puzzle.id,
|
||||
puzzle_name: item.ocrData?.puzzle || '',
|
||||
cost: item.ocrData?.cost,
|
||||
cycles: item.ocrData?.cycles,
|
||||
area: item.ocrData?.area,
|
||||
needs_manual_validation: (item.ocrData?.confidence.overall ?? 0) <= 0.8,
|
||||
ocr_confidence_cost: item.ocrData?.confidence?.cost || 0.0,
|
||||
ocr_confidence_cycles: item.ocrData?.confidence?.cycles || 0.0,
|
||||
ocr_confidence_area: item.ocrData?.confidence?.area || 0.0
|
||||
}
|
||||
}
|
||||
}).filter(item => item !== undefined)
|
||||
|
||||
// Extract actual File objects for upload
|
||||
const fileObjects = files.map(f => f.file)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { OpusMagnumData } from '@/types';
|
||||
import { createWorker } from 'tesseract.js';
|
||||
|
||||
export interface OpusMagnumData {
|
||||
export interface OpusMagnumOCRData {
|
||||
puzzle: string;
|
||||
cost: string;
|
||||
cycles: string;
|
||||
@@ -132,7 +133,7 @@ export class OpusMagnumOCRService {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
// Extract text from each region
|
||||
const results: Partial<OpusMagnumData> = {};
|
||||
const results: Partial<OpusMagnumOCRData> = {};
|
||||
const confidenceScores: Record<string, number> = {};
|
||||
|
||||
for (const [key, region] of Object.entries(this.regions)) {
|
||||
@@ -227,9 +228,9 @@ export class OpusMagnumOCRService {
|
||||
|
||||
resolve({
|
||||
puzzle: results.puzzle || '',
|
||||
cost: results.cost || '',
|
||||
cycles: results.cycles || '',
|
||||
area: results.area || '',
|
||||
cost: parseInt(results.cost || ''),
|
||||
cycles: parseInt(results.cycles || ''),
|
||||
area: parseInt(results.area || ''),
|
||||
confidence: {
|
||||
puzzle: confidenceScores.puzzle || 0,
|
||||
cost: confidenceScores.cost || 0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { Submission, SubmissionFile } from '@/types'
|
||||
import { submissionHelpers } from '@/services/apiService'
|
||||
@@ -12,6 +12,9 @@ export const useSubmissionsStore = defineStore('submissions', () => {
|
||||
const error = ref<string>('')
|
||||
const isSubmissionModalOpen = ref(false)
|
||||
|
||||
const puzzlesStore = usePuzzlesStore()
|
||||
const { puzzles } = storeToRefs(puzzlesStore)
|
||||
|
||||
// Actions
|
||||
const loadSubmissions = async (limit = 20, offset = 0) => {
|
||||
try {
|
||||
@@ -42,11 +45,9 @@ export const useSubmissionsStore = defineStore('submissions', () => {
|
||||
isLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
const puzzlesStore = usePuzzlesStore()
|
||||
|
||||
const response = await submissionHelpers.createFromFiles(
|
||||
files,
|
||||
puzzlesStore.puzzles,
|
||||
puzzles.value,
|
||||
notes,
|
||||
manualValidationRequested
|
||||
)
|
||||
|
||||
@@ -26,9 +26,9 @@ export interface SteamCollectionItem {
|
||||
|
||||
export interface OpusMagnumData {
|
||||
puzzle: string
|
||||
cost: string
|
||||
cycles: string
|
||||
area: string
|
||||
cost: number
|
||||
cycles: number
|
||||
area: number
|
||||
confidence: {
|
||||
puzzle: number
|
||||
cost: number
|
||||
@@ -54,21 +54,21 @@ export interface SubmissionFile {
|
||||
export interface PuzzleResponse {
|
||||
id?: number
|
||||
// puzzle: number | SteamCollectionItem
|
||||
puzzle: number
|
||||
puzzle_id: number
|
||||
puzzle_name: string
|
||||
cost?: string
|
||||
cycles?: string
|
||||
area?: string
|
||||
cost?: number
|
||||
cycles?: number
|
||||
area?: number
|
||||
needs_manual_validation?: boolean
|
||||
ocr_confidence_cost?: number
|
||||
ocr_confidence_cycles?: number
|
||||
ocr_confidence_area?: number
|
||||
validated_cost?: string
|
||||
validated_cycles?: string
|
||||
validated_area?: string
|
||||
final_cost?: string
|
||||
final_cycles?: string
|
||||
final_area?: string
|
||||
validated_cost?: number
|
||||
validated_cycles?: number
|
||||
validated_area?: number
|
||||
final_cost?: number
|
||||
final_cycles?: number
|
||||
final_area?: number
|
||||
files?: SubmissionFile[]
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
|
||||
Reference in New Issue
Block a user