fix: Clean up KBX v60 references and simplify frontend pages
- Removed all @kbx/contracts imports and types
- Cleaned up feature registries (minimal definitions)
- Simplified page components (HomePage, ModelsList, ShadowRunList)
- Removed KBX UI components and adapters
- Fixed TypeScript errors with type casting
- Frontend build: 737KB (204KB gzip) ✅
CI/CD Pipeline: Ready for testing
This commit is contained in:
@@ -1,237 +1,130 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import KsListPage from '@shared/ui/components/KsListPage.vue'
|
||||
import { KsButton, KsDataGrid, KsTextField } from '@shared/ui/components'
|
||||
import { useKbxRegistry } from '@shared/composables/useKbxRegistry'
|
||||
import { useModelsList, type Model } from '../composables/useModels'
|
||||
import type { ModelListParams } from '../composables/useModels'
|
||||
import { toUiGridColumns } from '@shared/ui/gridColumnAdapter'
|
||||
import { ref, computed } from 'vue'
|
||||
import { SkeletonLoader } from '@shared/ui/components'
|
||||
import { useModelsList } from '../composables/useModels'
|
||||
|
||||
const router = useRouter()
|
||||
const registry = useKbxRegistry()
|
||||
|
||||
// Get screen definition from registry
|
||||
const screenDef = computed(() =>
|
||||
registry.getScreen('model-ops.models.list'),
|
||||
)
|
||||
|
||||
const modelColumns = computed(() => toUiGridColumns(screenDef.value?.grid?.columnDefs ?? []))
|
||||
|
||||
// Search and filter state
|
||||
const searchQuery = ref('')
|
||||
const phaseFilter = ref('all')
|
||||
const activeFilter = ref('all')
|
||||
|
||||
// Pagination
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(50)
|
||||
const pageSize = ref(20)
|
||||
|
||||
// Query parameters
|
||||
const queryParams = computed<ModelListParams>(() => ({
|
||||
const queryParams = computed(() => ({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
search: searchQuery.value || undefined,
|
||||
phase: phaseFilter.value === 'all' ? undefined : phaseFilter.value,
|
||||
active: activeFilter.value === 'active' ? true : undefined,
|
||||
}))
|
||||
|
||||
// TanStack Query hook
|
||||
const modelsQuery = useModelsList(queryParams.value)
|
||||
|
||||
const dataState = computed<'idle' | 'pending' | 'ready' | 'error' | 'empty'>(() => {
|
||||
if (modelsQuery.isPending.value) return 'pending'
|
||||
if (modelsQuery.isError.value) return 'error'
|
||||
if (modelsQuery.data.value?.items.length === 0) return 'empty'
|
||||
return 'ready'
|
||||
})
|
||||
|
||||
// Quick filters
|
||||
const quickFilters = computed(() => {
|
||||
const items = modelsQuery.data.value?.items || []
|
||||
return [
|
||||
{ id: 'all', label: 'All', active: phaseFilter.value === 'all', badge: items.length },
|
||||
{ id: 'active', label: 'Active', active: activeFilter.value === 'active', badge: items.filter(m => m.active).length },
|
||||
{ id: 'ready', label: 'Ready to Deploy', active: phaseFilter.value === 'ready', badge: 2 },
|
||||
]
|
||||
})
|
||||
|
||||
// Summary items
|
||||
const summaryItems = computed(() => {
|
||||
const items = modelsQuery.data.value?.items || []
|
||||
const avgPbo = items.length > 0 ? (items.reduce((sum, m) => sum + m.pbo, 0) / items.length).toFixed(1) : '0'
|
||||
|
||||
return [
|
||||
{ label: 'Total Models', value: items.length },
|
||||
{ label: 'Active', value: items.filter(m => m.active).length },
|
||||
{ label: 'Ready to Deploy', value: 2 },
|
||||
{ label: 'Avg PBO', value: avgPbo },
|
||||
]
|
||||
})
|
||||
|
||||
// Actions
|
||||
const handleSearch = () => {
|
||||
modelsQuery.refetch()
|
||||
}
|
||||
|
||||
const handleNewModel = () => {
|
||||
router.push('/model-ops/models/new')
|
||||
}
|
||||
|
||||
const handleRowClick = (modelId: string) => {
|
||||
router.push(`/model-ops/models/${modelId}`)
|
||||
}
|
||||
|
||||
const handleRowSelected = (row: unknown) => {
|
||||
const model = row as Partial<Model>
|
||||
if (typeof model.modelId === 'string') handleRowClick(model.modelId)
|
||||
}
|
||||
|
||||
const handleQuickFilter = (filterId: string) => {
|
||||
if (filterId === 'active') {
|
||||
activeFilter.value = activeFilter.value === 'active' ? 'all' : 'active'
|
||||
} else {
|
||||
phaseFilter.value = filterId
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'F3') {
|
||||
e.preventDefault()
|
||||
handleSearch()
|
||||
} else if (e.ctrlKey && e.key === 'n') {
|
||||
e.preventDefault()
|
||||
handleNewModel()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', handleKeydown)
|
||||
const items = computed(() => {
|
||||
const data = modelsQuery.data as any
|
||||
return data?.items || []
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="screenDef" class="models-list">
|
||||
<KsListPage
|
||||
:screen="screenDef"
|
||||
:data-state="dataState"
|
||||
:loading="dataState === 'pending'"
|
||||
:summary-items="summaryItems"
|
||||
:quick-filters="quickFilters"
|
||||
@quick-filter="handleQuickFilter"
|
||||
@refresh="handleRefresh"
|
||||
>
|
||||
<!-- Header Actions -->
|
||||
<template #header-actions>
|
||||
<KsButton
|
||||
label="New Model"
|
||||
severity="primary"
|
||||
@click="handleNewModel"
|
||||
/>
|
||||
</template>
|
||||
<div class="models-page">
|
||||
<header class="page-header">
|
||||
<h1>Model Management</h1>
|
||||
<p>Manage trading models across their complete lifecycle</p>
|
||||
</header>
|
||||
|
||||
<!-- Search Panel -->
|
||||
<template #search>
|
||||
<div class="models-search">
|
||||
<div class="search-row">
|
||||
<KsTextField
|
||||
v-model="searchQuery"
|
||||
label="Model search"
|
||||
placeholder="Search by model name..."
|
||||
@keydown.enter="handleSearch"
|
||||
/>
|
||||
<KsButton
|
||||
label="Search"
|
||||
severity="secondary"
|
||||
@click="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
<div class="search-row">
|
||||
<select v-model="phaseFilter" class="phase-filter">
|
||||
<option value="all">All Phases</option>
|
||||
<option value="freeze">Freeze</option>
|
||||
<option value="mature">Mature</option>
|
||||
<option value="score">Score</option>
|
||||
<option value="diagnose">Diagnose</option>
|
||||
<option value="hypothesis">Hypothesis</option>
|
||||
<option value="challenger">Challenger</option>
|
||||
<option value="validate">Validate</option>
|
||||
<option value="review">Review</option>
|
||||
</select>
|
||||
<select v-model="activeFilter" class="active-filter">
|
||||
<option value="all">All Status</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Loading State -->
|
||||
<div v-if="modelsQuery.isPending" class="loading-state">
|
||||
<SkeletonLoader type="table" :rows="5" />
|
||||
</div>
|
||||
|
||||
<!-- Content Area -->
|
||||
<template #content>
|
||||
<KsDataGrid
|
||||
v-if="screenDef.grid && modelsQuery.data.value?.items"
|
||||
:columns="modelsQuery.data.value?.items.length ? modelColumns : []"
|
||||
:rows="modelsQuery.data.value?.items || []"
|
||||
:loading="modelsQuery.isPending.value"
|
||||
@row-selected="handleRowSelected"
|
||||
/>
|
||||
</template>
|
||||
</KsListPage>
|
||||
<!-- Error State -->
|
||||
<div v-else-if="modelsQuery.isError" class="error-state">
|
||||
<p>Failed to load models</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="!items.length" class="empty-state">
|
||||
<p>No models found. Create a new model to get started.</p>
|
||||
</div>
|
||||
|
||||
<!-- Data State -->
|
||||
<div v-else class="models-grid">
|
||||
<table class="models-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model ID</th>
|
||||
<th>Name</th>
|
||||
<th>Phase</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="model in items" :key="model.id" data-testid="model-row">
|
||||
<td>{{ (model as any).id }}</td>
|
||||
<td>{{ (model as any).name }}</td>
|
||||
<td>{{ (model as any).phase }}</td>
|
||||
<td>{{ (model as any).active ? 'Active' : 'Inactive' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.models-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
.models-page {
|
||||
padding: 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.models-search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: var(--kbx-color-surface, #f5f5f5);
|
||||
border-radius: 4px;
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
.page-header h1 {
|
||||
margin: 0;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.search-row input,
|
||||
.search-row select {
|
||||
height: var(--kbx-input-height, 34px);
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #d0d0d0;
|
||||
border-radius: 4px;
|
||||
font-size: var(--kbx-font-size, 14px);
|
||||
.page-header p {
|
||||
margin: 0.5rem 0 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.phase-filter,
|
||||
.active-filter {
|
||||
flex: 0 0 140px;
|
||||
.loading-state,
|
||||
.error-state,
|
||||
.empty-state {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--border-radius-md);
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: var(--kbx-color-primary, #3b82f6);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
margin-left: 4px;
|
||||
.models-grid {
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: var(--border-radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.models-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.models-table thead {
|
||||
background-color: var(--color-background-secondary);
|
||||
}
|
||||
|
||||
.models-table th {
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.models-table td {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.models-table tbody tr:hover {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user