Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 51 additions & 19 deletions frontend/src/components/TrustCenterView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
<header class="trust-center-header">
<div>
<p class="eyebrow">Trust Operations</p>
<h1>信任运营中心</h1>
<p>把 AI 推断、证据路径、open loop 和审查历史放在一个可操作的工作台里。</p>
<h1>信任工作台</h1>
<p>先处理高风险判断,证据、open loop 和诊断按需展开。</p>
</div>
<button class="paper-button" type="button" :disabled="busy" @click="$emit('refresh')">刷新</button>
</header>

<section class="trust-summary-grid">
<article v-for="metric in metrics" :key="metric.label" :class="metric.tone">
<span>{{ metric.label }}</span>
<strong>{{ metric.value }}</strong>
<small>{{ metric.caption }}</small>
</article>
<section class="trust-focus-strip" aria-label="信任工作台焦点">
<div class="trust-focus-primary">
<span class="section-kicker">今日焦点</span>
<h2>{{ focusHeadline }}</h2>
<p>{{ focusCaption }}</p>
</div>
<div class="trust-focus-stats">
<article v-for="metric in compactMetrics" :key="metric.label" :class="metric.tone">
<span>{{ metric.label }}</span>
<strong>{{ metric.value }}</strong>
<small>{{ metric.caption }}</small>
</article>
</div>
</section>

<TrustBatchActionBar
Expand Down Expand Up @@ -88,29 +95,54 @@ defineEmits<{
updateOpenLoop: [loopId: string, payload: TrustOpenLoopUpdatePayload]
}>()

