mutiple fixes

This commit is contained in:
2025-11-28 14:05:26 +01:00
parent 9ee45463a8
commit fa76fbce92
36 changed files with 694 additions and 242 deletions
+6 -3
View File
@@ -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 />
+23 -17
View File
@@ -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,
};
};
+3 -4
View File
@@ -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
+12
View File
@@ -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>
+19 -57
View File
@@ -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)
+6 -5
View 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,
+5 -4
View File
@@ -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
)
+13 -13
View File
@@ -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