diff --git a/.env.example b/.env.example index f85d421..6485b33 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,5 @@ # BD_AI_BASE_URL=https://api.deepseek.com/v1 # BD_AI_API_KEY= # BD_AI_MODEL=deepseek-chat +# 按能力选模型(选填,JSON):没配的能力用 BD_AI_MODEL +# BD_AI_MODELS={"reasoning": "deepseek-reasoner"} diff --git a/.gitignore b/.gitignore index 2bdea76..ca5a3c2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ dist/ *.tsbuildinfo .pytest_cache/ .ruff_cache/ + +# 评测输出(含真实招标文件内容) +evals/out/ diff --git a/AGENTS.md b/AGENTS.md index c9d30b2..11a5db7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -197,4 +197,6 @@ samples/ - 2026-11-03:接口契约 v0.1(`docs/architecture/api-design.md` + `openapi.yaml`,OpenAPI 3.1,64 个路径、81 个操作),覆盖 MVP 原型 13 页的主流程,运营后台另行定义。API1–API5 全部按建议确认:骨架搭好前以 `openapi.yaml` 为准,之后以 FastAPI 生成的快照为准;事件流用 fetch 读;异地登录确认后挤掉旧设备;扣点说明和实际不一致时拒绝、不扣点;报告后台生成。 - 2026-11-03:工程骨架完成(分支 `feat/engineering-skeleton`)。「上传 → 解析 → 显示清单」最小链路在真实样本 S02、S06 和虚构样例上跑通:分片上传、按文件头识别格式、Gotenberg 转 PDF、文字层与稳定句子编号、实质性条款抽取(假模型 + 三道校验 + 标注类规则兜底)、并入清单;额度预扣 / 结算 / 退回、行级安全策略、对象加密都有测试。后端 30 个测试、pyright 严格模式、import 规则全部通过;前端 5 个页面按设计规范 v0.2 实现,类型检查和构建通过。搭建中的调整记在架构 §13(SK1–SK8),SK1、SK2 待确认。 - 2026-11-03:测试环境接上真实模型(DeepSeek,配置在不进 git 的根目录 `.env`)。读标补齐五类清单和项目基本信息的抽取,修了国动样本暴露的 4 个问题(标注规则碎片、页码混入与跨页拆分、无效写法漏认、上级标题下的无效情形),见架构 §13 SK9、SK10。 -- 下一步:在 S01–S10 上做标注集评测(各类召回率、准确率),再做读标的修改类接口和 pdf.js 原文查看器;之后是查标(M3)。 +- 2026-11-03:读标可以走完一份文件:待确认区采纳 / 补上出处后采纳 / 修改后采纳 / 丢弃,正式清单修改、补出处、删除恢复、人工新增、修改记录,确认清单生成不可变版本;右侧 pdf.js 原文查看器点出处跳页、按坐标框高亮原句,选句补出处。实现约定见 `docs/architecture/api-design.md` §10。 +- 2026-11-03:审计待确认区:多数是好结果被校验误拦,根源在输入(表格框线被认成下划线)、提示词(要求逐项拆开)和模型没守规则。规则只修真正的缺陷,不迁就模型的错误输出;提示词出新版本(引文复制整句、并列不拆、跨页只引上一行,除实质性条款外输入改成纯文本)。国动的待确认 46 → 4,S02 17 → 13,S06 174 → 57。见架构 §13 SK11;离线评测工具 `evals/run_extract.py`。 +- 下一步:在 S01–S10 上做标注集评测(各类召回率、准确率),据此定模型选型;之后是查标(M3)。 diff --git a/apps/web/package.json b/apps/web/package.json index 65accd1..2cf6b70 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,6 +15,7 @@ "@fontsource/noto-serif-sc": "5.3.0", "@lucide/vue": "1.50.0", "@tanstack/vue-query": "5.104.1", + "pdfjs-dist": "6.4.299", "reka-ui": "2.10.5", "vue": "3.5.43", "vue-router": "4.6.4" diff --git a/apps/web/src/components/AppShell.vue b/apps/web/src/components/AppShell.vue index 8c6d410..5a22841 100644 --- a/apps/web/src/components/AppShell.vue +++ b/apps/web/src/components/AppShell.vue @@ -8,6 +8,7 @@ import { useQueryClient } from "@tanstack/vue-query"; import { computed, onBeforeUnmount, onMounted, ref } from "vue"; import { RouterLink, RouterView, useRoute, useRouter } from "vue-router"; import logoUrl from "@design/logo/svg/logo-vertical-reverse.svg?url"; +import Toasts from "./Toasts.vue"; import { api } from "@/lib/api"; import { provideCrumbs } from "@/lib/crumbs"; import { startEventStream } from "@/lib/events"; @@ -103,5 +104,6 @@ async function logout() { + diff --git a/apps/web/src/components/ChecklistItemRow.vue b/apps/web/src/components/ChecklistItemRow.vue index 42e1a35..5fb4e1f 100644 --- a/apps/web/src/components/ChecklistItemRow.vue +++ b/apps/web/src/components/ChecklistItemRow.vue @@ -2,17 +2,21 @@ /** * 清单项(设计规范 §7.5、§7.6、§7.16):重要性 + 标题 + 标签(来源、分类、包)+ 出处。 * AI 产出的文字带「AI」标签、正文墨色;待确认区的条目另加左侧 2px 蓝铅笔虚线(.ai-block)。 + * 当前条(.sel)= 浅底 + 3px 墨色竖条(Q3);操作按钮放在 actions 插槽,只在当前条和待确认条目上显示。 */ import { computed } from "vue"; import CitationBlock from "./CitationBlock.vue"; +import { groupCitations, type CitationGroup } from "@/lib/citations"; import { category, IMPORTANCE_MARK, IMPORTANCE_NAME, pendingReason, specFacts } from "@/lib/labels"; import type { ChecklistItem } from "@/lib/queries"; -const props = defineProps<{ item: ChecklistItem; pending?: boolean }>(); +const props = defineProps<{ item: ChecklistItem; pending?: boolean; selected?: boolean; activeCitation?: string | null }>(); +const emit = defineEmits<{ select: []; openCitation: [g: CitationGroup] }>(); -/** 一条结论最多默认展开 2 条出处,其余写「另有 N 处」(§7.3) */ -const shown = computed(() => props.item.citations.slice(0, 2)); -const more = computed(() => props.item.citations.length - shown.value.length); +/** 一条结论最多默认展开 2 处出处,其余写「另有 N 处」(§7.3);跨页的一句话算一处 */ +const groups = computed(() => groupCitations(props.item.citations)); +const shown = computed(() => groups.value.slice(0, 2)); +const more = computed(() => groups.value.length - shown.value.length); const mark = computed(() => IMPORTANCE_MARK[props.item.importance] ?? ""); /** AI 摘要和标题不同时才单独显示,避免重复 */ const summary = computed(() => (props.item.summary && props.item.summary !== props.item.title ? props.item.summary : "")); @@ -21,7 +25,7 @@ const facts = computed(() => specFacts(props.item.spec));