Files
DevFlow/src/components/ai/CIStatus.vue
T
lxy e68d283651 优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)
- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod

- AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation

- 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n

- 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
2026-08-05 22:15:34 +08:00

124 lines
3.2 KiB
Vue

<template>
<div class="ci-status" v-if="statuses.length > 0">
<div class="ci-header">
<span class="ci-title">{{ $t('aiChat.ciTitle') }}</span>
<span class="ci-summary" :class="summaryClass">{{ summaryText }}</span>
</div>
<div class="ci-list">
<a
v-for="check in statuses"
:key="check.name"
:href="check.url || undefined"
class="ci-item"
:class="'ci-item--' + check.status"
target="_blank"
rel="noopener"
>
<span class="ci-item-icon">{{ statusIcon(check.status) }}</span>
<span class="ci-item-name">{{ check.name }}</span>
</a>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { invoke } from '@tauri-apps/api/core'
const { t } = useI18n()
interface CheckStatus {
name: string
status: string
url: string | null
}
const props = defineProps<{
repoUrl: string
commitSha: string
}>()
const statuses = ref<CheckStatus[]>([])
const summaryClass = computed(() => {
const hasFailure = statuses.value.some(s => s.status === 'failure')
const hasPending = statuses.value.some(s => s.status === 'pending')
if (hasFailure) return 'ci-summary--fail'
if (hasPending) return 'ci-summary--pending'
return 'ci-summary--pass'
})
const summaryText = computed(() => {
const pass = statuses.value.filter(s => s.status === 'success').length
const fail = statuses.value.filter(s => s.status === 'failure').length
const pending = statuses.value.filter(s => s.status === 'pending').length
if (fail > 0) return t('aiChat.ciNFailed', { n: fail })
if (pending > 0) return t('aiChat.ciNPending', { n: pending })
return t('aiChat.ciNPassed', { n: pass })
})
function statusIcon(status: string): string {
switch (status) {
case 'success': return '✓'
case 'failure': return '✗'
case 'pending': return '⏳'
default: return '○'
}
}
onMounted(async () => {
try {
statuses.value = await invoke<CheckStatus[]>('get_commit_status', {
repoUrl: props.repoUrl,
commitSha: props.commitSha,
})
} catch {
statuses.value = []
}
})
</script>
<style scoped>
.ci-status {
border: 1px solid var(--df-border);
border-radius: 8px;
padding: 8px 12px;
margin: 8px 0;
background: var(--df-bg-secondary);
font-size: 12px;
}
.ci-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-weight: 600;
}
.ci-title { color: var(--df-text); }
.ci-summary--pass { color: var(--df-success); }
.ci-summary--fail { color: var(--df-danger); }
.ci-summary--pending { color: var(--df-warning); }
.ci-list {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.ci-item {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
background: var(--df-bg);
border: 1px solid var(--df-border);
text-decoration: none;
color: var(--df-text);
}
.ci-item--success { border-color: var(--df-success); }
.ci-item--failure { border-color: var(--df-danger); }
.ci-item--pending { border-color: var(--df-warning); }
.ci-item-icon { font-size: 10px; font-weight: 500; }
.ci-item:hover { opacity: 0.8; }
</style>