对齐 Parent Group 前端展示与复核
This commit is contained in:
1 parent
92489af18e
commit
bf796946f9
13 files changed
+575
-13
No files matched your search
@@ -88,7 +88,7 @@
|
||||
<div>
|
||||
<dt>{{ t('task.taskCardType') }}</dt>
|
||||
<dd>
|
||||
{{ formatReservationTaskCard(t, detail.task_card_type) }}
|
||||
{{ formatReservationTaskCardLabel(t, detail) }}
|
||||
<small class="th-code">{{ detail.task_card_type }}</small>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -191,6 +191,79 @@
|
||||
<pre>{{ formatJson(manualReviewRecord) }}</pre>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="manualReviewEvidence"
|
||||
class="manual-review-evidence"
|
||||
>
|
||||
<header>
|
||||
<h2>{{ t('task.manualReviewEvidence') }}</h2>
|
||||
<p>{{ manualReviewEvidence.visibleReason || t('task.manualReviewEvidenceFallback') }}</p>
|
||||
</header>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{{ t('task.manualResolution.reasonCode') }}</dt>
|
||||
<dd class="th-code">
|
||||
{{ manualReviewEvidence.reasonCode || '-' }}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.manualReviewMissingFields') }}</dt>
|
||||
<dd>{{ formatStringList(manualReviewEvidence.missingFields) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.manualReviewBlockingPoints') }}</dt>
|
||||
<dd>{{ formatStringList(manualReviewEvidence.blockingPoints) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.manualReviewConflictingPoints') }}</dt>
|
||||
<dd>{{ formatStringList(manualReviewEvidence.conflictingPoints) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.manualReviewSuggestedActions') }}</dt>
|
||||
<dd>{{ formatStringList(manualReviewEvidence.suggestedHumanActions) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{{ t('task.manualReviewEvidenceToCheck') }}</dt>
|
||||
<dd>{{ formatStringList(manualReviewEvidence.evidenceToCheck) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="manual-review-evidence__candidates">
|
||||
<h3>{{ t('task.parentIdentityCandidates') }}</h3>
|
||||
<div
|
||||
v-if="manualReviewEvidence.parentIdentityCandidates.length"
|
||||
class="candidate-grid"
|
||||
>
|
||||
<article
|
||||
v-for="(candidate, index) in manualReviewEvidence.parentIdentityCandidates"
|
||||
:key="`${candidate.title}-${candidate.summary}-${index}`"
|
||||
class="candidate-item"
|
||||
>
|
||||
<strong>{{ candidate.title || t('task.parentIdentityCandidate') }}</strong>
|
||||
<span class="th-code">{{ candidate.summary || '-' }}</span>
|
||||
<small>{{ candidate.sourceSummary || '-' }}</small>
|
||||
<dl
|
||||
v-if="candidate.attributes.length"
|
||||
class="candidate-item__attributes"
|
||||
>
|
||||
<div
|
||||
v-for="attribute in candidate.attributes"
|
||||
:key="attribute.key"
|
||||
>
|
||||
<dt>{{ attribute.key }}</dt>
|
||||
<dd>{{ attribute.value }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
</div>
|
||||
<p
|
||||
v-else
|
||||
class="manual-review-evidence__empty"
|
||||
>
|
||||
{{ t('task.noParentIdentityCandidates') }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="adapterContractErrors.length"
|
||||
class="task-ai-diagnostics task-ai-diagnostics--error"
|
||||
@@ -366,6 +439,7 @@ import {
|
||||
formatReservationMaybeStableReason,
|
||||
formatReservationSystemProcessCategory,
|
||||
formatReservationTaskCard,
|
||||
formatReservationTaskCardLabel,
|
||||
formatReservationTaskRouteSummary,
|
||||
isReservationFallbackManualReviewTask,
|
||||
isReservationReadonlyDiagnosticTask,
|
||||
@@ -404,6 +478,7 @@ const isPendingTypeKnownManualReviewDetail = computed(
|
||||
const isFallbackManualReviewDetail = computed(() => isReservationFallbackManualReviewTask(detail.value))
|
||||
const sourceMessageOnlyResult = computed(() => detail.value?.source_message_only_result ?? null)
|
||||
const manualReviewRecord = computed(() => detail.value?.manual_review ?? sourceMessageOnlyResult.value?.manual_review ?? null)
|
||||
const manualReviewEvidence = computed(() => buildManualReviewEvidence(detail.value, manualReviewRecord.value))
|
||||
const adapterContractErrors = computed(() => detail.value?.adapter_contract_errors ?? [])
|
||||
const unhandledIntents = computed(() => detail.value?.unhandled_intents ?? [])
|
||||
const isFieldRendererReadOnly = computed(
|
||||
@@ -659,6 +734,123 @@ function replaceOperation(operation: ReservationOperaOperationResult): void {
|
||||
function formatJson(value: unknown): string {
|
||||
return JSON.stringify(value ?? {}, null, 2)
|
||||
}
|
||||
|
||||
interface ManualReviewEvidenceCandidate {
|
||||
title: string | null
|
||||
summary: string | null
|
||||
sourceSummary: string | null
|
||||
attributes: Array<{
|
||||
key: string
|
||||
value: string
|
||||
}>
|
||||
}
|
||||
|
||||
interface ManualReviewEvidence {
|
||||
reasonCode: string | null
|
||||
visibleReason: string | null
|
||||
missingFields: string[]
|
||||
blockingPoints: string[]
|
||||
conflictingPoints: string[]
|
||||
suggestedHumanActions: string[]
|
||||
evidenceToCheck: string[]
|
||||
parentIdentityCandidates: ManualReviewEvidenceCandidate[]
|
||||
}
|
||||
|
||||
function buildManualReviewEvidence(
|
||||
taskDetail: ReservationTaskDetailResult | null,
|
||||
manualReview: ReservationRecord | null | undefined,
|
||||
): ManualReviewEvidence | null {
|
||||
if (!manualReview || normalizeStableCode(readString(manualReview.reason_code)) !== 'TARGET_OBJECT_UNCLEAR') {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
reasonCode: readString(manualReview.reason_code),
|
||||
visibleReason: readString(manualReview.visible_reason),
|
||||
missingFields: readStringArray(manualReview.missing_fields),
|
||||
blockingPoints: readStringArray(manualReview.blocking_points),
|
||||
conflictingPoints: readStringArray(manualReview.conflicting_points),
|
||||
suggestedHumanActions: readStringArray(manualReview.suggested_human_actions),
|
||||
evidenceToCheck: readStringArray(manualReview.evidence_to_check),
|
||||
parentIdentityCandidates: readParentIdentityCandidates(taskDetail, manualReview),
|
||||
}
|
||||
}
|
||||
|
||||
function readParentIdentityCandidates(
|
||||
taskDetail: ReservationTaskDetailResult | null,
|
||||
manualReview: ReservationRecord,
|
||||
): ManualReviewEvidenceCandidate[] {
|
||||
const contextCandidates = [
|
||||
readRecord(taskDetail?.context_used)?.parent_identity_candidates,
|
||||
readRecord(manualReview.context_used)?.parent_identity_candidates,
|
||||
manualReview.parent_identity_candidates,
|
||||
]
|
||||
const candidateList = contextCandidates.find((item) => Array.isArray(item))
|
||||
if (!Array.isArray(candidateList)) {
|
||||
return []
|
||||
}
|
||||
return candidateList
|
||||
.map((item) => readRecord(item))
|
||||
.filter((item): item is ReservationRecord => item !== null)
|
||||
.map((item) => {
|
||||
const attributes = Object.entries(item)
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
value: stringifyCandidateValue(value),
|
||||
}))
|
||||
.filter((attribute) => attribute.value !== '')
|
||||
return {
|
||||
title: readFirstString(item, ['field', 'candidate_field', 'key', 'field_path']),
|
||||
summary: readFirstString(item, ['value', 'raw_value', 'normalized_value', 'candidate_value']),
|
||||
sourceSummary: readFirstString(item, ['evidence_source', 'source', 'evidence', 'origin']),
|
||||
attributes,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function readRecord(value: unknown): ReservationRecord | null {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return null
|
||||
}
|
||||
return value as ReservationRecord
|
||||
}
|
||||
|
||||
function readString(value: unknown): string | null {
|
||||
return typeof value === 'string' && value.trim() ? value : null
|
||||
}
|
||||
|
||||
function readFirstString(record: ReservationRecord, keys: string[]): string | null {
|
||||
for (const key of keys) {
|
||||
const value = readString(record[key])
|
||||
if (value) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function readStringArray(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return []
|
||||
}
|
||||
return value.filter((item): item is string => typeof item === 'string' && item.trim() !== '')
|
||||
}
|
||||
|
||||
function stringifyCandidateValue(value: unknown): string {
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value)
|
||||
}
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
|
||||
function formatStringList(items: string[]): string {
|
||||
return items.length ? items.join(' / ') : '-'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -751,6 +943,15 @@ function formatJson(value: unknown): string {
|
||||
border-color: var(--th-color-danger);
|
||||
}
|
||||
|
||||
.manual-review-evidence {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
border: 1px solid var(--th-color-warning);
|
||||
border-radius: var(--th-radius-md);
|
||||
background: color-mix(in srgb, var(--th-color-warning) 8%, var(--th-color-white));
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.source-only-result {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
@@ -766,6 +967,10 @@ function formatJson(value: unknown): string {
|
||||
.task-route-info dl,
|
||||
.task-ai-diagnostics h2,
|
||||
.task-ai-diagnostics pre,
|
||||
.manual-review-evidence h2,
|
||||
.manual-review-evidence h3,
|
||||
.manual-review-evidence p,
|
||||
.manual-review-evidence dl,
|
||||
.source-only-result h2,
|
||||
.source-only-result p,
|
||||
.source-only-result dl {
|
||||
@@ -775,11 +980,24 @@ function formatJson(value: unknown): string {
|
||||
.task-evidence h2,
|
||||
.task-route-info h2,
|
||||
.task-ai-diagnostics h2,
|
||||
.manual-review-evidence h2,
|
||||
.source-only-result h2 {
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.manual-review-evidence header {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.manual-review-evidence header p,
|
||||
.manual-review-evidence__empty {
|
||||
color: var(--th-color-slate-600);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.source-only-result header {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -793,7 +1011,8 @@ function formatJson(value: unknown): string {
|
||||
}
|
||||
|
||||
.task-evidence dl,
|
||||
.task-route-info dl {
|
||||
.task-route-info dl,
|
||||
.manual-review-evidence > dl {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
@@ -806,7 +1025,8 @@ function formatJson(value: unknown): string {
|
||||
}
|
||||
|
||||
.task-evidence dt,
|
||||
.task-route-info dt {
|
||||
.task-route-info dt,
|
||||
.manual-review-evidence > dl dt {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
@@ -814,6 +1034,7 @@ function formatJson(value: unknown): string {
|
||||
|
||||
.task-evidence dd,
|
||||
.task-route-info dd,
|
||||
.manual-review-evidence > dl dd,
|
||||
.source-only-result dd {
|
||||
margin: 5px 0 0;
|
||||
color: var(--th-color-slate-900);
|
||||
@@ -821,6 +1042,65 @@ function formatJson(value: unknown): string {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.manual-review-evidence__candidates {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.manual-review-evidence__candidates h3 {
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.candidate-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.candidate-item {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
border: 1px solid var(--th-color-slate-200);
|
||||
border-radius: var(--th-radius-sm);
|
||||
background: var(--th-color-white);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.candidate-item strong {
|
||||
color: var(--th-color-slate-900);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.candidate-item small {
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.candidate-item__attributes {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
.candidate-item__attributes div {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(90px, 0.35fr) minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.candidate-item__attributes dt,
|
||||
.candidate-item__attributes dd {
|
||||
margin: 0;
|
||||
color: var(--th-color-slate-500);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.candidate-item__attributes dd {
|
||||
color: var(--th-color-slate-700);
|
||||
}
|
||||
|
||||
.source-only-result__raw {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
@@ -934,7 +1214,9 @@ function formatJson(value: unknown): string {
|
||||
}
|
||||
|
||||
.task-evidence dl,
|
||||
.task-route-info dl {
|
||||
.task-route-info dl,
|
||||
.manual-review-evidence > dl,
|
||||
.candidate-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@@ -942,6 +1224,8 @@ function formatJson(value: unknown): string {
|
||||
@media (max-width: 640px) {
|
||||
.task-evidence dl,
|
||||
.task-route-info dl,
|
||||
.manual-review-evidence > dl,
|
||||
.candidate-grid,
|
||||
.source-only-result dl {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
>
|
||||
<span class="task-row__index">{{ task.queue_sequence ?? '-' }}</span>
|
||||
<span class="task-row__main">
|
||||
<strong>{{ formatReservationTaskCard(t, task.task_type, task.card_name) }}</strong>
|
||||
<strong>{{ formatReservationTaskCardLabel(t, task) }}</strong>
|
||||
<small>{{ formatReservationTaskRouteSummary(t, task) }}</small>
|
||||
<em :class="{ 'task-row__reason--blocked': !task.can_process }">
|
||||
{{ task.can_process ? t('taskList.canProcess') : t('taskList.cannotProcess') }}
|
||||
@@ -51,7 +51,7 @@ import ReservationStatusBadge from '@/components/reservation/ReservationStatusBa
|
||||
import type { ReservationOrderTaskTimelineItem } from '@/types/reservation'
|
||||
import {
|
||||
formatReservationReadonlyReason,
|
||||
formatReservationTaskCard,
|
||||
formatReservationTaskCardLabel,
|
||||
formatReservationTaskRouteSummary,
|
||||
} from '@/utils/reservationDisplay'
|
||||
|
||||
|
||||
Reference in new issue
Block a user