feat: game info display, session car/track metadata
- Add GameInfo type + getGameInfo API call. - Telemetry store polls /api/game-info every ~2s. - Dashboard shows car @ track in Sim Connection section when receiving. - SessionDetailView shows car_name and track_name in session info. - Add car_name/track_name to UseCaseSession type.
This commit is contained in:
parent
02d3ea0515
commit
63e926f26e
5 changed files with 39 additions and 2 deletions
|
|
@ -28,6 +28,7 @@ import type {
|
|||
TrackSummary,
|
||||
BuildTrackModelRequest,
|
||||
CarProfile,
|
||||
GameInfo,
|
||||
} from '@/types'
|
||||
|
||||
const api = axios.create({
|
||||
|
|
@ -45,6 +46,11 @@ export async function getLiveTelemetry(): Promise<LiveTelemetryResponse> {
|
|||
return res.data
|
||||
}
|
||||
|
||||
export async function getGameInfo(): Promise<GameInfo> {
|
||||
const res = await api.get<GameInfo>('/game-info')
|
||||
return res.data
|
||||
}
|
||||
|
||||
// ── Recordings ──
|
||||
|
||||
export async function listRecordings(): Promise<RecordingManifest[]> {
|
||||
|
|
|
|||
|
|
@ -15,17 +15,19 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { LiveTelemetryResponse, LiveFeedSnapshot } from '@/types'
|
||||
import { getLiveTelemetry } from '@/api/client'
|
||||
import type { LiveTelemetryResponse, LiveFeedSnapshot, GameInfo } from '@/types'
|
||||
import { getLiveTelemetry, getGameInfo } from '@/api/client'
|
||||
|
||||
export const useTelemetryStore = defineStore('telemetry', () => {
|
||||
const feeds = ref<LiveFeedSnapshot[]>([])
|
||||
const activeRecording = ref<string | null>(null)
|
||||
const gameInfo = ref<GameInfo>({})
|
||||
const connected = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const lastUpdate = ref<Date | null>(null)
|
||||
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
let gameInfoCounter = 0
|
||||
|
||||
const anyFeedAlive = computed(() => feeds.value.some((f) => f.health === 'alive'))
|
||||
const anyFeedStale = computed(() => feeds.value.some((f) => f.health === 'stale'))
|
||||
|
|
@ -65,6 +67,12 @@ export const useTelemetryStore = defineStore('telemetry', () => {
|
|||
connected.value = true
|
||||
error.value = null
|
||||
lastUpdate.value = new Date()
|
||||
gameInfoCounter++
|
||||
if (gameInfoCounter % 8 === 0) {
|
||||
try {
|
||||
gameInfo.value = await getGameInfo()
|
||||
} catch { /* non-critical */ }
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
connected.value = false
|
||||
error.value = e instanceof Error ? e.message : 'Connection failed'
|
||||
|
|
@ -87,6 +95,7 @@ export const useTelemetryStore = defineStore('telemetry', () => {
|
|||
return {
|
||||
feeds,
|
||||
activeRecording,
|
||||
gameInfo,
|
||||
connected,
|
||||
error,
|
||||
lastUpdate,
|
||||
|
|
|
|||
|
|
@ -118,6 +118,8 @@ export interface UseCaseSession {
|
|||
analysis: AnalysisResult | null
|
||||
guidance: string | null
|
||||
recording_id: string | null
|
||||
car_name?: string | null
|
||||
track_name?: string | null
|
||||
}
|
||||
|
||||
export interface CreateSessionRequest {
|
||||
|
|
@ -139,6 +141,14 @@ export interface UseCaseInfo {
|
|||
guidance: string
|
||||
}
|
||||
|
||||
// ── Game Info (from KSUDP handshake) ──
|
||||
|
||||
export interface GameInfo {
|
||||
car_name?: string | null
|
||||
track_name?: string | null
|
||||
driver_name?: string | null
|
||||
}
|
||||
|
||||
// ── Analysis Results ──
|
||||
|
||||
// Shift Point Analysis (use_case = "shift_points")
|
||||
|
|
|
|||
|
|
@ -114,6 +114,10 @@ const aliveCount = computed(() => telemetry.feeds.filter((f) => f.health === 'al
|
|||
<span class="text-success">{{ aliveCount }} feed(s) receiving data</span>
|
||||
<span class="text-muted">·</span>
|
||||
<span class="text-secondary">{{ telemetry.totalPackets.toLocaleString() }} packets total</span>
|
||||
<template v-if="telemetry.gameInfo.car_name || telemetry.gameInfo.track_name">
|
||||
<span class="text-muted">·</span>
|
||||
<span class="text-secondary">{{ [telemetry.gameInfo.car_name, telemetry.gameInfo.track_name].filter(Boolean).join(' @ ') }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="telemetry.anyFeedStale">
|
||||
<span class="text-warning">Data stream stalled</span>
|
||||
|
|
|
|||
|
|
@ -240,6 +240,14 @@ async function handleBuildTrack() {
|
|||
<span class="info-label">Use Case</span>
|
||||
<span>{{ session.use_case.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) }}</span>
|
||||
</div>
|
||||
<div v-if="session.car_name" class="info-item">
|
||||
<span class="info-label">Car</span>
|
||||
<span>{{ session.car_name }}</span>
|
||||
</div>
|
||||
<div v-if="session.track_name" class="info-item">
|
||||
<span class="info-label">Track</span>
|
||||
<span>{{ session.track_name }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Created</span>
|
||||
<span>{{ formatTime(session.created_at) }}</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue