Spaces:
Sleeping
Sleeping
| <template> | |
| <div class="mcts-data-loader"> | |
| <div class="file-inputs"> | |
| <!-- TGN File Input --> | |
| <div class="file-input-group"> | |
| <label for="tgn-file" class="file-label"> | |
| TGN Game File | |
| <span class="file-status" :class="{ ready: tgnFile }"> | |
| {{ tgnFile ? `✓ ${tgnFile.name}` : "Not selected" }} | |
| </span> | |
| </label> | |
| <input | |
| id="tgn-file" | |
| type="file" | |
| accept=".tgn" | |
| @change="onTGNFileChange" | |
| class="file-input" | |
| /> | |
| </div> | |
| <!-- Visit Counts JSON Input --> | |
| <div class="file-input-group"> | |
| <label for="json-file" class="file-label"> | |
| Visit Counts JSON | |
| <span class="file-status" :class="{ ready: jsonFile }"> | |
| {{ jsonFile ? `✓ ${jsonFile.name}` : "Not selected" }} | |
| </span> | |
| </label> | |
| <input | |
| id="json-file" | |
| type="file" | |
| accept=".json" | |
| @change="onJSONFileChange" | |
| class="file-input" | |
| /> | |
| </div> | |
| </div> | |
| <!-- Load Button --> | |
| <button | |
| @click="loadData" | |
| :disabled="!canLoad || loading" | |
| class="btn-load" | |
| :class="{ loading }" | |
| > | |
| {{ loading ? "Loading..." : "Load Data" }} | |
| </button> | |
| <!-- Status Messages --> | |
| <div v-if="loading" class="status-message loading"> | |
| <div class="spinner"></div> | |
| <span>Parsing data...</span> | |
| </div> | |
| <div v-if="error" class="status-message error"> | |
| <strong>Error:</strong> {{ error }} | |
| </div> | |
| <div v-if="success" class="status-message success"> | |
| <strong>Success:</strong> Loaded {{ movesCount }} moves from a {{ boardShape }} board | |
| </div> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref, computed } from "vue"; | |
| import { useMCTSStore } from "@/stores/mctsStore"; | |
| import { parseMCTSData } from "@/utils/mctsDataParser"; | |
| const mctsStore = useMCTSStore(); | |
| // File state | |
| const tgnFile = ref<File | null>(null); | |
| const jsonFile = ref<File | null>(null); | |
| // Loading state | |
| const loading = ref(false); | |
| const error = ref<string | null>(null); | |
| const success = ref(false); | |
| // Success data | |
| const movesCount = ref(0); | |
| const boardShape = ref(""); | |
| // Computed | |
| const canLoad = computed(() => tgnFile.value !== null && jsonFile.value !== null); | |
| // Handlers | |
| function onTGNFileChange(event: Event) { | |
| const target = event.target as HTMLInputElement; | |
| tgnFile.value = target.files?.[0] || null; | |
| clearMessages(); | |
| } | |
| function onJSONFileChange(event: Event) { | |
| const target = event.target as HTMLInputElement; | |
| jsonFile.value = target.files?.[0] || null; | |
| clearMessages(); | |
| } | |
| function clearMessages() { | |
| error.value = null; | |
| success.value = false; | |
| } | |
| async function loadData() { | |
| if (!canLoad.value) return; | |
| loading.value = true; | |
| clearMessages(); | |
| try { | |
| // Read files | |
| const tgnContent = await tgnFile.value!.text(); | |
| const jsonContent = await jsonFile.value!.text(); | |
| // Parse data | |
| const data = await parseMCTSData(tgnContent, jsonContent); | |
| // Store data | |
| mctsStore.setGameHistory(data); | |
| // Show success | |
| success.value = true; | |
| movesCount.value = data.length; | |
| const shape = data[0].gameState.getShape(); | |
| boardShape.value = `${shape.x}×${shape.y}×${shape.z}`; | |
| console.log(`[MCTS Loader] Successfully loaded ${data.length} moves`); | |
| } catch (err) { | |
| error.value = err instanceof Error ? err.message : String(err); | |
| console.error("[MCTS Loader] Error loading data:", err); | |
| } finally { | |
| loading.value = false; | |
| } | |
| } | |
| </script> | |
| <style scoped lang="scss"> | |
| .mcts-data-loader { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 15px; | |
| } | |
| .file-inputs { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| } | |
| .file-input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .file-label { | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: #c0c0c0; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .file-status { | |
| font-size: 11px; | |
| color: #808080; | |
| font-weight: normal; | |
| &.ready { | |
| color: #4a90e2; | |
| } | |
| } | |
| .file-input { | |
| padding: 8px; | |
| background-color: #1a1a1a; | |
| border: 1px solid #404040; | |
| border-radius: 4px; | |
| color: #e0e0e0; | |
| font-size: 13px; | |
| cursor: pointer; | |
| &:hover { | |
| border-color: #4a90e2; | |
| } | |
| &:focus { | |
| outline: none; | |
| border-color: #4a90e2; | |
| } | |
| &::file-selector-button { | |
| padding: 6px 12px; | |
| background-color: #2a2a2a; | |
| border: 1px solid #404040; | |
| border-radius: 3px; | |
| color: #e0e0e0; | |
| font-size: 12px; | |
| cursor: pointer; | |
| margin-right: 10px; | |
| &:hover { | |
| background-color: #3a3a3a; | |
| border-color: #4a90e2; | |
| } | |
| } | |
| } | |
| .btn-load { | |
| padding: 12px 20px; | |
| background-color: #4a90e2; | |
| border: none; | |
| border-radius: 4px; | |
| color: #ffffff; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: background-color 0.2s, opacity 0.2s; | |
| &:hover:not(:disabled) { | |
| background-color: #357abd; | |
| } | |
| &:active:not(:disabled) { | |
| background-color: #2a6ba8; | |
| } | |
| &:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| &.loading { | |
| opacity: 0.7; | |
| } | |
| } | |
| .status-message { | |
| padding: 10px; | |
| border-radius: 4px; | |
| font-size: 13px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| &.loading { | |
| background-color: #2a3a4a; | |
| color: #4a90e2; | |
| } | |
| &.error { | |
| background-color: #4a2a2a; | |
| color: #ff6b6b; | |
| } | |
| &.success { | |
| background-color: #2a4a2a; | |
| color: #6bff6b; | |
| } | |
| strong { | |
| font-weight: 600; | |
| } | |
| } | |
| .spinner { | |
| width: 16px; | |
| height: 16px; | |
| border: 2px solid #4a90e2; | |
| border-top-color: transparent; | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| </style> | |