project-analyzer: analisis completo con contexto global + dedup
- services/project-analyzer.ts: analiza sesiones + resumenes + HUs existentes - generateMissingHUs: proposicion inteligente sin duplicados - createMissingHUs: crea en KAPPA solo las que no existen (comparacion por titulo) - AiProjectChat: sin limite de HUs en contexto JSON compacto - DashboardView: card 'Generar HUs faltantes' con resultado - ai.ts: stripThinkTags() para eliminar bloques de razonamiento
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, watch } from 'vue'
|
||||
import { computed, watch, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useProjectsStore } from '@/stores/projects'
|
||||
import { useWorkItemsStore } from '@/stores/workitems'
|
||||
import { getTypeLabel, getTypeColor, getTypeIcon } from '@/services/hierarchy'
|
||||
import { Activity, FileText, Layers, Clock, Info, AlertTriangle, Plus, Brain } from 'lucide-vue-next'
|
||||
import { Activity, FileText, Layers, Clock, Info, AlertTriangle, Plus, Brain, Sparkles, Loader2, CheckCircle2, XCircle } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import HuDrafts from '@/components/HuDrafts.vue'
|
||||
import AiProjectChat from '@/components/AiProjectChat.vue'
|
||||
import { analyzeProject, createMissingHUs } from '@/services/project-analyzer'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
@@ -27,6 +29,35 @@ const emit = defineEmits<{
|
||||
'navigate-settings': []
|
||||
}>()
|
||||
|
||||
// ─── Project analysis ────────────────────────────────────
|
||||
const analyzing = ref(false)
|
||||
const analysisResult = ref<{ created: number; skipped: number; errors: string[] } | null>(null)
|
||||
const analysisSummary = ref('')
|
||||
|
||||
async function runAnalysis() {
|
||||
if (!project.value) return
|
||||
analyzing.value = true
|
||||
analysisResult.value = null
|
||||
analysisSummary.value = ''
|
||||
|
||||
try {
|
||||
const result = await analyzeProject(project.value.id, project.value.name || '', workItems.userStories)
|
||||
analysisSummary.value = result.summary
|
||||
|
||||
if (result.hus.length > 0) {
|
||||
const outcome = await createMissingHUs(project.value.id, result, workItems.userStories)
|
||||
analysisResult.value = outcome
|
||||
await workItems.fetchWorkItems(project.value.id)
|
||||
} else {
|
||||
analysisResult.value = { created: 0, skipped: 0, errors: [] }
|
||||
}
|
||||
} catch (e: any) {
|
||||
analysisResult.value = { created: 0, skipped: 0, errors: [e.message] }
|
||||
} finally {
|
||||
analyzing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const project = computed(() => projects.selected)
|
||||
|
||||
watch(
|
||||
@@ -120,6 +151,7 @@ const statusLabel = (status: unknown) => {
|
||||
|
||||
<!-- AI Chat -->
|
||||
<AiProjectChat
|
||||
:project-id="project.id"
|
||||
:project-name="project.name ?? ''"
|
||||
:project-description="project.description ?? ''"
|
||||
:epic-count="workItems.totalEpics"
|
||||
@@ -127,6 +159,42 @@ const statusLabel = (status: unknown) => {
|
||||
@navigate-settings="emit('navigate-settings')"
|
||||
/>
|
||||
|
||||
<!-- Project Analysis -->
|
||||
<Card id="dashboard-analysis" class="border-dashed">
|
||||
<CardHeader class="pb-3 flex flex-row items-center justify-between">
|
||||
<CardTitle class="text-sm font-medium flex items-center gap-2">
|
||||
<Sparkles class="size-4" />
|
||||
Análisis completo del proyecto
|
||||
</CardTitle>
|
||||
<Button
|
||||
size="sm"
|
||||
:disabled="analyzing"
|
||||
@click="runAnalysis()"
|
||||
>
|
||||
<Loader2 v-if="analyzing" class="size-4 mr-1 animate-spin" />
|
||||
<Sparkles v-else class="size-4 mr-1" />
|
||||
{{ analyzing ? 'Analizando...' : 'Generar HUs faltantes' }}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent v-if="analysisResult" class="space-y-2 text-sm">
|
||||
<p class="text-muted-foreground">{{ analysisSummary }}</p>
|
||||
<div class="flex items-center gap-3 text-xs">
|
||||
<span v-if="analysisResult.created > 0" class="text-green-600 dark:text-green-400 flex items-center gap-1">
|
||||
<CheckCircle2 class="size-3" /> {{ analysisResult.created }} HUs creadas
|
||||
</span>
|
||||
<span v-if="analysisResult.skipped > 0" class="text-amber-600 dark:text-amber-400 flex items-center gap-1">
|
||||
{{ analysisResult.skipped }} duplicadas saltadas
|
||||
</span>
|
||||
<span v-if="analysisResult.created === 0 && analysisResult.skipped === 0 && analysisResult.errors.length === 0" class="text-muted-foreground">
|
||||
Todo ya está cubierto. No se requieren nuevas HUs.
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="analysisResult.errors.length > 0" class="text-xs text-destructive space-y-0.5">
|
||||
<p v-for="e in analysisResult.errors" :key="e">⚠ {{ e }}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- Loading -->
|
||||
<template v-if="workItems.loading">
|
||||
<Skeleton class="h-8 w-1/3" />
|
||||
|
||||
Reference in New Issue
Block a user