const metrics = computed(() => [
const urgentReviewCount = computed(() => props.review.summary.critical + props.review.summary.high)

const focusHeadline = computed(() => {
if (props.review.summary.critical) {
return `先确认 ${props.review.summary.critical} 条 critical 推断`
}
if (props.review.summary.high) {
return `先扫完 ${props.review.summary.high} 条高风险判断`
}
if (props.openLoops.summary.total) {
return `把 ${props.openLoops.summary.total} 个 open loop 变成下一步`
}
return '当前没有高压审查项'
})

const focusCaption = computed(() => {
if (urgentReviewCount.value) {
return '默认只展示最能帮助你做决定的信息;需要证据、历史和诊断时再展开。'
}
if (props.review.summary.unreviewed) {
return '剩余项目风险较低,可以按空间或关键词慢慢清理,不需要一次看完所有细节。'
}
return '信任边界已经比较干净,后续重点是继续沉淀用户确认过的理由。'
})

const compactMetrics = computed(() => [
{
label: 'critical',
value: props.review.summary.critical,
caption: '必须先判断的 AI 推断',
label: '需判断',
value: props.review.summary.unreviewed,
caption: `${urgentReviewCount.value} 条高优先级`,
tone: 'tone-critical',
},
{
label: 'high',
value: props.review.summary.high,
caption: '高风险证据或未审查项',
label: 'AI 推断',
value: props.review.summary.ai_inferred,
caption: `${props.review.summary.user_stated} 条用户原话`,
tone: 'tone-high',
},
{
label: 'open loops',
label: 'open loop',
value: props.openLoops.summary.total,
caption: '还没有闭环的问题',
caption: `${props.openLoops.summary.by_state.next || 0} 个下一步`,
tone: 'tone-loop',
},
{
label: 'history',
label: '已审查',
value: props.diagnostics?.history_count || 0,
caption: '已经写入的审查记录',
caption: '可追溯记录',
tone: 'tone-history',
},
])
Expand Down
34 changes: 29 additions & 5 deletions frontend/src/components/TrustDiagnosticsPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<strong>{{ diagnostics?.queue_total || 0 }}</strong>
</div>

<p class="trust-diagnostics-summary">{{ diagnosticsSummary }}</p>

<div class="trust-diagnostics-grid">
<div>
<span>AI 未审查</span>
Expand All @@ -27,17 +29,39 @@
</div>
</div>

<div class="trust-warning-list">
<p v-for="warning in diagnostics?.warnings || []" :key="warning">{{ warning }}</p>
<p v-if="!(diagnostics?.warnings || []).length" class="trust-empty">没有诊断警告。</p>
</div>
<details class="trust-detail-disclosure">
<summary>
<span>诊断警告</span>
<strong>{{ diagnostics?.warnings.length || 0 }} 条</strong>
</summary>
<div class="trust-warning-list">
<p v-for="warning in diagnostics?.warnings || []" :key="warning">{{ warning }}</p>
<p v-if="!(diagnostics?.warnings || []).length" class="trust-empty">没有诊断警告。</p>
</div>
</details>
</section>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { TrustDiagnostics } from './trustCenterTypes'

defineProps<{
const props = defineProps<{
diagnostics: TrustDiagnostics | null
}>()

const diagnosticsSummary = computed(() => {
const diagnostics = props.diagnostics
if (!diagnostics) return '还没有诊断数据。'
if (diagnostics.critical_count) {
return `${diagnostics.critical_count} 条 critical 项需要先处理,其余诊断已收起。`
}
if (diagnostics.ai_inferred_unreviewed) {
return `${diagnostics.ai_inferred_unreviewed} 条 AI 推断还没被用户确认。`
}
if (diagnostics.warnings.length) {
return '有少量诊断警告,展开后再逐条检查。'
}
return '当前没有诊断警告,信任边界状态稳定。'
})
</script>
11 changes: 8 additions & 3 deletions frontend/src/components/TrustOpenLoopPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</div>

<div class="trust-open-loop-list">
<article v-for="loop in payload.items" :key="loop.loop_id" class="trust-open-loop-card">
<article v-for="loop in visibleLoops" :key="loop.loop_id" class="trust-open-loop-card">
<div>
<span>{{ loop.state }}</span>
<strong>{{ loop.text }}</strong>
Expand All @@ -29,16 +29,19 @@
<button class="ghost-button" type="button" :disabled="busy" @click="setState(loop.loop_id, 'dismissed')">dismissed</button>
</div>
</article>
<p v-if="overflowCount" class="trust-loop-overflow-note">
还有 {{ overflowCount }} 个 open loop 已收起,先处理上面这些更高价值的问题。
</p>
<p v-if="!payload.items.length" class="trust-empty">当前没有 open-loop。</p>
</div>
</section>
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import { computed, reactive } from 'vue'
import type { TrustOpenLoopPayload, TrustOpenLoopState, TrustOpenLoopUpdatePayload } from './trustCenterTypes'

defineProps<{
const props = defineProps<{
payload: TrustOpenLoopPayload
busy: boolean
}>()
Expand All @@ -48,6 +51,8 @@ const emit = defineEmits<{
}>()

const notes = reactive<Record<string, string>>({})
const visibleLoops = computed(() => props.payload.items.slice(0, 4))
const overflowCount = computed(() => Math.max(props.payload.items.length - visibleLoops.value.length, 0))

function setState(loopId: string, state: TrustOpenLoopState) {
emit('updateLoop', loopId, {
Expand Down
90 changes: 61 additions & 29 deletions frontend/src/components/TrustReviewDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,61 @@
<button class="ghost-button" type="button" @click="$emit('close')">关闭</button>
</div>

<section class="trust-detail-section">
<span>当前保存理由</span>
<section class="trust-detail-brief">
<div>
<span>{{ boundaryLabel(detail.item.why_saved_status) }}</span>
<strong>{{ detailRiskCopy }}</strong>
</div>
<p>{{ detail.item.why_saved || '没有稳定保存理由。' }}</p>
<div class="trust-review-meta">
<span>{{ detail.item.why_saved_status }}</span>
<span>{{ detail.item.review_status }}</span>
<span>{{ detail.item.risk_level }}</span>
<span>{{ detail.item.review_status }}</span>
<span>可信度 {{ Math.round(detail.item.confidence * 100) }}%</span>
</div>
</section>

<section class="trust-detail-section">
<span>证据路径</span>
<div v-if="evidencePaths.length" class="trust-path-list">
<p v-for="path in evidencePaths" :key="path.edge_id">{{ path.path }}</p>
</div>
<p v-else class="trust-empty">这条材料还没有可展示的图谱证据路径。</p>
</section>
<details class="trust-detail-disclosure">
<summary>
<span>证据路径</span>
<strong>{{ evidencePaths.length }} 条</strong>
</summary>
<section class="trust-detail-section">
<div v-if="evidencePaths.length" class="trust-path-list">
<p v-for="path in evidencePaths" :key="path.edge_id">{{ path.path }}</p>
</div>
<p v-else class="trust-empty">这条材料还没有可展示的图谱证据路径。</p>
</section>
</details>

<section class="trust-detail-section">
<span>Open loops</span>
<p v-for="loop in detail.open_loops" :key="loop.loop_id" class="trust-loop-line">
{{ loop.text }} · {{ loop.state }}
</p>
<p v-if="!detail.open_loops.length" class="trust-empty">没有关联 open loop。</p>
</section>
<details class="trust-detail-disclosure">
<summary>
<span>Open loops</span>
<strong>{{ detail.open_loops.length }} 个</strong>
</summary>
<section class="trust-detail-section">
<p v-for="loop in detail.open_loops" :key="loop.loop_id" class="trust-loop-line">
{{ loop.text }} · {{ loop.state }}
</p>
<p v-if="!detail.open_loops.length" class="trust-empty">没有关联 open loop。</p>
</section>
</details>

<section class="trust-detail-section">
<span>审查历史</span>
<div v-if="history.length" class="trust-history-list">
<article v-for="item in history" :key="item.id">
<strong>{{ item.action }}</strong>
<small>{{ item.created_at }}</small>
<p>{{ item.note || '没有备注。' }}</p>
</article>
</div>
<p v-else class="trust-empty">还没有审查历史。</p>
</section>
<details class="trust-detail-disclosure">
<summary>
<span>审查历史</span>
<strong>{{ history.length }} 条</strong>
</summary>
<section class="trust-detail-section">
<div v-if="history.length" class="trust-history-list">
<article v-for="item in history" :key="item.id">
<strong>{{ item.action }}</strong>
<small>{{ item.created_at }}</small>
<p>{{ item.note || '没有备注。' }}</p>
</article>
</div>
<p v-else class="trust-empty">还没有审查历史。</p>
</section>
</details>

<section class="trust-detail-section trust-detail-actions">
<span>单项动作</span>
Expand Down Expand Up @@ -81,6 +99,14 @@ const note = ref('')
const rewriteText = ref('')
const evidencePaths = computed(() => props.detail?.evidence_paths || [])
const history = computed(() => props.detail?.history || [])
const detailRiskCopy = computed(() => {
const item = props.detail?.item
if (!item) return ''
if (item.risk_level === 'critical') return '这条会直接影响回答可信度'
if (item.risk_level === 'high') return '建议先看证据再确认'
if (item.has_open_loops) return '它还连着未闭环问题'
return '低压力,可以批量处理'
})

watch(
() => props.detail?.item.source_id,
Expand Down Expand Up @@ -110,4 +136,10 @@ function submitRewrite() {
},
})
}

function boundaryLabel(status: string) {
if (status === 'user-stated') return '用户原话'
if (status === 'AI-inferred') return 'AI 推断'
return status || '边界未知'
}
</script>
35 changes: 28 additions & 7 deletions frontend/src/components/TrustReviewInbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="trust-panel-head">
<div>
<span class="section-kicker">Review Inbox</span>
<h3>AI 推断审查队列</h3>
<h3>待判断</h3>
</div>
<strong>{{ items.length }} 条</strong>
</div>
Expand Down Expand Up @@ -48,13 +48,16 @@
</label>
<button class="trust-review-main" type="button" @click="$emit('selectReview', item.source_id)">
<strong>{{ item.title }}</strong>
<p>{{ item.why_saved || item.summary || '这条材料还没有稳定的保存理由。' }}</p>
<p>{{ compactReason(item) }}</p>
</button>
<div class="trust-review-meta">
<span>{{ item.why_saved_status }}</span>
<span>{{ item.space_name }}</span>
<span>{{ Math.round(item.confidence * 100) }}%</span>
<span v-if="item.open_loops.length">{{ item.open_loops.length }} open loop</span>
<div class="trust-review-action-hint">
{{ actionHint(item) }}
</div>
<div class="trust-review-compact-meta">
<span>{{ boundaryLabel(item.why_saved_status) }}</span>
<span>{{ item.space_name || '未分配空间' }}</span>
<span>可信度 {{ Math.round(item.confidence * 100) }}%</span>
<span v-if="item.open_loops.length">{{ item.open_loops.length }} 个 open loop</span>
</div>
</article>
<p v-if="!items.length" class="trust-empty">当前筛选下没有待审查项。</p>
Expand Down Expand Up @@ -108,4 +111,22 @@ function riskLabel(level: TrustRiskLevel) {
if (level === 'medium') return 'medium'
return 'low'
}

function compactReason(item: TrustReviewItem) {
const text = item.why_saved || item.summary || '这条材料还没有稳定的保存理由。'
return text.length > 108 ? `${text.slice(0, 108)}...` : text
}

function boundaryLabel(status: string) {
if (status === 'user-stated') return '用户原话'
if (status === 'AI-inferred') return 'AI 推断'
return status || '边界未知'
}

function actionHint(item: TrustReviewItem) {
if (item.risk_level === 'critical') return '建议现在处理:确认、改写或拒绝这条推断。'
if (item.risk_level === 'high') return '建议优先扫一眼证据,再决定是否确认。'
if (item.open_loops.length) return '可以转成下一步,避免问题继续悬空。'
return item.recommended_action || '低压力项目,可以稍后批量处理。'
}
</script>
Loading
Loading