licanglong 1 сар өмнө
parent
commit
5902151a38

+ 3 - 0
components.d.ts

@@ -14,11 +14,14 @@ declare module 'vue' {
     ElBadge: typeof import('element-plus/es')['ElBadge']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
+    ElCollapse: typeof import('element-plus/es')['ElCollapse']
+    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']

+ 4 - 2
src/api/invoice.ts

@@ -1,5 +1,7 @@
-export const searchInvoicesAPI = (params: any) => {
-  return fetch("/api/invoice/list/by_params", {
+import type { SystemPage } from "@/types";
+
+export const searchInvoicesAPI = (params: any, page: SystemPage) => {
+  return fetch(`/api/invoice/list/by_params?pageNumber=${page.pageNumber}&pageSize=${page.pageSize}`, {
     method: "POST",
     headers: {
       "Content-Type": "application/json",

+ 17 - 0
src/api/risk.ts

@@ -29,3 +29,20 @@ export const evidenceSearch = (params: any) => {
     return res.json();
   });
 };
+
+
+export const similarIdentificationApi = (params: any) => {
+  return fetch("/api/risk/similar", {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+      Accept: "application/json",
+    },
+    body: JSON.stringify(params),
+  }).then((res) => {
+    if (!res.ok) {
+      throw new Error(`HTTP error! status: ${res.status}`);
+    }
+    return res.json();
+  });
+};

+ 113 - 12
src/components/InvoiceRiskAgent.vue

@@ -23,7 +23,7 @@
     <el-card class="mb-8">
       <h3>发票列表</h3>
 
-      <el-table :data="invoiceList" @selection-change="onInvoiceSelect" style="width: 100%">
+      <el-table :data="invoiceList" ref="tableRef" @selection-change="onInvoiceSelect" style="width: 100%">
         <el-table-column type="selection" width="50" />
         <el-table-column prop="zzfphm" label="发票号" />
         <el-table-column prop="gmfmc" label="销售方" />
@@ -31,6 +31,18 @@
         <el-table-column prop="hjje" label="金额" />
         <el-table-column prop="kprq" label="开票日期" :formatter="elTableDateFormatter" />
       </el-table>
+      <div class="p-5 flex justify-end">
+        <el-pagination
+          v-model:current-page="invoicePage.pageNumber"
+          v-model:page-size="invoicePage.pageSize"
+          :page-sizes="[10, 20]"
+          :page-count="5"
+          prev-text="上一页"
+          next-text="下一页"
+          layout="sizes, prev, pager, next"
+          :total="1000"
+          @change="handlePageChange" />
+      </div>
 
       <el-button
         type="success"
@@ -38,13 +50,22 @@
         :disabled="selectedInvoices.length === 0"
         @click="doAnalyzeInvoices"
         :loading="agentDecideLoading">
-        提交智能体分析
+        个人发票智能体分析
+      </el-button>
+
+      <el-button
+        type="success"
+        style="margin-top: 12px"
+        :disabled="selectedInvoices.length === 0"
+        @click="doSimilarIdentification"
+        :loading="agentSimilarLoading">
+        同类发票智能体分析
       </el-button>
     </el-card>
 
     <!-- 三、智能体分析结果 -->
     <el-card v-if="agentResult" class="mb-8">
-      <h3>智能体分析结果</h3>
+      <h3>智能体个人发票分析结果</h3>
 
       <el-tag :type="decisionTag">
         {{ agentResult.decision }}
@@ -72,10 +93,37 @@
       </div>
     </el-card>
 
+    <el-card class="analysis-result-card" v-if="similarAgentResult">
+      <!-- 分析结果 -->
+      <h3>智能体同类识别结果</h3>
+
+      <el-tag :type="similarDecisionTag">
+        {{ similarAgentResult.decision }}
+      </el-tag>
+
+      <el-tag style="margin-left: 8px">置信度:{{ similarAgentResult.confidence }}</el-tag>
+
+      <div class="mt-8">
+        {{ similarAgentResult.summary }}
+      </div>
+
+      <el-divider v-if="hasSimilarEvidence">证据链(引用)</el-divider>
+
+      <!-- 证据链 -->
+      <el-timeline v-if="hasSimilarEvidence">
+        <el-timeline-item v-for="evidence in similarAgentResult.evidence_chain" :key="Math.random()">
+          <strong>信息引用</strong>
+          <p>{{ evidence.summary }}</p>
+          <el-tag style="margin-top: 4px">置信度:{{ evidence.confidence }}</el-tag>
+          <el-tag style="margin-top: 4px; margin-left: 4px">引用来源:{{ evidence.source }}</el-tag>
+        </el-timeline-item>
+      </el-timeline>
+    </el-card>
+
     <el-card v-if="allEvidenceItems.length > 0" class="mb-8 danger-block" v-loading="approveLoading">
       <h3>风控知识人工审核</h3>
 
-      <el-tabs v-model="activeTab">
+      <el-tabs v-model="activeKnowledgeTab">
         <el-tab-pane name="RULE">
           <template #label>
             <el-badge :value="tabCounts.RULE" :hidden="tabCounts.RULE === 0">
@@ -141,9 +189,10 @@
 
 <script setup lang="ts">
 import { searchInvoicesAPI } from "@/api/invoice";
-import { analyzeInvoices, evidenceSearch } from "@/api/risk";
+import { analyzeInvoices, evidenceSearch, similarIdentificationApi } from "@/api/risk";
 import { storeCases, storeIndustries, storeRules, storeSignals } from "@/api/vectorStore";
-import type { AgentResult, Invoice, ReviewItem } from "@/types/agent";
+import type { AgentResult, Invoice, ReviewItem, SimilarIdentificationResult } from "@/types/agent";
+import type { SystemPage } from "@/types/index";
 import { formatDateTime } from "@/utils/dateutils";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { computed, reactive, ref } from "vue";
@@ -156,18 +205,27 @@ const query = reactive({
 
 const invoiceList = ref<Invoice[]>([]);
 const selectedInvoices = ref<Invoice[]>([]);
-
+const invoicePage = reactive<SystemPage>({
+  pageNumber: 1,
+  pageSize: 10,
+});
+const tableRef = ref<any>(null);
 /* ---------------- 智能体结果 ---------------- */
 const agentDecideLoading = ref(false);
 const evidenceReplenishLoading = ref(false);
 const approveLoading = ref(false);
+const agentSimilarLoading = ref(false);
 
 const agentResult = ref<AgentResult | null>(null);
 const agentEvidenceResult = ref<any | null>(null);
 
+const similarAgentResult = ref<SimilarIdentificationResult | null>(null);
 /* ---------------- 计算属性 ---------------- */
 
 const hasEvidence = computed(() => agentResult.value && agentResult.value.completion.evidence_chain.length > 0);
+const hasSimilarEvidence = computed(
+  () => similarAgentResult.value && similarAgentResult.value.evidence_chain.length > 0,
+);
 
 // const needManualReview = computed(() => agentResult.value?.decision === "UNCERTAIN");
 
@@ -178,13 +236,19 @@ const decisionTag = computed(() => {
   return "success";
 });
 
+const similarDecisionTag = computed(() => {
+  if (!similarAgentResult.value) return "info";
+  if (similarAgentResult.value.decision === "NOT_BELONG") return "danger";
+  return "success";
+});
+
 /* ---------------- 方法 ---------------- */
 const elTableDateFormatter = (_: any, __: any, cellValue: string) => {
   return formatDateTime(cellValue);
 };
 
 const searchInvoices = async () => {
-  searchInvoicesAPI(query)
+  searchInvoicesAPI(query, invoicePage)
     .then((data) => {
       invoiceList.value = data.data;
       ElMessage.success("发票查询成功");
@@ -193,15 +257,33 @@ const searchInvoices = async () => {
       throw new Error(`Fetch error: ${error.message}`);
     });
 };
+
+const handlePageChange = async () => {
+  await searchInvoices();
+};
+
 searchInvoices();
 
 const onInvoiceSelect = (rows: Invoice[]) => {
-  selectedInvoices.value = rows;
-};
+  if (rows.length > 1) {
+    ElMessage.warning("仅支持单选发票进行分析,已自动保留第一条记录")
+
+    const firstRow = rows[0] as Invoice
+
+    tableRef.value?.clearSelection()
+    tableRef.value?.toggleRowSelection(firstRow, true)
+
+    selectedInvoices.value = [firstRow]
+    return
+  }
+
+  selectedInvoices.value = rows ?? null
+}
 
 const doAnalyzeInvoices = async () => {
   agentEvidenceResult.value = null;
   agentResult.value = null;
+  similarAgentResult.value = null;
   allEvidenceItems.value = [];
   agentDecideLoading.value = true;
   analyzeInvoices(selectedInvoices.value[0])
@@ -217,6 +299,25 @@ const doAnalyzeInvoices = async () => {
     });
 };
 
+const doSimilarIdentification = async () => {
+  agentEvidenceResult.value = null;
+  agentResult.value = null;
+  similarAgentResult.value = null;
+  allEvidenceItems.value = [];
+  agentSimilarLoading.value = true;
+  similarIdentificationApi(selectedInvoices.value[0])
+    .then((data) => {
+      similarAgentResult.value = data.data;
+      ElMessage.success("智能体分析成功");
+    })
+    .catch((error) => {
+      throw new Error(`Fetch error: ${error.message}`);
+    })
+    .finally(() => {
+      agentSimilarLoading.value = false;
+    });
+};
+
 const evidenceReplenish = async () => {
   evidenceReplenishLoading.value = true;
   evidenceSearch(selectedInvoices.value[0])
@@ -238,7 +339,7 @@ const evidenceReplenish = async () => {
     });
 };
 /* ---------------- 风控知识人工审核 ---------------- */
-const activeTab = ref<"CASE" | "INDUSTRY" | "RULE" | "SIGNAL">("RULE");
+const activeKnowledgeTab = ref<"CASE" | "INDUSTRY" | "RULE" | "SIGNAL">("RULE");
 
 const allEvidenceItems = ref<ReviewItem[]>([]);
 const tabCounts = computed(() => ({
@@ -295,7 +396,7 @@ const buildSignals = (signals: any[]): ReviewItem[] =>
   }));
 
 /* 当前 Tab 数据 */
-const currentItems = computed(() => allEvidenceItems.value.filter((i) => i.type === activeTab.value));
+const currentItems = computed(() => allEvidenceItems.value.filter((i) => i.type === activeKnowledgeTab.value));
 
 /* 操作 */
 const approve = async (item: ReviewItem) => {

+ 16 - 0
src/types/agent.ts

@@ -105,3 +105,19 @@ export interface ReviewItem {
   confidence?: number;
   raw: any; // 原始对象(完整入库)
 }
+
+
+export interface SimilarIdentificationEvidence {
+    confidence: number;  // 置信度,范围:0.0 ~ 1.0
+    source: string;  // 引用来源
+    summary: string;  // 证据的关键影响摘要
+}
+
+export interface SimilarIdentificationResult {
+    info: string;  // 商品信息
+    type: string;  // 商品分类
+    decision: "BELONG" | "NOT_BELONG" | "UNCERTAIN";  // 判断结果:BELONG | NOT_BELONG | UNCERTAIN
+    confidence: number;  // 置信度,范围:0.0 ~ 1.0
+    summary: string;  // 最终判断结论
+    evidence_chain: SimilarIdentificationEvidence[];  // 证据链,包含一系列对判断有影响的证据
+}

+ 4 - 0
src/types/index.ts

@@ -0,0 +1,4 @@
+export interface SystemPage {
+  pageNumber: number;
+  pageSize: number;
+}

+ 1 - 0
vite.config.ts

@@ -20,6 +20,7 @@ export default defineConfig({
   server: {
     port: 8873,
     open: true,
+    host: true,
     proxy: {
       "/api": {
         target: "http://127.0.0.1:7699",