diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..02367dc --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,14 @@ + + + + + + 标博士 + + + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..65accd1 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,29 @@ +{ + "name": "@bid-doctor/web", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "typecheck": "vue-tsc --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "@bid-doctor/api-client": "workspace:*", + "@fontsource/barlow-semi-condensed": "5.3.0", + "@fontsource/noto-serif-sc": "5.3.0", + "@lucide/vue": "1.50.0", + "@tanstack/vue-query": "5.104.1", + "reka-ui": "2.10.5", + "vue": "3.5.43", + "vue-router": "4.6.4" + }, + "devDependencies": { + "@types/node": "24.19.2", + "@vitejs/plugin-vue": "6.0.9", + "typescript": "5.8.3", + "vite": "7.3.7", + "vue-tsc": "3.3.12" + } +} diff --git a/apps/web/public/config.js b/apps/web/public/config.js new file mode 100644 index 0000000..f644499 --- /dev/null +++ b/apps/web/public/config.js @@ -0,0 +1,9 @@ +/* 标博士用户端运行时配置。部署时按环境替换本文件,不用重新构建。 + * apiBaseUrl:接口地址。网页版默认同源 "/api/v1";桌面版写用户选择的服务器,如 "https://saas.example.com/api/v1"。 + * routerMode:"history"(网页版)或 "hash"(桌面外壳以 file:// 或自定义协议加载时)。 + * routerBase:history 模式下的路由基础路径,部署在子路径时改成如 "/app/"。 */ +window.__BD_CONFIG__ = { + apiBaseUrl: "/api/v1", + routerMode: "history", + routerBase: "/", +}; diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue new file mode 100644 index 0000000..02dd6ec --- /dev/null +++ b/apps/web/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/web/src/components/AppShell.vue b/apps/web/src/components/AppShell.vue new file mode 100644 index 0000000..8c6d410 --- /dev/null +++ b/apps/web/src/components/AppShell.vue @@ -0,0 +1,107 @@ + + + diff --git a/apps/web/src/components/ChecklistItemRow.vue b/apps/web/src/components/ChecklistItemRow.vue new file mode 100644 index 0000000..a87d7ae --- /dev/null +++ b/apps/web/src/components/ChecklistItemRow.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/apps/web/src/components/CitationBlock.vue b/apps/web/src/components/CitationBlock.vue new file mode 100644 index 0000000..87fb61c --- /dev/null +++ b/apps/web/src/components/CitationBlock.vue @@ -0,0 +1,20 @@ + + + diff --git a/apps/web/src/components/ConfirmDialog.vue b/apps/web/src/components/ConfirmDialog.vue new file mode 100644 index 0000000..388f62d --- /dev/null +++ b/apps/web/src/components/ConfirmDialog.vue @@ -0,0 +1,45 @@ + + + diff --git a/apps/web/src/components/ProjectRow.vue b/apps/web/src/components/ProjectRow.vue new file mode 100644 index 0000000..15614b3 --- /dev/null +++ b/apps/web/src/components/ProjectRow.vue @@ -0,0 +1,60 @@ + + + diff --git a/apps/web/src/env.d.ts b/apps/web/src/env.d.ts new file mode 100644 index 0000000..cd39087 --- /dev/null +++ b/apps/web/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module "*.vue" { + import type { DefineComponent } from "vue"; + const component: DefineComponent; + export default component; +} diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts new file mode 100644 index 0000000..73c3b72 --- /dev/null +++ b/apps/web/src/lib/api.ts @@ -0,0 +1,19 @@ +/** + * 接口客户端单例。网页版走 cookie(写操作由 api-client 自动带 X-CSRF-Token); + * 桌面版以后在这里传 getToken(从系统钥匙串取 Bearer 令牌),业务代码不用改。 + */ +import { ApiError, createApi, type ClientOptions } from "@bid-doctor/api-client"; +import { runtimeConfig } from "./config"; + +export const apiOptions: ClientOptions = { baseUrl: runtimeConfig.apiBaseUrl }; +export const api = createApi(apiOptions); + +/** 网络中断等非接口错误也包成「请求失败」,界面统一处理。 */ +export function errorTitle(e: unknown): string { + if (e instanceof ApiError) return e.problem.title; + return "网络连接失败,请检查网络后重试"; +} + +export function isApiError(e: unknown, code?: string): e is ApiError { + return e instanceof ApiError && (code === undefined || e.code === code); +} diff --git a/apps/web/src/lib/config.ts b/apps/web/src/lib/config.ts new file mode 100644 index 0000000..78175d7 --- /dev/null +++ b/apps/web/src/lib/config.ts @@ -0,0 +1,24 @@ +/** + * 运行时配置(架构 §5.2 第 2、6 条):接口地址和路由基础路径都不写死, + * 由 public/config.js 在页面加载前写入 window.__BD_CONFIG__;桌面外壳可以在加载前注入同名对象。 + */ +export interface RuntimeConfig { + apiBaseUrl: string; + routerMode: "history" | "hash"; + routerBase: string; +} + +declare global { + interface Window { + __BD_CONFIG__?: Partial; + } +} + +const raw = window.__BD_CONFIG__ ?? {}; +const viteBase = import.meta.env.BASE_URL.startsWith("/") ? import.meta.env.BASE_URL : "/"; + +export const runtimeConfig: Readonly = Object.freeze({ + apiBaseUrl: (raw.apiBaseUrl ?? "/api/v1").replace(/\/+$/, ""), + routerMode: raw.routerMode === "hash" ? "hash" : "history", + routerBase: raw.routerBase ?? viteBase, +}); diff --git a/apps/web/src/lib/crumbs.ts b/apps/web/src/lib/crumbs.ts new file mode 100644 index 0000000..41118f6 --- /dev/null +++ b/apps/web/src/lib/crumbs.ts @@ -0,0 +1,21 @@ +/** 顶栏面包屑:页面声明自己的位置,AppShell 显示。纯界面状态,用 provide / inject,不进 Pinia。 */ +import { inject, provide, ref, watchEffect, type InjectionKey, type Ref } from "vue"; +import type { RouteLocationRaw } from "vue-router"; + +export interface Crumb { + label: string; + to?: RouteLocationRaw; +} + +const KEY: InjectionKey> = Symbol("crumbs"); + +export function provideCrumbs(): Ref { + const crumbs = ref([]); + provide(KEY, crumbs); + return crumbs; +} + +export function useCrumbs(get: () => Crumb[]) { + const crumbs = inject(KEY, null); + if (crumbs) watchEffect(() => (crumbs.value = get())); +} diff --git a/apps/web/src/lib/events.ts b/apps/web/src/lib/events.ts new file mode 100644 index 0000000..0d84ee2 --- /dev/null +++ b/apps/web/src/lib/events.ts @@ -0,0 +1,70 @@ +/** + * 事件流(SSE,用 fetch 读,API2):事件只是「有变化」的提示,以资源的 GET 结果为准。 + * 进度事件直接写进解析任务和项目列表的缓存,让进度条即时动;其余事件让相关查询失效、重新请求。 + * 断线由 api-client 自动重连;页面上的 5 秒轮询兜底。 + */ +import { subscribeEvents, type BdEvent } from "@bid-doctor/api-client"; +import type { QueryClient } from "@tanstack/vue-query"; +import { apiOptions } from "./api"; +import { qk } from "./keys"; +import { TERMINAL_JOB, type ParseJob, type ProjectListItem } from "./queries"; + +type ProjectPage = { items: ProjectListItem[]; next_cursor: string | null }; + +function onEvent(qc: QueryClient, e: BdEvent) { + const job = e.job; + const jobId = job?.id; + if ((e.type === "job.progress" || e.type === "job.finished") && job && jobId && job.kind === "parse_job") { + // 事件可能比 GET 结果旧(重连补发、乱序):已结束的任务不再被进度事件改回「进行中」,进度只增不减 + qc.setQueryData(qk.parseJob(jobId), (old) => + !old || TERMINAL_JOB.has(old.status) || (job.progress ?? 0) < old.progress + ? old + : { + ...old, + status: job.status ?? old.status, + stage: (job.stage as ParseJob["stage"]) ?? old.stage, + progress: job.progress ?? old.progress, + }, + ); + qc.setQueryData(qk.projects, (old) => + old && { + ...old, + items: old.items.map((p) => + p.active_job && p.active_job.id === jobId && (job.progress ?? 0) >= (p.active_job.progress ?? 0) ? { ...p, active_job: { ...p.active_job, progress: job.progress ?? p.active_job.progress } } : p, + ), + }, + ); + if (e.type === "job.finished") { + // 结束后要拿完整结果(error、result),并刷新项目、额度、清单 + void qc.invalidateQueries({ queryKey: qk.parseJob(jobId) }); + void qc.invalidateQueries({ queryKey: qk.projects }); + void qc.invalidateQueries({ queryKey: qk.quota }); + if (e.project_id) { + void qc.invalidateQueries({ queryKey: qk.project(e.project_id) }); + void qc.invalidateQueries({ queryKey: qk.checklist(e.project_id) }); + void qc.invalidateQueries({ queryKey: qk.checklistItems(e.project_id) }); + } + } + return; + } + switch (e.type) { + case "quota.changed": + void qc.invalidateQueries({ queryKey: qk.quota }); + break; + case "project.changed": + void qc.invalidateQueries({ queryKey: qk.projects }); + if (e.project_id) void qc.invalidateQueries({ queryKey: qk.project(e.project_id) }); + break; + case "resync": + void qc.invalidateQueries(); + break; + default: + // notification.created、report.ready:骨架阶段还没有对应界面 + break; + } +} + +/** 登录后的页面框架挂载时开始订阅,返回取消函数。 */ +export function startEventStream(qc: QueryClient): () => void { + return subscribeEvents(apiOptions, (e) => onEvent(qc, e)); +} diff --git a/apps/web/src/lib/keys.ts b/apps/web/src/lib/keys.ts new file mode 100644 index 0000000..ed7df5a --- /dev/null +++ b/apps/web/src/lib/keys.ts @@ -0,0 +1,12 @@ +/** TanStack Query 的查询键,集中定义,事件流按这些键让缓存失效。 */ +export const qk = { + me: ["me"] as const, + quota: ["quota"] as const, + plans: ["plans"] as const, + projects: ["projects"] as const, + project: (id: string) => ["project", id] as const, + document: (id: string) => ["document", id] as const, + parseJob: (id: string) => ["parse-job", id] as const, + checklist: (projectId: string) => ["checklist", projectId] as const, + checklistItems: (projectId: string) => ["checklist-items", projectId] as const, +}; diff --git a/apps/web/src/lib/labels.ts b/apps/web/src/lib/labels.ts new file mode 100644 index 0000000..26a1d4d --- /dev/null +++ b/apps/web/src/lib/labels.ts @@ -0,0 +1,134 @@ +/** 枚举的中文显示名由前端维护(api-design §1);不认识的枚举值原样显示,不报错。 */ +import type { Schemas } from "@bid-doctor/api-client"; + +type Dict = Readonly>; +const pick = (d: Dict, k: string | null | undefined): string => (k ? (d as Record)[k] ?? k : ""); + +export const PROJECT_STATUS: Dict = { + tracking: "跟进中", + drafting: "编制中", + checked: "已检查", + submitted: "已递交", + archived: "已归档", +}; +export const projectStatus = (s: string) => pick(PROJECT_STATUS, s); + +export const LEGAL_REGIME: Dict = { + gov_procurement: "政府采购", + tendering: "招标投标", + unknown: "法律体系待识别", +}; +export const legalRegime = (s: string) => pick(LEGAL_REGIME, s); + +export const CATEGORY_ORDER: readonly Schemas["Category"][] = [ + "qualification", + "invalidity", + "scoring", + "schedule", + "tech_param", + "format_doc", + "price", +]; +export const CATEGORY: Dict = { + qualification: "资格条件", + invalidity: "无效条款", + scoring: "评分办法", + schedule: "日期与递交", + tech_param: "技术参数", + format_doc: "格式文件", + price: "报价", +}; +export const category = (s: string) => pick(CATEGORY, s); + +/** 界面显示为 ★ / ▲ / 一般(一般不显示符号) */ +export const IMPORTANCE_MARK: Dict = { substantive: "★", important: "▲", normal: "" }; +export const IMPORTANCE_NAME: Dict = { substantive: "实质性要求", important: "重要", normal: "一般" }; + +export const PENDING_REASON: Dict = { + no_citation: "没找到可靠出处", + low_confidence: "把握不大", + verify_failed: "出处核对没通过", + marker_uncovered: "标注句没被提取覆盖,由规则补出", + absent_in_rerun: "重新解析时没再出现", +}; +export const pendingReason = (s: string | null | undefined) => pick(PENDING_REASON, s); + +export const MARKER: Dict = { + star: "★", + asterisk: "*", + triangle: "▲", + italic_underline: "斜体加下划线", + bold: "加粗", + underline: "下划线", + keyword: "关键词", +}; + +export const QUOTA_UNIT: Dict = { parse: "解析", check: "检查", generate: "生成", dedupe: "查重" }; +export const quotaUnit = (s: string) => pick(QUOTA_UNIT, s); + +export const PLAN_KEY: Dict = { free: "free", pro: "pro", enterprise: "ent" }; + +/** 工作台「下一步」。target 为空表示骨架阶段还没有对应页面,只显示文字。 */ +export const NEXT_ACTION: Dict = { + upload_tender: "上传招标文件", + wait_parse: "查看解析进度", + select_lots: "选择所投的包", + confirm_checklist: "确认清单", + upload_bid: "上传投标文件", + run_check: "发起检查", + handle_fatal: "处理致命问题", + export_report: "导出检查报告", + archive: "归档项目", + none: "查看项目", +}; +export const nextAction = (s: string) => pick(NEXT_ACTION, s); + +/** 解析阶段(§7.11):服务端的 8 个 stage 合成界面上的 5 步 */ +export const PARSE_STEPS: readonly { label: string; stages: readonly string[] }[] = [ + { label: "识别文件格式", stages: ["detect", "convert"] }, + { label: "提取文字", stages: ["text_layer", "ocr"] }, + { label: "识别章节结构", stages: ["structure"] }, + { label: "提取清单", stages: ["extract"] }, + { label: "核对出处", stages: ["verify", "merge"] }, +]; +export const parseStepIndex = (stage: string | null | undefined) => + stage ? PARSE_STEPS.findIndex((s) => s.stages.includes(stage)) : -1; + +export const READY_TIP = "只表示致命问题都已处理,不代表投标文件没有问题。"; + +/* ---------- 时间 ---------- */ +const pad = (n: number) => String(n).padStart(2, "0"); +export const fmtDate = (d: Date) => `${d.getMonth() + 1}月${d.getDate()}日`; +export const fmtTime = (d: Date) => `${pad(d.getHours())}:${pad(d.getMinutes())}`; +export const fmtDateTime = (iso: string) => { + const d = new Date(iso); + return `${fmtDate(d)} ${fmtTime(d)}`; +}; +export const fmtDuration = (ms: number) => { + const s = Math.max(0, Math.round(ms / 1000)); + return { min: Math.floor(s / 60), sec: pad(s % 60) }; +}; + +/** 投标截止的两行显示和颜色类(components.css 的 .due-*) */ +export function deadline(iso: string | null | undefined, now = new Date()): { main: string; sub: string; cls: string } | null { + if (!iso) return null; + const d = new Date(iso); + const ms = d.getTime() - now.getTime(); + const when = `${fmtDate(d)} ${fmtTime(d)}`; + if (ms < 0) return { main: "已截止", sub: when, cls: "due-past" }; + const hours = Math.floor(ms / 3_600_000); + const days = Math.floor(hours / 24); + if (d.toDateString() === now.toDateString()) return { main: `今天 ${fmtTime(d)} 截止`, sub: `剩 ${hours} 小时`, cls: "due-today" }; + if (hours <= 72) return { main: `剩 ${days} 天 ${hours % 24} 小时`, sub: when, cls: "due-soon" }; + return { main: `剩 ${days} 天`, sub: when, cls: "" }; +} + +export function extOf(name: string): string { + const m = /\.([a-z0-9]{1,5})$/i.exec(name); + return m?.[1] ? m[1].toUpperCase() : "文件"; +} + +export function fmtSize(bytes: number): string { + if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`; + return `${(bytes / 1024 / 1024).toFixed(1)} MB`; +} diff --git a/apps/web/src/lib/queries.ts b/apps/web/src/lib/queries.ts new file mode 100644 index 0000000..6c62f36 --- /dev/null +++ b/apps/web/src/lib/queries.ts @@ -0,0 +1,92 @@ +/** 服务端数据:全部由 TanStack Query 管理,不放进 Pinia(架构 §4.2)。 */ +import { unwrap, type Schemas } from "@bid-doctor/api-client"; +import { useQuery } from "@tanstack/vue-query"; +import { computed, toValue, type MaybeRefOrGetter } from "vue"; +import { api } from "./api"; +import { qk } from "./keys"; + +export type Me = Schemas["Me"]; +export type Quota = Schemas["QuotaOverview"]; +export type Plan = Schemas["Plan"]; +export type ProjectListItem = Schemas["ProjectListItem"]; +export type ProjectDetail = Schemas["ProjectDetail"]; +export type DocumentInfo = Schemas["Document"]; +export type ParseJob = Schemas["ParseJob"]; +export type Checklist = Schemas["Checklist"]; +/** + * 契约里 ChecklistItem 把 categories、title、importance 列为必填,但它们定义在 allOf 的 ChecklistItemFields 里, + * openapi-typescript 生成的类型仍是可选。这里按契约的 required 收窄,不改字段本身。 + */ +export type ChecklistItem = Schemas["ChecklistItem"] & Required>; +export type Citation = Schemas["Citation"]; +export type MarkingRule = Schemas["MarkingRule"]; +export type SessionInfo = Schemas["SessionInfo"]; + +export const fetchMe = () => unwrap(api.GET("/me")); + +export function useMe() { + return useQuery({ queryKey: qk.me, queryFn: fetchMe, staleTime: 60_000 }); +} + +export function useQuota() { + return useQuery({ queryKey: qk.quota, queryFn: () => unwrap(api.GET("/quota")) }); +} + +/** 某个额度单位(如 parse)的本期账户;没有这个单位时为 undefined */ +export function useQuotaAccount(unit: Schemas["QuotaUnit"]) { + const q = useQuota(); + return computed(() => q.data.value?.accounts.find((a) => a.unit === unit)); +} + +export function usePlans() { + return useQuery({ queryKey: qk.plans, queryFn: () => unwrap(api.GET("/plans")), staleTime: 10 * 60_000 }); +} + +export function useProjects() { + // 骨架阶段只取第一页(默认按截止时间排序);项目多了再接游标分页 + return useQuery({ queryKey: qk.projects, queryFn: () => unwrap(api.GET("/projects", { params: { query: { limit: 100 } } })) }); +} + +export function useProject(id: MaybeRefOrGetter) { + return useQuery({ + queryKey: computed(() => qk.project(toValue(id) ?? "")), + queryFn: () => unwrap(api.GET("/projects/{project_id}", { params: { path: { project_id: toValue(id)! } } })), + enabled: computed(() => !!toValue(id)), + }); +} + +export function useDocument(id: MaybeRefOrGetter) { + return useQuery({ + queryKey: computed(() => qk.document(toValue(id) ?? "")), + queryFn: () => unwrap(api.GET("/documents/{document_id}", { params: { path: { document_id: toValue(id)! } } })), + enabled: computed(() => !!toValue(id)), + }); +} + +export const TERMINAL_JOB: ReadonlySet = new Set(["succeeded", "failed", "cancelled"]); + +/** 解析进度:事件流推送为主(lib/events.ts 直接改这条缓存),每 5 秒轮询兜底,结束后停止轮询。 */ +export function useParseJob(id: MaybeRefOrGetter) { + return useQuery({ + queryKey: computed(() => qk.parseJob(toValue(id))), + queryFn: () => unwrap(api.GET("/parse-jobs/{job_id}", { params: { path: { job_id: toValue(id) } } })), + refetchInterval: (query) => (query.state.data && TERMINAL_JOB.has(query.state.data.status) ? false : 5000), + }); +} + +export function useChecklist(projectId: MaybeRefOrGetter) { + return useQuery({ + queryKey: computed(() => qk.checklist(toValue(projectId))), + queryFn: () => unwrap(api.GET("/projects/{project_id}/checklist", { params: { path: { project_id: toValue(projectId) } } })), + }); +} + +export function useChecklistItems(projectId: MaybeRefOrGetter) { + return useQuery({ + queryKey: computed(() => qk.checklistItems(toValue(projectId))), + queryFn: () => + unwrap(api.GET("/projects/{project_id}/checklist/items", { params: { path: { project_id: toValue(projectId) } } })) as Promise<{ + items: ChecklistItem[]; + }>, + }); +} diff --git a/apps/web/src/lib/queryClient.ts b/apps/web/src/lib/queryClient.ts new file mode 100644 index 0000000..11d6774 --- /dev/null +++ b/apps/web/src/lib/queryClient.ts @@ -0,0 +1,24 @@ +import { ApiError } from "@bid-doctor/api-client"; +import { MutationCache, QueryCache, QueryClient } from "@tanstack/vue-query"; + +let onUnauthorized: (e: ApiError) => void = () => {}; +/** 会话失效(401:过期、被其他设备挤下线)时的统一处理,由 main.ts 注入(需要 router)。 */ +export function setUnauthorizedHandler(fn: (e: ApiError) => void) { + onUnauthorized = fn; +} + +const handle = (e: unknown) => { + if (e instanceof ApiError && e.status === 401) onUnauthorized(e); +}; + +export const queryClient = new QueryClient({ + queryCache: new QueryCache({ onError: handle }), + mutationCache: new MutationCache({ onError: handle }), + defaultOptions: { + queries: { + // 4xx 是确定的结果,不重试;网络和 5xx 重试两次 + retry: (count, e) => !(e instanceof ApiError && e.status < 500) && count < 2, + refetchOnWindowFocus: false, + }, + }, +}); diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts new file mode 100644 index 0000000..9a34634 --- /dev/null +++ b/apps/web/src/main.ts @@ -0,0 +1,28 @@ +import { VueQueryPlugin } from "@tanstack/vue-query"; +import { createApp } from "vue"; +import faviconUrl from "@design/logo/favicon.svg?url"; +// 字体全部自托管(设计规范 §4.1):标题思源宋体只引 600;数字 Barlow Semi Condensed 500 / 600;正文用系统黑体 +import "@fontsource/noto-serif-sc/600.css"; +import "@fontsource/barlow-semi-condensed/500.css"; +import "@fontsource/barlow-semi-condensed/600.css"; +// 设计规范是唯一的样式来源(AR10):直接引用 docs/design/system,不复制 +import "@design/system/tokens.css"; +import "@design/system/components.css"; +import "@/styles/app.css"; +import App from "./App.vue"; +import { queryClient, setUnauthorizedHandler } from "./lib/queryClient"; +import { router } from "./router"; + +const icon = document.createElement("link"); +icon.rel = "icon"; +icon.href = faviconUrl; +document.head.append(icon); + +setUnauthorizedHandler((e) => { + queryClient.clear(); + const cur = router.currentRoute.value; + if (cur.name === "login") return; + void router.replace({ name: "login", query: { redirect: cur.fullPath, reason: e.code } }); +}); + +createApp(App).use(router).use(VueQueryPlugin, { queryClient }).mount("#app"); diff --git a/apps/web/src/pages/ChecklistPage.vue b/apps/web/src/pages/ChecklistPage.vue new file mode 100644 index 0000000..0ff85b7 --- /dev/null +++ b/apps/web/src/pages/ChecklistPage.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/apps/web/src/pages/LoginPage.vue b/apps/web/src/pages/LoginPage.vue new file mode 100644 index 0000000..ae56f31 --- /dev/null +++ b/apps/web/src/pages/LoginPage.vue @@ -0,0 +1,401 @@ + + + + + diff --git a/apps/web/src/pages/ParsePage.vue b/apps/web/src/pages/ParsePage.vue new file mode 100644 index 0000000..0798845 --- /dev/null +++ b/apps/web/src/pages/ParsePage.vue @@ -0,0 +1,276 @@ + + + + + diff --git a/apps/web/src/pages/ProjectNewPage.vue b/apps/web/src/pages/ProjectNewPage.vue new file mode 100644 index 0000000..4392b4f --- /dev/null +++ b/apps/web/src/pages/ProjectNewPage.vue @@ -0,0 +1,262 @@ + + + + + diff --git a/apps/web/src/pages/WorkbenchPage.vue b/apps/web/src/pages/WorkbenchPage.vue new file mode 100644 index 0000000..a53a848 --- /dev/null +++ b/apps/web/src/pages/WorkbenchPage.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/apps/web/src/platform/index.ts b/apps/web/src/platform/index.ts new file mode 100644 index 0000000..be4fa3f --- /dev/null +++ b/apps/web/src/platform/index.ts @@ -0,0 +1,27 @@ +/** + * 平台能力抽象(架构 §5.2 第 4 条):文件选择等都从这里走。现在只有网页版实现; + * 以后迁到 packages/platform,桌面外壳另给一份实现,业务代码不用改。 + */ +export interface PickFileOptions { + /** 例如 [".pdf", ".docx"];只是选择框的过滤建议,真实格式以服务端按文件头识别为准 */ + accept: readonly string[]; +} + +export interface Platform { + pickFile(opts: PickFileOptions): Promise; +} + +const web: Platform = { + pickFile({ accept }) { + return new Promise((resolve) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = accept.join(","); + input.addEventListener("change", () => resolve(input.files?.[0] ?? null), { once: true }); + input.addEventListener("cancel", () => resolve(null), { once: true }); + input.click(); + }); + }, +}; + +export const platform: Platform = web; diff --git a/apps/web/src/router.ts b/apps/web/src/router.ts new file mode 100644 index 0000000..4195af6 --- /dev/null +++ b/apps/web/src/router.ts @@ -0,0 +1,55 @@ +import { ApiError } from "@bid-doctor/api-client"; +import { createRouter, createWebHashHistory, createWebHistory } from "vue-router"; +import AppShell from "@/components/AppShell.vue"; +import { runtimeConfig } from "@/lib/config"; +import { qk } from "@/lib/keys"; +import { fetchMe } from "@/lib/queries"; +import { queryClient } from "@/lib/queryClient"; + +declare module "vue-router" { + interface RouteMeta { + /** 不需要登录 */ + public?: boolean; + } +} + +export const router = createRouter({ + // 路由基础路径可配置;桌面外壳用 hash 模式(架构 §5.2 第 6 条) + history: runtimeConfig.routerMode === "hash" ? createWebHashHistory() : createWebHistory(runtimeConfig.routerBase), + routes: [ + { path: "/login", name: "login", component: () => import("@/pages/LoginPage.vue"), meta: { public: true } }, + { + path: "/", + component: AppShell, + children: [ + { path: "", name: "workbench", component: () => import("@/pages/WorkbenchPage.vue") }, + { path: "projects/new", name: "project-new", component: () => import("@/pages/ProjectNewPage.vue") }, + { + path: "projects/:projectId/parse/:jobId", + name: "parse", + component: () => import("@/pages/ParsePage.vue"), + props: true, + }, + { + path: "projects/:projectId/checklist", + name: "checklist", + component: () => import("@/pages/ChecklistPage.vue"), + props: true, + }, + ], + }, + { path: "/:pathMatch(.*)*", redirect: "/" }, + ], +}); + +router.beforeEach(async (to) => { + if (to.meta.public) return true; + try { + await queryClient.fetchQuery({ queryKey: qk.me, queryFn: fetchMe, staleTime: 60_000 }); + return true; + } catch (e) { + // 被其他设备挤下线(session_revoked)、过期(session_expired)时,登录页说明原因 + const reason = e instanceof ApiError && e.code !== "unauthenticated" ? { reason: e.code } : {}; + return { name: "login", query: { ...(to.fullPath === "/" ? {} : { redirect: to.fullPath }), ...reason } }; + } +}); diff --git a/apps/web/src/styles/app.css b/apps/web/src/styles/app.css new file mode 100644 index 0000000..e43823f --- /dev/null +++ b/apps/web/src/styles/app.css @@ -0,0 +1,242 @@ +/* 标博士用户端 · 页面框架与共用小件 + * 视觉来源:docs/design/system/tokens.css + components.css(main.ts 通过 @design 别名直接引用,不复制)。 + * 本文件只放这两份文件里还没有的「页面框架层」:侧栏、顶栏、页头、项目头,以及原型 docs/prototype/assets/app.css + * 第 1、2 部分里本应用用到的那几块(B1 图标、B3 按钮状态、B4 输入、B7 复选、B8 标签页、B11 对话框、B13 提示气泡、 + * B14 空状态与进度、B15 杂项、B16 AI 块、B17 统计、B19 分类导航、B20 项目行),选择器和写法照搬原型。 + * 只用 tokens.css 的变量,不写裸色值。 + * TODO:这一层应提升到 docs/design/system(或 packages/ui)作为唯一来源,届时删掉本文件里的副本。 + * 与原型的差别:原型把框架挂在 body.app 上,这里挂在 AppShell 的根元素 .app-frame 上。 */ + +/* ===================================================================== + * 1. 页面框架:96px 墨色渐变侧栏 + 透明顶栏 + 内容区左上角圆角 + * ===================================================================== */ +.app-frame{display:grid;grid-template-columns:var(--rail-w) minmax(0,1fr);min-height:100vh;min-width:1280px;background:linear-gradient(180deg,var(--side-top) 0%,var(--side-bottom) 100%) fixed} +.sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:0 0 var(--sp-4) 10px;color:var(--side-fg);min-width:0} +.sidebar::after{content:"";position:absolute;left:0;right:0;bottom:0;height:260px;pointer-events:none;background-image:radial-gradient(var(--side-dots) 1px,transparent 1.2px);background-size:14px 14px;-webkit-mask-image:linear-gradient(to top,black,transparent);mask-image:linear-gradient(to top,black,transparent)} +.sidebar > *{position:relative;z-index:1} +.sidebar .brand{display:flex;justify-content:center;padding:var(--sp-5) 10px var(--sp-5) 0;border-radius:var(--radius-ctl)} +.sidebar .brand img{width:60px;height:auto;display:block} +.sidebar .nav{display:flex;flex-direction:column;gap:var(--sp-1)} +.sidebar .nav a{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-1);height:62px;margin-right:10px;border-radius:var(--radius-card);font-size:var(--fs-caption);line-height:16px;color:var(--side-fg);text-align:center;text-decoration:none;white-space:nowrap;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.sidebar .nav a .ic{width:22px;height:22px} +.sidebar .nav a:hover{background:var(--side-hover);color:var(--side-fg-strong)} +.sidebar .nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)} +.sidebar a:focus-visible,.sidebar button:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px} +.sidebar .me{display:flex;flex-direction:column;align-items:center;margin-top:auto;padding:var(--sp-4) 10px var(--sp-1) 0;color:var(--side-muted);text-align:center} +.sidebar .avatar{display:inline-grid;place-items:center;width:44px;height:44px;margin-bottom:var(--sp-2);border-radius:50%;background:var(--side-avatar-bg);box-shadow:0 0 0 1px var(--side-avatar-ring);color:var(--side-fg-strong);font-size:var(--fs-title);font-weight:var(--fw-bold)} +.sidebar .uname{color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +/* 套餐标签:只在账户区使用;免费版中性、专业版浅金、企业版金色实底 */ +.plan{display:inline-block;margin-top:var(--sp-1);padding:0 var(--sp-2);border-radius:var(--radius-xs);font-size:var(--fs-caption);line-height:20px;font-weight:var(--fw-bold);white-space:nowrap} +.plan[data-plan="free"]{background:var(--plan-free-bg);color:var(--side-fg)} +.plan[data-plan="pro"]{background:var(--plan-pro-bg);color:var(--plan-pro-fg);box-shadow:inset 0 0 0 1px var(--plan-pro-line)} +.plan[data-plan="ent"]{background:var(--plan-ent-bg);color:var(--c-ink)} + +.main{min-width:0;display:flex;flex-direction:column;min-height:100vh;background:var(--bg-page);border-top-left-radius:var(--radius-card)} +.top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:var(--sp-3);height:var(--topbar-h);padding:0 var(--pad-page);background:transparent;border-bottom:var(--line) solid transparent;border-top-left-radius:var(--radius-card);transition:background-color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease)} +.top.scrolled{background:var(--topbar-glass);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2);border-bottom-color:var(--border-subtle)} +.crumbs{display:flex;align-items:center;gap:6px;min-width:0;color:var(--text-secondary);white-space:nowrap;overflow:hidden} +.crumbs > *{min-width:0;overflow:hidden;text-overflow:ellipsis} +.crumbs > span{flex:none} +.crumbs .org{flex:none;color:var(--text-primary);font-weight:var(--fw-bold)} +.crumbs a{color:var(--text-secondary);text-decoration:none} +.crumbs a:hover{color:var(--text-primary);text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px} +.crumbs b{font-weight:var(--fw-regular)} +.top-right{display:flex;align-items:center;gap:var(--sp-1);flex:none;margin-left:auto} +/* 顶栏额度:用到 ≥ 80% 时该项数字变赭色加粗(.warn);不用红 */ +.quota-chip{display:inline-flex;align-items:center;gap:6px;height:var(--control-h-sm);padding:0 10px;border-radius:var(--radius-ctl);color:var(--text-secondary);text-decoration:none;white-space:nowrap} +.quota-chip .q{display:inline-flex;align-items:baseline;gap:6px} +.quota-chip .fig{color:var(--text-primary);font-size:var(--fs-figure-sm)} +.quota-chip .q.warn .fig{color:var(--warn-text);font-weight:var(--fw-bold)} +.quota-chip .sep{width:1px;height:12px;margin:0 var(--sp-1);background:var(--border-subtle)} +.top .vsep{flex:none;width:1px;height:20px;margin:0 var(--sp-1);background:var(--border-subtle)} +.content{width:100%;padding:20px var(--pad-page) var(--sp-12)} +.content.narrow{max-width:800px} +/* 页头:标题 + 一句话;右侧放页面级主按钮(Q12:页面级一个) */ +.page-title{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--sp-6);margin-bottom:20px} +.page-title h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0} +.page-title p{margin:2px 0 0;color:var(--text-secondary)} +.page-title .actions{margin-left:auto;display:flex;gap:var(--sp-3);align-items:center} +.stack > * + *{margin-top:var(--gap-card)} + +/* 项目头(项目内页面):名称 + 信息一行;右侧截止、状态;下方标签页 */ +.ph{padding:var(--sp-1) var(--pad-page) 0} +.ph-l1{display:flex;align-items:flex-end;gap:var(--sp-8)} +.ph-l1 > div{min-width:0} +.ph h1{margin:0;font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.ph-meta{margin:2px 0 0;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.ph .facts{display:flex;gap:var(--sp-8);margin:0 0 0 auto;flex:none} +.ph .facts > div{display:flex;flex-direction:column;gap:2px} +.ph .facts dt{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +.ph .facts dd{display:flex;align-items:center;gap:6px;min-height:var(--lh-body);margin:0;white-space:nowrap} +.ph .tabs{margin-top:var(--sp-3)} +.ph + .content{padding-top:var(--sp-5)} + +/* ===================================================================== + * 2. 共用小件 + * ===================================================================== */ +/* B1 图标(Lucide 线性,1.5px 描边,16 / 20px;侧栏菜单 22px) */ +.ic{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;vertical-align:-3px} +.ic.lg{width:20px;height:20px} +.ic.xl{width:32px;height:32px;stroke-width:1.25} +.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} + +/* B3 按钮补充状态:加载中两个文字叠在同一格,切换时宽度不变 */ +.btn .ic{margin:0 -2px} +.btn.swap{display:inline-grid;place-items:center} +.btn.swap > span{grid-area:1/1;white-space:nowrap} +.btn.swap > .busy,.btn.swap.is-loading > .idle{visibility:hidden} +.btn.swap.is-loading > .busy{visibility:visible} +.btn.is-loading{cursor:progress} +.btn.primary.is-loading{background:var(--primary-hover);border-color:var(--primary-hover)} +.icon-btn{position:relative;flex:none;display:inline-grid;place-items:center;width:var(--control-h);height:var(--control-h);padding:0;border:var(--line) solid transparent;border-radius:var(--radius-ctl);background:none;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.icon-btn:hover{background:var(--c-n100)} +.icon-btn.sm{width:var(--control-h-sm);height:var(--control-h-sm)} + +/* B4 输入框 */ +.input{width:100%;transition:border-color var(--dur-fast) var(--ease)} +.input:hover{border-color:var(--c-ink)} +.input:focus{outline:0;border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)} +.field.err .input:focus{border-color:var(--warn);box-shadow:0 0 0 1px var(--warn)} +.input[disabled]{background:var(--c-n100);border-color:var(--c-n100);color:var(--text-disabled);cursor:not-allowed} +.field label{display:flex;gap:var(--sp-1);align-items:baseline} +.field label small{margin-left:2px} +.field .choice{display:inline-flex;gap:var(--sp-2);align-items:flex-start;font-weight:var(--fw-regular)} +.hint-warn{display:flex;gap:6px;align-items:flex-start;margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--warn-text)} +.hint-warn .ico{flex:none;width:14px;height:14px;font-size:10px;margin:3px 0 0} +.field + .field{margin-top:0} +.search{position:relative;display:block} +.search > .ic{position:absolute;left:11px;top:10px;color:var(--text-secondary);pointer-events:none} +.search .input{padding-left:34px} + +/* B7 复选框 */ +.ck{appearance:none;flex:none;display:inline-grid;place-content:center;width:16px;height:16px;margin:0;border:var(--line) solid var(--border-control);border-radius:var(--radius-xs);background:var(--bg-surface);cursor:pointer;vertical-align:-3px} +.ck:hover{border-color:var(--c-ink)} +.ck::after{content:"";display:none} +.ck:checked{background:var(--c-ink);border-color:var(--c-ink)} +.ck:checked::after{display:block;width:8px;height:4px;border:solid var(--c-white);border-width:0 0 1.75px 1.75px;transform:translateY(-1px) rotate(-45deg)} +.choice{display:inline-flex;gap:var(--sp-2);align-items:flex-start;cursor:pointer;line-height:var(--lh-body)} +.choice .ck{margin-top:3px} + +/* B8 标签页 */ +.tabs{display:flex;gap:var(--sp-6);border-bottom:var(--line) solid var(--border-subtle)} +.tabs [role=tab]{display:flex;gap:6px;align-items:center;margin-bottom:-1px;padding:var(--sp-2) 0 10px;border:0;border-bottom:2px solid transparent;background:none;font:var(--fs-body) var(--font-sans);color:var(--text-secondary);cursor:pointer;text-decoration:none;white-space:nowrap} +.tabs [role=tab]:hover{color:var(--text-primary)} +.tabs [role=tab][aria-selected="true"]{color:var(--text-primary);font-weight:var(--fw-bold);border-bottom-color:var(--c-ink)} + +/* B11 对话框(480 / 640,圆角 12;对话框里可以有自己的主按钮) */ +.scrim{position:fixed;inset:0;z-index:50;display:grid;overflow:auto;padding:var(--sp-8) var(--sp-4);background:var(--scrim)} +.dialog{position:relative;margin:auto;width:480px;max-width:100%;padding:var(--sp-6);background:var(--bg-surface);border-radius:var(--radius-card);box-shadow:var(--shadow-overlay)} +.dialog h2{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 var(--sp-8) var(--sp-3) 0} +.dialog .body > p{margin:0 0 var(--sp-3)} +.dialog .foot{display:flex;justify-content:flex-end;align-items:center;gap:var(--sp-2);margin-top:var(--sp-6)} + +/* B12 提醒补充 */ +.alert > div{flex:1;min-width:0} +.alert > p{flex:1} +.alert .sub{display:block;margin-top:2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +.errno{font:var(--fw-medium) var(--fs-figure-xs)/1 var(--font-figures);color:var(--text-secondary);letter-spacing:.02em} + +/* B13 提示气泡(悬停 / 聚焦显示) */ +[data-tip]{position:relative} +[data-tip]:hover::after,[data-tip]:focus-visible::after{content:attr(data-tip);position:absolute;z-index:55;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);width:max-content;max-width:280px;padding:6px 10px;border-radius:var(--radius-ctl);background:var(--c-ink);color:var(--text-on-dark);font:var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-sans);white-space:normal;text-align:left;pointer-events:none} +[data-tip].tip-below:hover::after,[data-tip].tip-below:focus-visible::after{bottom:auto;top:calc(100% + 8px)} +[data-tip].tip-left:hover::after,[data-tip].tip-left:focus-visible::after{left:auto;right:0;transform:none} + +/* B14 空状态、骨架屏、进度(骨架屏静止,Q8) */ +.empty{display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--sp-12) var(--sp-6)} +.empty > .ic{color:var(--text-secondary);margin-bottom:var(--sp-3)} +.empty b{font-size:var(--fs-title);line-height:var(--lh-title)} +.empty p{margin:var(--sp-1) 0 var(--sp-5);color:var(--text-secondary);max-width:30em} +.empty.sm{padding:var(--sp-8) var(--sp-4)} +.empty.sm b{font-size:var(--fs-body);line-height:var(--lh-body)} +.empty.sm p{margin-bottom:0} +.empty.sm > .btn{margin-top:var(--sp-4)} +.skel{display:block;height:12px;border-radius:var(--radius-xs);background:var(--c-n100)} +.skel.t{height:16px;width:40%} +.skel + .skel{margin-top:var(--sp-2)} +.progress{display:flex;align-items:center;gap:10px} +.progress .bar{flex:1;margin:0} +.progress .fig{min-width:3em;text-align:right} +.steps .dot{line-height:1;font-family:var(--font-sans)} +.steps li > span:nth-child(2){flex:1} +.steps .fail .dot{border-color:var(--warn);color:var(--warn-text);font-weight:var(--fw-bold)} +.steps .fail{color:var(--warn-text)} + +/* B15 杂项小件 */ +.kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;border-radius:var(--radius-tag);background:var(--c-n100);font:var(--fw-bold) var(--fs-caption)/1 var(--font-figures);color:var(--text-secondary);white-space:nowrap} +.muted{color:var(--text-secondary)} +.cap{font-size:var(--fs-caption);line-height:var(--lh-caption)} +.fig.m{font-weight:var(--fw-medium)} +.fig.xs{font-size:var(--fs-figure-xs)} +:is(.hint-warn,.stat p,.quota p) .fig{font-size:var(--fs-figure-xs)} +.kv{display:grid;grid-template-columns:96px minmax(0,1fr);gap:var(--sp-2) var(--sp-4);margin:0} +.kv dt{color:var(--text-secondary)} +.kv dd{margin:0} +.drop{display:flex;flex-direction:column;align-items:center;gap:var(--sp-1);border:1.5px dashed var(--border-control);border-radius:var(--radius-card);background:var(--bg-surface);padding:var(--sp-8) var(--sp-6);text-align:center;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)} +.drop:hover,.drop.is-over{border-color:var(--c-ink);background:var(--c-n50)} +.drop > .ic{color:var(--text-secondary)} +.drop b{font-size:var(--fs-title);line-height:var(--lh-title)} +.drop span{color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} +.drop[aria-disabled="true"]{pointer-events:none;color:var(--text-disabled);border-color:var(--border-subtle);background:var(--c-n50)} +.file-row{display:grid;grid-template-columns:40px minmax(0,1fr);grid-auto-flow:column;grid-auto-columns:auto;gap:var(--sp-3);align-items:center;padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card)} +.file-row .ext{font:var(--fw-bold) 11px/1 var(--font-figures);background:var(--c-n100);border-radius:var(--radius-xs);padding:5px 0;text-align:center} +.file-row b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.file-row .acts{display:flex;gap:var(--sp-1);align-items:center} + +/* B16 AI 产出块:确认前「AI」小标签 + 左侧 2px 蓝铅笔虚线;正文始终墨色(Q2) */ +.ai-block{border-left:2px dashed var(--ai);padding:var(--sp-1) 0 var(--sp-1) var(--sp-3);color:var(--text-primary)} + +/* B17 卡片头、统计 */ +.card-h{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)} +.stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden} +.stat{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;padding:var(--sp-4) var(--card-x);border:0;border-left:var(--line) solid var(--border-subtle);background:none;font:inherit;color:inherit;text-align:left} +.stat:first-child{border-left:0} +.stat small{display:block;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-secondary)} +.stat .big{font:var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums} +.stat .big.fatal{color:var(--sev-fatal)} +.stat .big.due{color:var(--warn-text)} + +/* B19 列表卡片:左侧分类导航 + 列表;列表里的分组标题(.ai 是清单顶部的「待确认」分组) */ +.cmp-list{display:grid;grid-template-columns:176px minmax(0,1fr);min-height:0;overflow:hidden} +@media (max-width:1399px){.cmp-list{grid-template-columns:152px minmax(0,1fr)}} +.cmp-list > .lst{position:relative;min-width:0} +.cats{display:flex;flex-direction:column;gap:2px;border-right:var(--line) solid var(--border-subtle);padding:var(--sp-3) var(--sp-2)} +.cats button{display:flex;flex:none;width:100%;align-items:center;justify-content:space-between;height:var(--control-h-sm);padding:0 10px;border:0;background:none;border-radius:var(--radius-ctl);font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;text-align:left;transition:background-color var(--dur-fast) var(--ease)} +.cats button:hover{background:var(--c-n100)} +.cats button[aria-current="true"]{background:var(--c-n100);font-weight:var(--fw-bold)} +.cats button .fig{font-size:var(--fs-figure-sm);font-weight:var(--fw-medium);color:var(--text-secondary)} +.cats button.ai,.cats button.ai .fig{color:var(--ai)} +.cats hr{width:100%;margin:var(--sp-2) 0;border:0;border-top:var(--line) solid var(--border-subtle)} +.grp-h{display:flex;align-items:baseline;gap:var(--sp-2);padding:var(--sp-5) var(--card-x) var(--sp-2)} +.grp-h b{font-weight:var(--fw-bold)} +.grp-h .fig{font-weight:var(--fw-medium);color:var(--text-secondary)} +.grp-h.ai b,.grp-h.ai .fig{color:var(--ai)} +.grp-h p{margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} + +/* B20 项目行:栅格对齐的列表,整行可点 */ +:root{--prow-cols:minmax(0,1fr) 120px 120px 64px 140px var(--control-h-sm);--prow-gap:var(--sp-4)} +@media (max-width:1399px){:root{--prow-cols:minmax(0,1fr) 108px 108px 56px 132px var(--control-h-sm);--prow-gap:var(--sp-3)}} +.prow-head,.prow{display:grid;grid-template-columns:var(--prow-cols);column-gap:var(--prow-gap);align-items:center;padding:0 var(--card-x)} +.prow-head{padding-bottom:var(--sp-2);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} +:is(.prow-head,.prow) > .c{justify-self:center;text-align:center} +.prows{list-style:none;margin:0;padding:0} +.prows > .empty{border-top:var(--line) solid var(--border-subtle)} +.prow{position:relative;min-height:var(--row-h);padding-top:10px;padding-bottom:10px;border-top:var(--line) solid var(--border-subtle);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)} +.prow:hover,.prow:focus-within{background:var(--bg-hover)} +.prow:last-child{border-radius:0 0 var(--radius-card) var(--radius-card)} +.prow:has(.pname:focus-visible){outline:2px solid var(--focus-ring);outline-offset:-2px} +.prow .pt{min-width:0} +.pname{display:block;font-weight:var(--fw-bold);color:var(--text-primary);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;outline:0} +.prow .pname::after{content:"";position:absolute;inset:0} +.psub{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.prow .dl b{display:block;font-weight:var(--fw-regular);white-space:nowrap} +.prow .dl b.due-today{font-weight:var(--fw-bold)} +.prow .dl small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap} +.prow .st{display:flex;align-items:center;gap:var(--sp-2);white-space:nowrap} +.prow .st .hint-u{position:relative;z-index:1} +.dash{color:var(--text-secondary)} +.nx{position:relative;z-index:1;display:inline-flex;align-items:center;gap:var(--sp-1);height:var(--control-h-sm);padding:0;border:0;background:none;font:inherit;color:var(--text-primary);white-space:nowrap;cursor:pointer;text-decoration:none} +.nx span{text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px;transition:text-decoration-color var(--dur-fast) var(--ease)} +.nx:hover span{text-decoration-color:var(--c-ink)} +.nx .ic{color:var(--text-secondary);transition:transform var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} +.prow:hover .nx .ic,.prow:focus-within .nx .ic{transform:translateX(3px);color:var(--text-primary)} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..6be70d5 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client", "node"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "jsx": "preserve", + "noEmit": true, + "skipLibCheck": true, + "paths": { "@/*": ["./src/*"] } + }, + "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..998f087 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,29 @@ +import { fileURLToPath, URL } from "node:url"; +import vue from "@vitejs/plugin-vue"; +import { defineConfig } from "vite"; + +/** + * 开发:Vite 跑在 127.0.0.1:5173,把 /api 代理到后端 127.0.0.1:8009,网页版同源、cookie 直接可用。 + * 构建:资源基础路径默认 "/";桌面外壳(file:// 或自定义协议)构建时设 VITE_BASE=./, + * 同时在 public/config.js 里把 routerMode 改成 "hash"(架构 §5.2 第 6 条)。 + */ +export default defineConfig({ + base: process.env.VITE_BASE ?? "/", + plugins: [vue()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + // 设计规范是唯一的样式来源:tokens.css、components.css、logo 直接从 docs/design 引用,不复制 + "@design": fileURLToPath(new URL("../../docs/design", import.meta.url)), + }, + }, + server: { + host: "127.0.0.1", + port: 5173, + strictPort: true, + proxy: { + "/api": { target: "http://127.0.0.1:8009", changeOrigin: false }, + }, + }, + preview: { host: "127.0.0.1", port: 4173 }, +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..d9c6935 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "bid-doctor", + "private": true, + "packageManager": "pnpm@10.34.5", + "engines": { "node": ">=22" }, + "scripts": { + "dev": "pnpm --filter @bid-doctor/web dev", + "build": "pnpm -r build", + "typecheck": "pnpm -r typecheck", + "gen:api": "pnpm --filter @bid-doctor/api-client gen" + } +} diff --git a/packages/api-client/package.json b/packages/api-client/package.json new file mode 100644 index 0000000..104df07 --- /dev/null +++ b/packages/api-client/package.json @@ -0,0 +1,18 @@ +{ + "name": "@bid-doctor/api-client", + "private": true, + "version": "0.1.0", + "type": "module", + "main": "src/index.ts", + "scripts": { + "gen": "openapi-typescript ../../docs/architecture/openapi.yaml -o src/schema.d.ts", + "typecheck": "tsc --noEmit -p ." + }, + "dependencies": { + "openapi-fetch": "0.13.8" + }, + "devDependencies": { + "openapi-typescript": "7.8.0", + "typescript": "5.8.3" + } +} diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts new file mode 100644 index 0000000..dbaad00 --- /dev/null +++ b/packages/api-client/src/index.ts @@ -0,0 +1,146 @@ +/** + * 接口客户端:类型由 openapi.yaml 生成(pnpm gen:api),不手写。 + * + * 守住架构 §5.2 的约束: + * - 接口地址由调用方传入(网页版同源,桌面版用用户选择的服务器); + * - 网页版用 cookie,写操作自动带 X-CSRF-Token;桌面版用 Bearer 令牌; + * - 事件流用 fetch 读(API2),EventSource 带不了 Bearer 头。 + */ +import createClient, { type Middleware } from "openapi-fetch"; +import type { components, paths } from "./schema"; + +export type { components, paths }; +export type Schemas = components["schemas"]; +export type Problem = Schemas["Problem"]; +export type BdEvent = Schemas["Event"]; + +export interface ClientOptions { + /** 例如 "/api/v1" 或 "https://saas.example.com/api/v1" */ + baseUrl: string; + /** 桌面版:返回 Bearer 令牌;网页版不传,走 cookie */ + getToken?: () => string | null; +} + +const SAFE = new Set(["GET", "HEAD", "OPTIONS"]); + +function readCookie(name: string): string | null { + if (typeof document === "undefined") return null; + const m = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)); + return m?.[1] ? decodeURIComponent(m[1]) : null; +} + +/** 服务端返回的 problem+json,带稳定的 code(api-design §5)。前端按 code 判断,不解析文案。 */ +export class ApiError extends Error { + constructor(public readonly status: number, public readonly problem: Problem) { + super(problem.title); + } + get code(): string { + return this.problem.code; + } +} + +export function createApi(opts: ClientOptions) { + const auth: Middleware = { + onRequest({ request }) { + const token = opts.getToken?.(); + if (token) { + request.headers.set("Authorization", `Bearer ${token}`); + } else if (!SAFE.has(request.method)) { + const csrf = readCookie("bd_csrf"); + if (csrf) request.headers.set("X-CSRF-Token", csrf); + } + return request; + }, + }; + const client = createClient({ baseUrl: opts.baseUrl, credentials: "include" }); + client.use(auth); + return client; +} + +export type Api = ReturnType; + +/** 取出数据,失败时抛 ApiError。 */ +export async function unwrap(p: Promise<{ data?: T; error?: unknown; response: Response }>): Promise { + const { data, error, response } = await p; + if (error !== undefined || !response.ok) { + const problem = (error ?? { title: "请求失败", status: response.status, code: "internal_error" }) as Problem; + throw new ApiError(response.status, problem); + } + return data as T; +} + +/** 按分片上传一个文件(api-design §4.3),返回完成后的上传信息。 */ +export async function uploadFile( + api: Api, + opts: ClientOptions, + file: Blob & { name: string }, + purpose: "project_document" | "qualification_file", + onProgress?: (ratio: number) => void, +): Promise { + const up = await unwrap( + api.POST("/uploads", { body: { filename: file.name, size_bytes: file.size, purpose } }), + ); + for (let n = 1; n <= up.part_count; n++) { + if (up.received_parts.includes(n)) continue; + const part = file.slice((n - 1) * up.part_size, n * up.part_size); + const res = await fetch(`${opts.baseUrl}/uploads/${up.id}/parts/${n}`, { + method: "PUT", + body: part, + credentials: "include", + headers: authHeaders(opts, "PUT"), + }); + if (!res.ok) throw new ApiError(res.status, (await res.json()) as Problem); + onProgress?.(n / up.part_count); + } + return unwrap(api.POST("/uploads/{upload_id}/complete", { params: { path: { upload_id: up.id } } })); +} + +function authHeaders(opts: ClientOptions, method: string): Record { + const token = opts.getToken?.(); + if (token) return { Authorization: `Bearer ${token}` }; + const csrf = SAFE.has(method) ? null : readCookie("bd_csrf"); + return csrf ? { "X-CSRF-Token": csrf } : {}; +} + +/** + * 订阅事件流(SSE,用 fetch 读)。断线后带 Last-Event-ID 自动重连; + * 事件只是「有变化」的提示,收到后让相关查询重新请求即可(api-design §4.2)。 + * 返回取消函数。 + */ +export function subscribeEvents(opts: ClientOptions, onEvent: (e: BdEvent) => void): () => void { + const ctrl = new AbortController(); + let lastId = ""; + const loop = async () => { + while (!ctrl.signal.aborted) { + try { + const headers: Record = { Accept: "text/event-stream", ...authHeaders(opts, "GET") }; + if (lastId) headers["Last-Event-ID"] = lastId; + const res = await fetch(`${opts.baseUrl}/events`, { headers, credentials: "include", signal: ctrl.signal }); + if (!res.ok || !res.body) throw new Error(`events ${res.status}`); + const reader = res.body.pipeThrough(new TextDecoderStream()).getReader(); + let buf = ""; + for (;;) { + const { value, done } = await reader.read(); + if (done) break; + buf += value; + let i: number; + while ((i = buf.indexOf("\n\n")) >= 0) { + const block = buf.slice(0, i); + buf = buf.slice(i + 2); + let data = ""; + for (const line of block.split("\n")) { + if (line.startsWith("id: ")) lastId = line.slice(4); + else if (line.startsWith("data: ")) data += line.slice(6); + } + if (data) onEvent(JSON.parse(data) as BdEvent); + } + } + } catch { + if (ctrl.signal.aborted) return; + } + await new Promise((r) => setTimeout(r, 3000)); // 断线:3 秒后重连;页面上的轮询会兜底 + } + }; + void loop(); + return () => ctrl.abort(); +} diff --git a/packages/api-client/src/schema.d.ts b/packages/api-client/src/schema.d.ts new file mode 100644 index 0000000..b4a2f2b --- /dev/null +++ b/packages/api-client/src/schema.d.ts @@ -0,0 +1,4467 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/auth/sms-codes": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 发送短信验证码 */ + post: operations["sendSmsCode"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/register": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 手机号注册(同时创建企业主体和免费版订阅) */ + post: operations["register"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 手机号 + 验证码登录 + * @description 同一账号已有其他设备在线时返回 409 `concurrent_session`,附在线设备列表; + * 用户确认后带 `force: true` 重发,其他会话被挤下线(API3)。 + * + */ + post: operations["login"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/auth/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 退出登录(吊销当前会话) */ + post: operations["logout"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 当前用户、企业、套餐、权限点 */ + get: operations["getMe"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** 修改显示名 */ + patch: operations["updateMe"]; + trace?: never; + }; + "/me/sessions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 我的在线会话 */ + get: operations["listMySessions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/me/sessions/{session_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 让某个设备下线 */ + delete: operations["revokeMySession"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/tenant/close": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 注销账号(删除企业下全部业务数据,不可恢复) + * @description data-model §6.2。需要短信验证码和企业名称两次确认。成功后当前会话失效。 + */ + post: operations["closeTenant"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/plans": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 套餐列表(含额度、文件大小和页数上限,购买页和上传页展示用) */ + get: operations["listPlans"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/quota": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 额度总览:本期已用、剩余、按功能分布 */ + get: operations["getQuota"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/quota/entries": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 额度流水 */ + get: operations["listQuotaEntries"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/activation-requests": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 我的专业版开通申请 */ + get: operations["listActivationRequests"]; + put?: never; + /** 提交专业版开通申请(之后对公转账,运营确认到账后开通,G8) */ + post: operations["createActivationRequest"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 工作台项目列表(FR-M6-01) */ + get: operations["listProjects"]; + put?: never; + /** 新建项目 */ + post: operations["createProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 项目概览 */ + get: operations["getProject"]; + put?: never; + post?: never; + /** 删除项目(彻底删除业务内容,不可恢复;额度流水保留,G10) */ + delete: operations["deleteProject"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/fields/{field_key}": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + field_key: components["schemas"]["ProjectFieldKey"]; + }; + cookie?: never; + }; + get?: never; + /** + * 人工修改项目基本信息字段(FR-M2-03) + * @description `value` 为 null 表示撤销人工值,恢复为 AI 提取值。 + */ + put: operations["setProjectField"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/fields/{field_key}/pending": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + field_key: components["schemas"]["ProjectFieldKey"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 确认或驳回补遗带来的新值(FR-M2-10) */ + post: operations["resolvePendingField"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/transitions": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 手动变更项目状态(递交 / 撤销递交 / 归档 / 撤销归档,FR-M6-02) + * @description 致命问题未处理也可以递交(红线 4);响应里带当时的就绪状态,由前端提示。 + */ + post: operations["transitionProject"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/lots": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 分包列表 */ + get: operations["listLots"]; + put?: never; + /** 手工增加分包(FR-M2-04) */ + post: operations["createLot"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/bidding-lots": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + get?: never; + /** 选择要投的包 */ + put: operations["setBiddingLots"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/lots/{lot_id}": { + parameters: { + query?: never; + header?: never; + path: { + lot_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** 删除分包(原属这个包的清单项变为各包通用) */ + delete: operations["deleteLot"]; + options?: never; + head?: never; + /** 修改分包 */ + patch: operations["updateLot"]; + trace?: never; + }; + "/uploads": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 开始一次分片上传(架构 §5.2 第 7 条) + * @description 先按套餐校验文件大小;超出上限直接返回 409,不建上传(FR-M2-01)。 + */ + post: operations["createUpload"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/uploads/{upload_id}": { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + }; + cookie?: never; + }; + /** 查询已收到的分片(断点续传用) */ + get: operations["getUpload"]; + put?: never; + post?: never; + /** 放弃上传 */ + delete: operations["abortUpload"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/uploads/{upload_id}/parts/{part_no}": { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + part_no: number; + }; + cookie?: never; + }; + get?: never; + /** 上传一个分片(重复上传同一分片会覆盖) */ + put: operations["putUploadPart"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/uploads/{upload_id}/complete": { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 完成上传:合并分片、按文件头识别格式、读页数 + * @description 格式、加密、损坏、页数超限都在这里拦截,不建任务、不扣点(FR-M2-01)。 + * 识别出电子投标平台的加密格式时返回 `bid_file_platform_encrypted`(FR-M3-01)。 + * + */ + post: operations["completeUpload"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/documents": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 项目的文档(招标文件、补遗、澄清、投标分册的各个版本) */ + get: operations["listDocuments"]; + put?: never; + /** 把上传好的文件挂到项目上(不扣点;招标侧文件要另外发起解析) */ + post: operations["createDocument"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documents/{document_id}": { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + /** 文档详情 */ + get: operations["getDocument"]; + put?: never; + post?: never; + /** + * 删除文档 + * @description 已被清单项或检查批次引用的文档不能删除,返回 `document_in_use`。 + */ + delete: operations["deleteDocument"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documents/{document_id}/file": { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + /** 取文件的短时下载地址(pdf.js 直接加载,不需要带鉴权头) */ + get: operations["getDocumentFileUrl"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documents/{document_id}/pages": { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + /** 逐页摘要(阅读器的页尺寸、OCR 标记) */ + get: operations["listDocumentPages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documents/{document_id}/pages/{page_no}/sentences": { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + page_no: number; + }; + cookie?: never; + }; + /** 一页的句子(阅读器里选句补出处用;数据来自对象存储的文字层,DM1) */ + get: operations["listPageSentences"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/citations": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 人工建一条出处(在阅读器里选中句子) */ + post: operations["createCitation"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documents/{document_id}/parse-jobs": { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 发起解析(预扣 1 次解析;失败、超时、取消都退回) + * @description 只用于招标文件、补遗、澄清;投标分册的解析算在检查里(01 §4)。 + * 失败后重试也调用这个接口,会建一条新任务并重新预扣,上一条已经退回(FR-M2-02)。 + * + */ + post: operations["startParseJob"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/parse-jobs/{job_id}": { + parameters: { + query?: never; + header?: never; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + /** 解析进度(事件流断线时的轮询兜底) */ + get: operations["getParseJob"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/parse-jobs/{job_id}/cancel": { + parameters: { + query?: never; + header?: never; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 取消解析(退回额度) */ + post: operations["cancelParseJob"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/checklist": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 清单概况:状态、版本、各类数量、标注规则、数量核对提示 */ + get: operations["getChecklist"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/checklist/items": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 清单项(一个项目几百条,一次返回全部,不分页) */ + get: operations["listChecklistItems"]; + put?: never; + /** 人工新增清单项(标注「人工添加」,可以没有出处) */ + post: operations["createChecklistItem"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/checklist-items/{item_id}": { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + /** 清单项详情 */ + get: operations["getChecklistItem"]; + put?: never; + post?: never; + /** 删除(软删除,可以恢复;待确认区的条目就是「丢弃」) */ + delete: operations["deleteChecklistItem"]; + options?: never; + head?: never; + /** 修改清单项(改过的字段记入 edited_fields;已确认的清单回到草稿) */ + patch: operations["updateChecklistItem"]; + trace?: never; + }; + "/checklist-items/{item_id}/restore": { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 恢复已删除的条目 */ + post: operations["restoreChecklistItem"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/checklist-items/{item_id}/accept": { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 从待确认区采纳进正式清单(FR-M2-07) + * @description 必须补上出处,或者声明「人工确认、无出处」;可以同时修改字段(修改后采纳)。 + */ + post: operations["acceptPendingItem"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/checklist-items/{item_id}/citations": { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + get?: never; + /** 替换清单项的出处列表(按顺序) */ + put: operations["setItemCitations"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/checklist-items/{item_id}/supersession": { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 确认或驳回「已被补遗 N 更新」(FR-M2-10) + * @description 接受时旧条目软删除,新条目保留;驳回时解除关联,两条都保留,由用户自己处理。 + */ + post: operations["resolveSupersession"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/checklist-items/{item_id}/history": { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + /** 修改记录(谁、什么时候、改前、改后;来自审计事件) */ + get: operations["getItemHistory"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/checklist/confirm": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 确认清单(生成不可变版本) */ + post: operations["confirmChecklist"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/checklist/versions": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 清单版本 */ + get: operations["listChecklistVersions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/check-preview": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** + * 发起检查前的预览:这次扣不扣点、用哪些文件、有什么提示 + * @description 「开始检查」按钮旁的扣点说明来自这里(红线 9)。 + */ + get: operations["getCheckPreview"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/check-runs": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 检查记录 */ + get: operations["listCheckRuns"]; + put?: never; + /** 发起检查(FR-M3-02) */ + post: operations["startCheckRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/check-runs/{run_id}": { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + /** 检查批次(进度、覆盖范围、各级别数量) */ + get: operations["getCheckRun"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/check-runs/{run_id}/cancel": { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 取消检查(退回额度) */ + post: operations["cancelCheckRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/check-runs/{run_id}/findings": { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + /** 本批次的全部问题(含「本次已解决」,一次返回全部) */ + get: operations["listFindings"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/check-runs/{run_id}/findings/bulk-resolve": { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 批量处理(只限「提示」级,FR-M3-07) */ + post: operations["bulkResolveFindings"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/findings/{finding_id}/resolve": { + parameters: { + query?: never; + header?: never; + path: { + finding_id: components["parameters"]["FindingId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 处理一个问题 + * @description 忽略必须写原因;致命问题选「确认无误」也必须写说明,说明同时进入误报反馈(U4)。 + * 只有最近一次检查的问题可以处理,历史批次只读。 + * + */ + post: operations["resolveFinding"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/findings/{finding_id}/reopen": { + parameters: { + query?: never; + header?: never; + path: { + finding_id: components["parameters"]["FindingId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 撤销处理,回到待处理(U 键) */ + post: operations["reopenFinding"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/manual-checks": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 需人工核对的条目和勾选结果(按项目存,P2) */ + get: operations["listManualChecks"]; + /** 勾选或取消勾选一条 */ + put: operations["setManualCheck"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/check-runs/{run_id}/reports": { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** + * 生成检查报告 PDF(后台生成,完成后推送事件) + * @description 致命问题未处理也可以导出(红线 4)。每次生成都是一份新报告。 + */ + post: operations["generateReport"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/report-jobs/{job_id}": { + parameters: { + query?: never; + header?: never; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + /** 报告生成进度(轮询兜底) */ + get: operations["getReportJob"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/projects/{project_id}/reports": { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + /** 项目的报告(随项目归档,可以重复下载) */ + get: operations["listReports"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/reports/{report_id}/file": { + parameters: { + query?: never; + header?: never; + path: { + report_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + /** 报告的短时下载地址 */ + get: operations["getReportFileUrl"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/qualifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 资质库 */ + get: operations["listQualifications"]; + put?: never; + /** 新增资质 */ + post: operations["createQualification"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/qualifications/{qualification_id}": { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + }; + cookie?: never; + }; + get?: never; + /** 修改资质 */ + put: operations["updateQualification"]; + post?: never; + /** 删除资质(连同附件) */ + delete: operations["deleteQualification"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/qualifications/{qualification_id}/files": { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + }; + cookie?: never; + }; + get?: never; + put?: never; + /** 挂上一个已上传的附件 */ + post: operations["attachQualificationFile"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/qualifications/{qualification_id}/files/{file_id}": { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + file_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + /** 附件的短时下载地址 */ + get: operations["getQualificationFileUrl"]; + put?: never; + post?: never; + /** 删除附件 */ + delete: operations["deleteQualificationFile"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/notifications": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** 站内通知 */ + get: operations["listNotifications"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/notifications/read": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 标为已读(不传编号就是全部已读) */ + post: operations["markNotificationsRead"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * 事件流(SSE) + * @description 当前用户在当前企业下的事件。前端用 fetch 读流(可以带 Bearer 头),不用浏览器的 EventSource(API2)。 + * 每条事件带 `id`;断线重连时带 `Last-Event-ID`,服务端尽量补发,补不了时发 `resync`,前端重新拉取数据。 + * 事件只是「有变化」的提示,权威状态以对应资源的 GET 为准。 + * + */ + get: operations["streamEvents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/feedback": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** 反馈判断有误(误报 / 漏报,J5) */ + post: operations["createFeedback"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** Format: uuid */ + Id: string; + /** + * Format: date-time + * @description RFC 3339,带时区 + */ + Timestamp: string; + /** @description 金额用字符串传,避免浮点误差;单位元 */ + Money: string; + /** @description 中国大陆手机号,11 位 */ + Phone: string; + Page: { + /** @description 为 null 表示没有下一页 */ + next_cursor: string | null; + }; + /** @description RFC 9457 problem details,`code` 是给前端判断用的稳定编码(api-design §5) */ + Problem: { + /** @description 固定为 about:blank 或错误码文档地址 */ + type: string; + /** @description 给用户看的一句话(中文) */ + title: string; + status: number; + code: string; + detail?: string; + request_id: string; + errors?: { + /** @description 字段路径,如 spec.tech_param.value */ + field: string; + code: string; + message: string; + }[]; + } & { + [key: string]: unknown; + }; + DownloadUrl: { + /** Format: uri */ + url: string; + expires_at: components["schemas"]["Timestamp"]; + sha256?: string; + }; + /** @description 操作人;人离职或注销后仍显示当时的姓名 */ + UserRef: { + id: components["schemas"]["Id"]; + name: string; + }; + /** @enum {string} */ + ProjectStatus: "tracking" | "drafting" | "checked" | "submitted" | "archived"; + /** @enum {string} */ + LegalRegime: "gov_procurement" | "tendering" | "unknown"; + /** @enum {string} */ + DocumentRole: "tender" | "addendum" | "clarification" | "bid_volume"; + /** @enum {string} */ + DetectedFormat: "pdf" | "docx" | "doc" | "wps" | "ofd" | "encrypted_bid" | "unknown"; + /** @enum {string} */ + JobStatus: "queued" | "running" | "succeeded" | "failed" | "cancelled"; + /** @enum {string} */ + Category: "qualification" | "invalidity" | "scoring" | "schedule" | "tech_param" | "format_doc" | "price"; + /** + * @description 界面显示为 ★ / ▲ / 一般 + * @enum {string} + */ + Importance: "substantive" | "important" | "normal"; + /** @enum {string} */ + Zone: "pending" | "formal"; + /** @enum {string} */ + PendingReason: "no_citation" | "low_confidence" | "verify_failed" | "marker_uncovered" | "absent_in_rerun"; + /** @enum {string} */ + Origin: "ai" | "rule" | "human"; + /** @enum {string} */ + FindingType: "unresponded" | "negative_deviation" | "suspected_unmet" | "formality" | "qualification" | "price_form" | "manual_review"; + /** @enum {string} */ + Severity: "fatal" | "important" | "hint"; + /** @enum {string} */ + FindingStatus: "pending" | "confirmed_ok" | "modified" | "ignored"; + /** + * @description 新增 / 沿用 / 本次已解决 + * @enum {string} + */ + Presence: "new" | "carried" | "resolved"; + /** @enum {string} */ + BillingMode: "charged" | "waived_recheck" | "free_plan"; + /** @enum {string} */ + QuotaUnit: "parse" | "check" | "generate" | "dedupe"; + /** @enum {string} */ + QuotaEntryKind: "grant" | "hold" | "settle" | "release" | "expire" | "adjust" | "waive"; + /** @enum {string} */ + PlanCode: "free" | "pro" | "enterprise"; + /** @enum {string} */ + QualificationKind: "certificate" | "system_cert" | "software_copyright" | "other"; + /** @enum {string} */ + ProjectFieldKey: "name" | "code" | "purchaser" | "agency" | "procurement_method" | "legal_regime" | "budget_total" | "ceiling_total" | "eval_method" | "accept_consortium" | "accept_imported" | "sme_only" | "deadline_at"; + /** + * @description 工作台「下一步」,由服务端按项目状态算出 + * @enum {string} + */ + NextAction: "upload_tender" | "wait_parse" | "select_lots" | "confirm_checklist" | "upload_bid" | "run_check" | "handle_fatal" | "export_report" | "archive" | "none"; + /** + * @description 权限点(AR12)。MVP 的所有者拥有全部;前端只用来决定按钮显不显示,服务端仍会检查 + * @enum {string} + */ + Permission: "project.create" | "project.edit" | "project.delete" | "checklist.edit" | "checklist.confirm" | "check.run" | "finding.resolve" | "report.export" | "quota.view" | "billing.manage" | "qualification.edit" | "tenant.close"; + RegisterRequest: { + phone: components["schemas"]["Phone"]; + sms_code: string; + /** @description 企业名称(MVP 不做认证,G9) */ + tenant_name: string; + display_name: string; + /** @description 同意的服务协议版本(含围串标禁止条款) */ + terms_version: string; + /** @enum {string} */ + client_kind: "web" | "desktop"; + }; + LoginRequest: { + phone: components["schemas"]["Phone"]; + sms_code: string; + /** @enum {string} */ + client_kind: "web" | "desktop"; + /** + * @description 已有其他设备在线时,确认挤下线后重发 + * @default false + */ + force: boolean; + }; + LoginResult: { + me: components["schemas"]["Me"]; + /** @description 只有 client_kind = desktop 时返回,且只返回这一次 */ + token?: string; + expires_at?: components["schemas"]["Timestamp"]; + }; + SessionInfo: { + id: components["schemas"]["Id"]; + /** @enum {string} */ + client_kind: "web" | "desktop"; + /** @description 从 User-Agent 归纳,如「Chrome · macOS」 */ + device?: string; + /** @description IP 归属地,如「江苏南京」;不返回完整 IP */ + ip_region?: string; + created_at: components["schemas"]["Timestamp"]; + last_seen_at: components["schemas"]["Timestamp"]; + current: boolean; + }; + Me: { + user: { + id: components["schemas"]["Id"]; + display_name: string; + phone_hint: string; + }; + tenant: { + id: components["schemas"]["Id"]; + name: string; + }; + /** @enum {string} */ + role: "owner" | "admin" | "member"; + plan: components["schemas"]["PlanSummary"]; + permissions: components["schemas"]["Permission"][]; + }; + PlanSummary: { + code: components["schemas"]["PlanCode"]; + name: string; + starts_at?: components["schemas"]["Timestamp"]; + ends_at?: components["schemas"]["Timestamp"] | null; + /** @description 套餐到期后数据只读(FR-M6-05) */ + read_only: boolean; + }; + Plan: { + code: components["schemas"]["PlanCode"]; + version: number; + name: string; + /** @description 每个周期的额度;专业版的检查从解析额度扣 */ + quotas: { + [key: string]: number; + }; + limits: { + max_file_bytes?: number; + max_pages?: number; + max_projects?: number | null; + }; + /** @description 给用户看的检查计费说明,如「检查从解析额度扣 1 次;同一项目 24 小时内复查不扣」 */ + check_rule?: string; + }; + QuotaOverview: { + plan: components["schemas"]["PlanSummary"]; + accounts: { + unit: components["schemas"]["QuotaUnit"]; + /** @description 剩余,含正在预扣的 */ + balance: number; + /** @description 正在预扣、还没结算的 */ + held?: number; + period_granted: number; + period_used: number; + /** @description 0~1;≥ 0.8 时界面变赭色(Q9) */ + used_ratio?: number; + /** @description 按功能分布,如解析、检查 */ + by_feature?: { + /** @enum {string} */ + feature: "parse" | "check"; + used: number; + }[]; + }[]; + }; + QuotaEntry: { + id: components["schemas"]["Id"]; + unit: components["schemas"]["QuotaUnit"]; + kind: components["schemas"]["QuotaEntryKind"]; + /** @description 对余额的影响;结算、免扣为 0 */ + amount: number; + /** @description 调整原因;免扣时为规则说明,如「24 小时内复查」 */ + reason?: string; + project?: { + id?: components["schemas"]["Id"]; + /** @description 项目已删除时为 null,界面显示「(已删除的项目)」 */ + name?: string | null; + deleted?: boolean; + } | null; + ref?: { + /** @enum {string} */ + type?: "parse_job" | "check_run"; + id?: components["schemas"]["Id"]; + /** @description 如「招标文件解析」「第 3 次检查」 */ + label?: string | null; + } | null; + created_at: components["schemas"]["Timestamp"]; + }; + ActivationRequestCreate: { + /** @enum {string} */ + plan_code: "pro"; + invoice_info: components["schemas"]["InvoiceInfo"]; + }; + InvoiceInfo: { + /** @description 发票抬头 */ + title: string; + /** @description 纳税人识别号 */ + tax_no: string; + address_phone?: string; + bank_account?: string; + /** @enum {string} */ + invoice_type?: "general" | "special"; + contact_name?: string; + contact_phone?: string; + }; + ActivationRequest: { + id: components["schemas"]["Id"]; + plan_code: components["schemas"]["PlanCode"]; + /** @enum {string} */ + status: "submitted" | "paid" | "activated" | "rejected" | "cancelled"; + invoice_info?: components["schemas"]["InvoiceInfo"]; + /** @description 对公转账账户和备注要求 */ + payment_instructions?: string; + ops_note?: string; + created_at: components["schemas"]["Timestamp"]; + }; + /** @description 就绪状态只在 status = checked 时有意义(G7);未就绪也不阻止导出和递交(红线 4) */ + ProjectReadiness: { + status: components["schemas"]["ProjectStatus"]; + open_fatal_count: number; + /** @description 不是 checked 状态时为 null */ + ready: boolean | null; + }; + ProjectListItem: { + id: components["schemas"]["Id"]; + name: string; + code?: string | null; + purchaser?: string | null; + deadline_at?: components["schemas"]["Timestamp"] | null; + status: components["schemas"]["ProjectStatus"]; + readiness: components["schemas"]["ProjectReadiness"]; + bidding_lots: string[]; + /** @description 待确认区条目数 */ + pending_item_count?: number; + /** @description 正在跑的解析或检查 */ + active_job?: { + /** @enum {string} */ + kind?: "parse_job" | "check_run"; + id?: components["schemas"]["Id"]; + progress?: number; + label?: string; + } | null; + next_action: components["schemas"]["NextAction"]; + updated_at: components["schemas"]["Timestamp"]; + }; + ProjectDetail: components["schemas"]["ProjectListItem"] & { + legal_regime: components["schemas"]["LegalRegime"]; + agency?: string | null; + procurement_method?: string | null; + /** @description 基本信息字段(FR-M2-03),每个都带出处 */ + fields: components["schemas"]["ProjectField"][]; + /** @description 关键日期时间线(FR-M2-09),来自「日期与递交」类清单项 */ + key_dates: { + item_id: components["schemas"]["Id"]; + label: string; + at: components["schemas"]["Timestamp"]; + /** @description 补遗带来了新值、等用户确认 */ + superseded_pending?: boolean; + }[]; + latest_check_run_id?: components["schemas"]["Id"] | null; + members?: { + user?: components["schemas"]["UserRef"]; + /** @enum {string} */ + role?: "lead" | "editor" | "viewer"; + }[]; + created_by: components["schemas"]["UserRef"]; + created_at: components["schemas"]["Timestamp"]; + }; + /** @description 按字段:文本、布尔、金额(Money 字符串)或时间 */ + FieldValue: string | boolean | null; + ProjectField: { + key: components["schemas"]["ProjectFieldKey"]; + value: components["schemas"]["FieldValue"]; + /** @enum {string} */ + source: "ai" | "human" | "none"; + ai_value?: components["schemas"]["FieldValue"]; + citation?: components["schemas"]["Citation"] | null; + updated_by?: components["schemas"]["UserRef"] | null; + /** @description 补遗带来的新值(FR-M2-10) */ + pending?: { + value?: components["schemas"]["FieldValue"]; + document_label?: string; + citation?: components["schemas"]["Citation"]; + } | null; + }; + LotInput: { + lot_no?: string; + name?: string; + budget_amount?: components["schemas"]["Money"] | null; + ceiling_amount?: components["schemas"]["Money"] | null; + }; + Lot: { + id: components["schemas"]["Id"]; + lot_no: string; + name?: string | null; + budget_amount?: components["schemas"]["Money"] | null; + ceiling_amount?: components["schemas"]["Money"] | null; + /** @description 没有分包时自动建的默认包,界面跳过选包 */ + is_default: boolean; + is_bidding: boolean; + origin: components["schemas"]["Origin"]; + edited_fields: string[]; + }; + Upload: { + id: components["schemas"]["Id"]; + /** @description 字节,最后一片可以更小 */ + part_size: number; + part_count: number; + received_parts: number[]; + expires_at: components["schemas"]["Timestamp"]; + }; + UploadCompleted: { + id: components["schemas"]["Id"]; + filename: string; + size_bytes: number; + sha256: string; + detected_format: components["schemas"]["DetectedFormat"]; + /** @description 扩展名和真实格式不一致(07 F2),界面只做提示 */ + extension_mismatch?: boolean; + /** @description PDF 能直接读出;Word 要转换后才知道,这里为 null */ + page_count?: number | null; + }; + DocumentCreate: { + upload_id: components["schemas"]["Id"]; + role: components["schemas"]["DocumentRole"]; + /** @description 投标分册名:商务 / 技术 / 报价 / 资格证明 */ + volume_name?: string; + /** @description 投标分册重新上传时填上一版的编号,生成新版本(FR-M3-01) */ + replaces_document_id?: components["schemas"]["Id"]; + }; + Document: { + id: components["schemas"]["Id"]; + role: components["schemas"]["DocumentRole"]; + /** @description 如「招标文件」「补遗 1」「技术分册 v2」 */ + label: string; + seq_no?: number; + volume_name?: string | null; + lineage_id?: components["schemas"]["Id"]; + is_latest_version?: boolean; + original_filename: string; + detected_format: components["schemas"]["DetectedFormat"]; + size_bytes: number; + page_count?: number | null; + normalized_sha256?: string | null; + /** @enum {string} */ + status: "uploaded" | "normalizing" | "ready" | "failed"; + latest_parse_job?: components["schemas"]["ParseJob"] | null; + uploaded_by?: components["schemas"]["UserRef"]; + created_at: components["schemas"]["Timestamp"]; + }; + DocumentPage: { + page_no: number; + width_pt: number; + height_pt: number; + /** @enum {integer} */ + rotation: 0 | 90 | 180 | 270; + /** @enum {string} */ + text_source: "text" | "ocr" | "mixed" | "empty"; + }; + /** @description 一个矩形 [x0, y0, x1, y1],单位 pt,原点在页面左上角 */ + Quad: number[]; + Sentence: { + key: string; + text: string; + quads: components["schemas"]["Quad"][]; + /** @enum {string} */ + location: "body" | "table" | "header_footer"; + /** @description 文字格式片段 [起, 止, 格式],格式取 b / i / u 的组合 */ + format_runs?: [ + number, + number, + string + ][]; + /** @description 所在章节,如「第三章 采购需求 > 3.2 技术参数」 */ + section_path?: string; + }; + CitationCreate: { + document_id: components["schemas"]["Id"]; + /** @description 同一页上连续的句子 */ + sentence_keys: string[]; + char_start?: number; + char_end?: number; + }; + /** @description 出处,不可变(data-model §5.1) */ + Citation: { + id: components["schemas"]["Id"]; + document_id: components["schemas"]["Id"]; + document_label: string; + /** @description 和文档当前哈希不一致时前端不画高亮 */ + normalized_sha256: string; + page_no: number; + sentence_keys: string[]; + char_start?: number | null; + char_end?: number | null; + /** @description 原句 */ + quote: string; + quads: components["schemas"]["Quad"][]; + created_by_kind: components["schemas"]["Origin"]; + }; + /** @description 给用户看的失败原因;不含文档内容 */ + JobError: { + code: string; + message: string; + /** @description 额度是否已退回 */ + refunded: boolean; + }; + ParseJob: { + id: components["schemas"]["Id"]; + document_id: components["schemas"]["Id"]; + attempt_no: number; + status: components["schemas"]["JobStatus"]; + /** @enum {string|null} */ + stage?: "detect" | "convert" | "text_layer" | "ocr" | "structure" | "extract" | "verify" | "merge" | null; + progress: number; + cancel_requested?: boolean; + error?: components["schemas"]["JobError"] | null; + /** @description 成功后的摘要 */ + result?: { + items_added?: number; + items_updated?: number; + items_pending?: number; + /** @description 重新解析时没再出现的旧条目 */ + items_absent?: number; + lots_found?: number; + } | null; + created_at: components["schemas"]["Timestamp"]; + finished_at?: components["schemas"]["Timestamp"] | null; + }; + MarkingRule: { + id: components["schemas"]["Id"]; + /** @enum {string} */ + marker: "star" | "asterisk" | "triangle" | "italic_underline" | "bold" | "underline" | "keyword"; + marker_text?: string | null; + /** @enum {string} */ + meaning: "substantive" | "important"; + declared_count?: number | null; + /** @description 实际抽到的条数 */ + found_count?: number; + citation: components["schemas"]["Citation"]; + }; + Checklist: { + /** @enum {string} */ + status: "draft" | "confirmed"; + /** @description 最近一次确认的版本号;没确认过为 0 */ + version_no: number; + confirmed_by?: components["schemas"]["UserRef"] | null; + confirmed_at?: components["schemas"]["Timestamp"] | null; + counts: { + formal: number; + pending: number; + deleted: number; + superseded_pending?: number; + by_category?: { + [key: string]: number; + }; + }; + marking_rules: components["schemas"]["MarkingRule"][]; + /** @description 如「文件写明 ★ 项共 14 条,实际抽到 13 条」 */ + warnings?: { + /** @enum {string} */ + code: "declared_count_mismatch" | "marker_rule_not_found" | "legal_regime_unknown"; + message: string; + }[]; + }; + /** @description 类别特有字段(05 §3.3),按类别分组;一条清单项有几个类别就可以有几组 */ + ItemSpec: { + qualification?: { + /** @enum {string} */ + kind?: "general" | "specific" | "credit"; + validity_requirement?: string; + }; + invalidity?: { + /** @enum {string} */ + source?: "statute" | "tender_text" | "substantive_marker"; + }; + scoring?: { + factor?: string; + /** @description 分值,可能是「0~5」这样的区间,所以用字符串 */ + points?: string; + criteria?: string; + /** @enum {string} */ + part?: "price" | "technical" | "commercial"; + }; + schedule?: { + /** @enum {string} */ + date_kind?: "question_deadline" | "bid_deadline" | "opening" | "deposit_deadline" | "other"; + at?: components["schemas"]["Timestamp"]; + deposit_amount?: components["schemas"]["Money"]; + /** @enum {string} */ + submission_mode?: "electronic" | "paper" | "ca_encrypted" | "mixed"; + copies?: string; + }; + tech_param?: { + param?: string; + /** @enum {string} */ + op?: ">=" | "<=" | "=" | "contains" | "supports"; + value?: string; + unit?: string; + device?: string; + }; + format_doc?: { + doc_name?: string; + modifiable?: boolean; + }; + /** @description 分项限价、单价上限(DM7);只存招标文件写明的限价,系统不给任何报价建议(红线 6) */ + price?: { + /** @enum {string} */ + kind?: "item_ceiling" | "unit_price_ceiling"; + target?: string; + /** @enum {string} */ + op?: "<="; + value?: components["schemas"]["Money"]; + }; + }; + ChecklistItemFields: { + categories?: components["schemas"]["Category"][]; + title?: string; + requirement_text?: string | null; + importance?: components["schemas"]["Importance"]; + lot_id?: components["schemas"]["Id"] | null; + evidence_required?: string | null; + spec?: components["schemas"]["ItemSpec"]; + }; + ChecklistItemCreate: components["schemas"]["ChecklistItemFields"] & { + categories: components["schemas"]["Category"][]; + title: string; + citation_ids?: components["schemas"]["Id"][]; + }; + ChecklistItemUpdate: components["schemas"]["ChecklistItemFields"]; + ChecklistItem: components["schemas"]["ChecklistItemFields"] & { + categories: components["schemas"]["Category"][]; + title: string; + importance: components["schemas"]["Importance"]; + id: components["schemas"]["Id"]; + /** @description AI 摘要,界面标注 AI 生成 */ + summary?: string | null; + /** @enum {string|null} */ + importance_basis?: "marker" | "statute" | "tender_text" | "human" | null; + marking_rule_id?: components["schemas"]["Id"] | null; + /** @description 空表示各包通用 */ + lot_no?: string | null; + origin: components["schemas"]["Origin"]; + /** @description 如「AI」「人工添加」「补遗 1」 */ + origin_label?: string; + zone: components["schemas"]["Zone"]; + pending_reason?: components["schemas"]["PendingReason"] | null; + /** @description 人工确认、无出处 */ + no_citation_ack?: boolean; + citations: components["schemas"]["Citation"][]; + /** @description AI 原值;和当前值不同的字段界面可以显示「AI 原为……」 */ + ai_values?: Record | null; + edited_fields: string[]; + confidence?: number | null; + /** @description 已被补遗更新,等用户确认 */ + superseded_by?: { + item_id?: components["schemas"]["Id"]; + document_label?: string; + confirmed?: boolean; + } | null; + accepted_by?: components["schemas"]["UserRef"] | null; + deleted_at?: components["schemas"]["Timestamp"] | null; + updated_at: components["schemas"]["Timestamp"]; + }; + ChecklistVersion: { + id: components["schemas"]["Id"]; + version_no: number; + /** @enum {string} */ + kind: "confirmed" | "check"; + item_count: number; + created_by: components["schemas"]["UserRef"]; + created_at: components["schemas"]["Timestamp"]; + }; + HistoryEntry: { + action: string; + actor: components["schemas"]["UserRef"]; + at: components["schemas"]["Timestamp"]; + changes?: { + field: string; + before?: unknown; + after?: unknown; + }[]; + }; + CheckPreview: { + can_start: boolean; + /** @description can_start 为 false 的原因 */ + blockers?: ("no_parsed_tender" | "no_bid_documents" | "job_already_running" | "quota_exhausted" | "project_read_only")[]; + billing: components["schemas"]["BillingMode"]; + /** + * @description 这次检查扣几次 + * @enum {integer} + */ + charge: 0 | 1; + charge_unit?: components["schemas"]["QuotaUnit"]; + /** @description 24 小时免扣窗口的结束时间(从上一次扣点的检查算起,P1) */ + waive_until?: components["schemas"]["Timestamp"] | null; + /** @description 为 false 时提示「清单未确认,检查结果以当前清单为准」 */ + checklist_confirmed: boolean; + /** @description 待确认区条目不参与检查 */ + pending_item_count?: number; + /** @description 默认用每个分册的最新版本 */ + bid_documents: components["schemas"]["Document"][]; + lot_ids: components["schemas"]["Id"][]; + }; + CheckRunCreate: { + /** @description 本次检查的投标文件版本 */ + document_ids: components["schemas"]["Id"][]; + /** + * @description 前端按 check-preview 提示用户的扣点数;和服务端当下算的不一致时返回 `billing_changed`,不扣点(API4) + * @enum {integer} + */ + expected_charge: 0 | 1; + }; + CheckRun: { + id: components["schemas"]["Id"]; + run_no: number; + status: components["schemas"]["JobStatus"]; + /** @enum {string|null} */ + stage?: "prepare_bid" | "match" | "rules" | "verify" | "carry_over" | "summarize" | null; + progress: number; + cancel_requested?: boolean; + error?: components["schemas"]["JobError"] | null; + billing: components["schemas"]["BillingMode"]; + /** @description 只有最近一次检查的问题可以处理 */ + is_latest?: boolean; + checklist_version_no: number; + checklist_confirmed: boolean; + rule_set_version: string; + legal_regime?: components["schemas"]["LegalRegime"]; + documents?: components["schemas"]["Document"][]; + /** @description 查了什么、没查什么(报告首页,FR-M3-09) */ + coverage?: { + checked?: string[]; + not_checked?: string[]; + } | null; + counts?: { + fatal?: number; + important?: number; + hint?: number; + open_fatal?: number; + resolved_this_run?: number; + new?: number; + } | null; + created_by: components["schemas"]["UserRef"]; + created_at: components["schemas"]["Timestamp"]; + finished_at?: components["schemas"]["Timestamp"] | null; + }; + Finding: { + id: components["schemas"]["Id"]; + type: components["schemas"]["FindingType"]; + severity: components["schemas"]["Severity"]; + title: string; + explanation: string; + /** @enum {string} */ + detector: "ai" | "rule"; + /** @description AI 判断的问题必有;规则问题没有对应条目时为 null,显示 rule(DM6) */ + checklist_item?: { + id?: components["schemas"]["Id"]; + title?: string; + importance?: components["schemas"]["Importance"]; + categories?: components["schemas"]["Category"][]; + } | null; + rule?: { + code?: string; + description?: string; + } | null; + /** @description 按类型的补充信息:参数的要求值和响应值、资质预警种类(expired / insufficient_level / not_covered)、从投标文件读到的金额 */ + detail?: Record; + tender_citations: components["schemas"]["Citation"][]; + bid_citations: components["schemas"]["Citation"][]; + /** @description 「未找到响应」时找过哪些分册、哪些章节(FR-M3-03) */ + search_scope?: { + documents?: string[]; + sections?: string[]; + } | null; + presence: components["schemas"]["Presence"]; + /** @description 沿用时,上一次的处理状态(上次「已修改」的这次回到待处理) */ + carried_status?: components["schemas"]["FindingStatus"] | null; + status: components["schemas"]["FindingStatus"]; + resolution_note?: string | null; + resolved_by?: components["schemas"]["UserRef"] | null; + resolved_at?: components["schemas"]["Timestamp"] | null; + }; + ManualCheck: { + check_key: string; + label: string; + requirement_text?: string | null; + citation?: components["schemas"]["Citation"] | null; + checked: boolean; + note?: string | null; + checked_by?: components["schemas"]["UserRef"] | null; + checked_at?: components["schemas"]["Timestamp"] | null; + }; + ReportJob: { + id: components["schemas"]["Id"]; + check_run_id: components["schemas"]["Id"]; + status: components["schemas"]["JobStatus"]; + report_id?: components["schemas"]["Id"] | null; + error?: components["schemas"]["JobError"] | null; + }; + Report: { + id: components["schemas"]["Id"]; + check_run_id: components["schemas"]["Id"]; + run_no: number; + page_count: number; + sha256?: string; + template_version: string; + /** @description 导出时还没处理的致命问题数 */ + open_fatal_at_export?: number; + generated_by: components["schemas"]["UserRef"]; + created_at: components["schemas"]["Timestamp"]; + }; + QualificationInput: { + name: string; + kind: components["schemas"]["QualificationKind"]; + level?: string | null; + cert_no?: string | null; + issuer?: string | null; + /** Format: date */ + valid_from?: string | null; + /** + * Format: date + * @description 空表示长期有效 + */ + valid_to?: string | null; + }; + Qualification: components["schemas"]["QualificationInput"] & { + id: components["schemas"]["Id"]; + files: { + id: components["schemas"]["Id"]; + filename: string; + size_bytes: number; + }[]; + created_at: components["schemas"]["Timestamp"]; + updated_at: components["schemas"]["Timestamp"]; + }; + Notification: { + id: components["schemas"]["Id"]; + /** @enum {string} */ + kind: "parse_succeeded" | "parse_failed" | "check_succeeded" | "check_failed" | "quota_80" | "quota_100" | "subscription_activated" | "subscription_expiring"; + title: string; + project_id?: components["schemas"]["Id"] | null; + /** @description 前端路由,如 /projects//results */ + link?: string | null; + read_at?: components["schemas"]["Timestamp"] | null; + created_at: components["schemas"]["Timestamp"]; + }; + Event: { + /** @enum {string} */ + type: "job.progress" | "job.finished" | "report.ready" | "notification.created" | "quota.changed" | "project.changed" | "resync"; + at: components["schemas"]["Timestamp"]; + project_id?: components["schemas"]["Id"] | null; + job?: { + /** @enum {string} */ + kind?: "parse_job" | "check_run" | "report_job"; + id?: components["schemas"]["Id"]; + status?: components["schemas"]["JobStatus"]; + stage?: string | null; + progress?: number; + } | null; + notification_id?: components["schemas"]["Id"] | null; + }; + FeedbackCreate: { + project_id: components["schemas"]["Id"]; + /** @enum {string} */ + object_type: "finding" | "checklist_item" | "checklist"; + object_id?: components["schemas"]["Id"]; + /** @enum {string} */ + kind: "false_positive" | "false_negative"; + note?: string; + }; + }; + responses: { + /** @description `unauthenticated`、`session_expired`、`session_revoked`(被其他设备挤下线时附 `reason: concurrent_login`) */ + Unauthenticated: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `not_found`;访问别的企业的资源也返回这个(架构 §3.3) */ + NotFound: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `validation_failed`;`errors` 列出每个字段的问题 */ + ValidationFailed: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `rate_limited`;响应头 Retry-After */ + RateLimited: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `project_read_only`:项目已递交或已归档,要先撤销 */ + ProjectReadOnly: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `precondition_failed`:资源已被修改,请刷新后再改 */ + PreconditionFailed: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + parameters: { + ProjectId: components["schemas"]["Id"]; + DocumentId: components["schemas"]["Id"]; + UploadId: components["schemas"]["Id"]; + JobId: components["schemas"]["Id"]; + RunId: components["schemas"]["Id"]; + ItemId: components["schemas"]["Id"]; + FindingId: components["schemas"]["Id"]; + QualificationId: components["schemas"]["Id"]; + SessionId: components["schemas"]["Id"]; + /** @description 上一页返回的 next_cursor */ + Cursor: string; + Limit: number; + /** @description 选填;带上时版本不一致返回 412(多人编辑时防止覆盖) */ + IfMatch: string; + }; + requestBodies: never; + headers: { + /** @description 网页版登录时设置 `bd_session`(HttpOnly)和 `bd_csrf`(前端可读) */ + SetSessionCookie: string; + /** @description 资源版本;修改时放进 If-Match */ + ETag: string; + }; + pathItems: never; +} +export type $defs = Record; +export interface operations { + sendSmsCode: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + phone: components["schemas"]["Phone"]; + /** @enum {string} */ + purpose: "login" | "register" | "close_tenant"; + }; + }; + }; + responses: { + /** @description 已发送(号码是否注册都返回 202,不泄露注册状态) */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @description 多少秒后可以重发 */ + resend_after_s: number; + }; + }; + }; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + }; + }; + register: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RegisterRequest"]; + }; + }; + responses: { + /** @description 注册成功并登录。网页版设置会话 cookie;桌面版在响应体里拿到令牌(只返回这一次) */ + 201: { + headers: { + "Set-Cookie": components["headers"]["SetSessionCookie"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LoginResult"]; + }; + }; + /** @description `phone_already_registered` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + 422: components["responses"]["ValidationFailed"]; + }; + }; + login: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoginRequest"]; + }; + }; + responses: { + /** @description 登录成功 */ + 200: { + headers: { + "Set-Cookie": components["headers"]["SetSessionCookie"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LoginResult"]; + }; + }; + /** @description `phone_not_registered`(验证码校验通过后才会返回) */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `concurrent_session` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"] & { + sessions?: components["schemas"]["SessionInfo"][]; + }; + }; + }; + 422: components["responses"]["ValidationFailed"]; + 429: components["responses"]["RateLimited"]; + }; + }; + logout: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已退出 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 401: components["responses"]["Unauthenticated"]; + }; + }; + getMe: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Me"]; + }; + }; + 401: components["responses"]["Unauthenticated"]; + }; + }; + updateMe: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + display_name?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Me"]; + }; + }; + 422: components["responses"]["ValidationFailed"]; + }; + }; + listMySessions: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["SessionInfo"][]; + }; + }; + }; + }; + }; + revokeMySession: { + parameters: { + query?: never; + header?: never; + path: { + session_id: components["parameters"]["SessionId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已吊销 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + }; + }; + closeTenant: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + sms_code: string; + confirm_tenant_name: string; + }; + }; + }; + responses: { + /** @description 已受理,后台删除文件 */ + 202: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 422: components["responses"]["ValidationFailed"]; + }; + }; + listPlans: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Plan"][]; + }; + }; + }; + }; + }; + getQuota: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QuotaOverview"]; + }; + }; + }; + }; + listQuotaEntries: { + parameters: { + query?: { + unit?: components["schemas"]["QuotaUnit"]; + project_id?: components["schemas"]["Id"]; + /** @description 上一页返回的 next_cursor */ + cursor?: components["parameters"]["Cursor"]; + limit?: components["parameters"]["Limit"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按时间倒序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Page"] & { + items: components["schemas"]["QuotaEntry"][]; + }; + }; + }; + }; + }; + listActivationRequests: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ActivationRequest"][]; + }; + }; + }; + }; + }; + createActivationRequest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ActivationRequestCreate"]; + }; + }; + responses: { + /** @description 已提交 */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ActivationRequest"]; + }; + }; + /** @description `activation_pending`:已有一份待处理的申请 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + 422: components["responses"]["ValidationFailed"]; + }; + }; + listProjects: { + parameters: { + query?: { + status?: components["schemas"]["ProjectStatus"][]; + deadline_from?: string; + deadline_to?: string; + /** @description 按项目名称、编号、采购人搜索 */ + q?: string; + sort?: "deadline" | "-deadline" | "updated" | "-updated"; + /** @description 上一页返回的 next_cursor */ + cursor?: components["parameters"]["Cursor"]; + limit?: components["parameters"]["Limit"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Page"] & { + items: components["schemas"]["ProjectListItem"][]; + }; + }; + }; + }; + }; + createProject: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description 选填;不填时为「未命名项目」,解析后按招标文件自动填写 */ + name?: string; + }; + }; + }; + responses: { + /** @description 已创建;建项目的人自动成为负责人(AR12) */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectDetail"]; + }; + }; + /** @description `plan_limit_exceeded`:免费版限 1 个项目 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getProject: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + ETag: components["headers"]["ETag"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectDetail"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + deleteProject: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已删除;对象存储里的文件由后台清理 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + }; + }; + setProjectField: { + parameters: { + query?: never; + header?: { + /** @description 选填;带上时版本不一致返回 412(多人编辑时防止覆盖) */ + "If-Match"?: components["parameters"]["IfMatch"]; + }; + path: { + project_id: components["parameters"]["ProjectId"]; + field_key: components["schemas"]["ProjectFieldKey"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + value: components["schemas"]["FieldValue"]; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectField"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 412: components["responses"]["PreconditionFailed"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + resolvePendingField: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + field_key: components["schemas"]["ProjectFieldKey"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + accept: boolean; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectField"]; + }; + }; + /** @description `no_pending_value` 或 `project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + transitionProject: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @enum {string} */ + action: "submit" | "unsubmit" | "archive" | "unarchive"; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ProjectDetail"]; + }; + }; + /** @description `invalid_transition` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + listLots: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Lot"][]; + }; + }; + }; + }; + }; + createLot: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LotInput"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Lot"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + setBiddingLots: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + lot_ids: components["schemas"]["Id"][]; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Lot"][]; + }; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + }; + }; + deleteLot: { + parameters: { + query?: never; + header?: never; + path: { + lot_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已删除 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description `last_lot`:至少保留一个包;或 `project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + updateLot: { + parameters: { + query?: never; + header?: never; + path: { + lot_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LotInput"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Lot"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + createUpload: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + filename: string; + size_bytes: number; + /** @description 选填;提供时完成上传时校验 */ + sha256?: string; + /** @enum {string} */ + purpose: "project_document" | "qualification_file"; + }; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Upload"]; + }; + }; + /** @description `plan_limit_exceeded`:超出单文件大小上限,`detail` 写明上限值 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getUpload: { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Upload"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + abortUpload: { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已放弃 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + putUploadPart: { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + part_no: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/octet-stream": string; + }; + }; + responses: { + /** @description 已收到 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + completeUpload: { + parameters: { + query?: never; + header?: never; + path: { + upload_id: components["parameters"]["UploadId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UploadCompleted"]; + }; + }; + /** @description `upload_incomplete` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `file_format_unsupported`、`file_encrypted`、`file_corrupted`、`bid_file_platform_encrypted`、 + * `upload_checksum_mismatch`、`plan_limit_exceeded`(页数) + * */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + listDocuments: { + parameters: { + query?: { + role?: components["schemas"]["DocumentRole"]; + }; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Document"][]; + }; + }; + }; + }; + }; + createDocument: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DocumentCreate"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Document"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + getDocument: { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Document"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + deleteDocument: { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已删除 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description `document_in_use` 或 `project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getDocumentFileUrl: { + parameters: { + query: { + variant: "normalized" | "original"; + }; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadUrl"]; + }; + }; + /** @description `document_not_ready`:规范化 PDF 还没生成 */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + listDocumentPages: { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + normalized_sha256: string; + items: components["schemas"]["DocumentPage"][]; + }; + }; + }; + }; + }; + listPageSentences: { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + page_no: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Sentence"][]; + }; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + createCitation: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CitationCreate"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Citation"]; + }; + }; + /** @description `sentence_not_found`:句子编号不在这份文档里,或不在同一页 */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + startParseJob: { + parameters: { + query?: never; + header?: never; + path: { + document_id: components["parameters"]["DocumentId"]; + }; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** + * @description 前端提示用户的扣点数;和服务端算的不一致时返回 `billing_changed`(API4) + * @enum {integer} + */ + expected_charge?: 1; + }; + }; + }; + responses: { + /** @description 已排队 */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ParseJob"]; + }; + }; + /** @description `job_already_running`(附 `job_id`)、`quota_exhausted`、`billing_changed`、`document_role_not_parsable` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getParseJob: { + parameters: { + query?: never; + header?: never; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ParseJob"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + cancelParseJob: { + parameters: { + query?: never; + header?: never; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已受理;worker 在下一个检查点停下 */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ParseJob"]; + }; + }; + /** @description `job_finished` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getChecklist: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Checklist"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + listChecklistItems: { + parameters: { + query?: { + zone?: components["schemas"]["Zone"]; + category?: components["schemas"]["Category"]; + /** @description `bidding` = 通用 + 所投的包(默认,FR-M2-04);`all` = 全部 */ + lot_scope?: "bidding" | "all"; + include_deleted?: boolean; + }; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按原文顺序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ChecklistItem"][]; + }; + }; + }; + }; + }; + createChecklistItem: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChecklistItemCreate"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + getChecklistItem: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + ETag: components["headers"]["ETag"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + deleteChecklistItem: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已删除 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 409: components["responses"]["ProjectReadOnly"]; + }; + }; + updateChecklistItem: { + parameters: { + query?: never; + header?: { + /** @description 选填;带上时版本不一致返回 412(多人编辑时防止覆盖) */ + "If-Match"?: components["parameters"]["IfMatch"]; + }; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ChecklistItemUpdate"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + ETag: components["headers"]["ETag"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 412: components["responses"]["PreconditionFailed"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + restoreChecklistItem: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + }; + }; + acceptPendingItem: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + citation_ids?: components["schemas"]["Id"][]; + /** @default false */ + no_citation_ack?: boolean; + changes?: components["schemas"]["ChecklistItemUpdate"]; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + /** @description `item_not_pending` 或 `project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `citation_required`:既没有出处也没有声明无出处 */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + setItemCitations: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + citation_ids: components["schemas"]["Id"][]; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + }; + }; + resolveSupersession: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + accept: boolean; + }; + }; + }; + responses: { + /** @description 返回旧条目 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ChecklistItem"]; + }; + }; + /** @description `not_superseded` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getItemHistory: { + parameters: { + query?: never; + header?: never; + path: { + item_id: components["parameters"]["ItemId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按时间倒序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["HistoryEntry"][]; + }; + }; + }; + }; + }; + confirmChecklist: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Checklist"]; + }; + }; + /** @description `checklist_empty` 或 `project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + listChecklistVersions: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ChecklistVersion"][]; + }; + }; + }; + }; + }; + getCheckPreview: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CheckPreview"]; + }; + }; + }; + }; + listCheckRuns: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按时间倒序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["CheckRun"][]; + }; + }; + }; + }; + }; + startCheckRun: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CheckRunCreate"]; + }; + }; + responses: { + /** @description 已排队 */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CheckRun"]; + }; + }; + /** @description `job_already_running`(附 `job_id`)、`quota_exhausted`、`billing_changed`(附最新的 `preview`)、 + * `no_parsed_tender`、`no_bid_documents`、`project_read_only` + * */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getCheckRun: { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CheckRun"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + cancelCheckRun: { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已受理 */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CheckRun"]; + }; + }; + /** @description `job_finished` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + listFindings: { + parameters: { + query?: { + severity?: components["schemas"]["Severity"]; + status?: components["schemas"]["FindingStatus"]; + presence?: components["schemas"]["Presence"]; + }; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按级别、再按原文顺序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Finding"][]; + }; + }; + }; + }; + }; + bulkResolveFindings: { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + finding_ids: components["schemas"]["Id"][]; + /** @enum {string} */ + status: "confirmed_ok" | "ignored"; + note?: string; + }; + }; + }; + responses: { + /** @description 全部成功才提交,否则一条都不改 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Finding"][]; + project: components["schemas"]["ProjectReadiness"]; + }; + }; + }; + /** @description `run_superseded`、`project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `bulk_only_hint`(附不符合的问题编号)、`note_required` */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + resolveFinding: { + parameters: { + query?: never; + header?: never; + path: { + finding_id: components["parameters"]["FindingId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @enum {string} */ + status: "confirmed_ok" | "modified" | "ignored"; + note?: string; + }; + }; + }; + responses: { + /** @description 返回问题和项目最新的就绪状态 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + finding: components["schemas"]["Finding"]; + project: components["schemas"]["ProjectReadiness"]; + }; + }; + }; + /** @description `run_superseded`、`finding_resolved_this_run`、`project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + /** @description `note_required` */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + reopenFinding: { + parameters: { + query?: never; + header?: never; + path: { + finding_id: components["parameters"]["FindingId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + finding: components["schemas"]["Finding"]; + project: components["schemas"]["ProjectReadiness"]; + }; + }; + }; + /** @description `run_superseded`、`project_read_only` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + listManualChecks: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ManualCheck"][]; + }; + }; + }; + }; + }; + setManualCheck: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description item:<清单项编号> 或 rule:<规则编号> */ + check_key: string; + checked: boolean; + note?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ManualCheck"]; + }; + }; + 409: components["responses"]["ProjectReadOnly"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + generateReport: { + parameters: { + query?: never; + header?: never; + path: { + run_id: components["parameters"]["RunId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已排队 */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReportJob"]; + }; + }; + /** @description `run_not_succeeded` */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["Problem"]; + }; + }; + }; + }; + getReportJob: { + parameters: { + query?: never; + header?: never; + path: { + job_id: components["parameters"]["JobId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReportJob"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + listReports: { + parameters: { + query?: never; + header?: never; + path: { + project_id: components["parameters"]["ProjectId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按时间倒序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Report"][]; + }; + }; + }; + }; + }; + getReportFileUrl: { + parameters: { + query?: never; + header?: never; + path: { + report_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadUrl"]; + }; + }; + 404: components["responses"]["NotFound"]; + }; + }; + listQualifications: { + parameters: { + query?: { + kind?: components["schemas"]["QualificationKind"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Qualification"][]; + }; + }; + }; + }; + }; + createQualification: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QualificationInput"]; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Qualification"]; + }; + }; + 422: components["responses"]["ValidationFailed"]; + }; + }; + updateQualification: { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QualificationInput"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Qualification"]; + }; + }; + 404: components["responses"]["NotFound"]; + 422: components["responses"]["ValidationFailed"]; + }; + }; + deleteQualification: { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已删除 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + attachQualificationFile: { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + upload_id: components["schemas"]["Id"]; + }; + }; + }; + responses: { + /** @description OK */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Qualification"]; + }; + }; + }; + }; + getQualificationFileUrl: { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + file_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DownloadUrl"]; + }; + }; + }; + }; + deleteQualificationFile: { + parameters: { + query?: never; + header?: never; + path: { + qualification_id: components["parameters"]["QualificationId"]; + file_id: components["schemas"]["Id"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 已删除 */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + listNotifications: { + parameters: { + query?: { + unread?: boolean; + /** @description 上一页返回的 next_cursor */ + cursor?: components["parameters"]["Cursor"]; + limit?: components["parameters"]["Limit"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description 按时间倒序 */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Page"] & { + unread_count: number; + items: components["schemas"]["Notification"][]; + }; + }; + }; + }; + }; + markNotificationsRead: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + ids?: components["schemas"]["Id"][]; + }; + }; + }; + responses: { + /** @description OK */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + streamEvents: { + parameters: { + query?: never; + header?: { + "Last-Event-ID"?: string; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description `text/event-stream`;`event` 字段是 Event.type,`data` 是 Event 的 JSON */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "text/event-stream": components["schemas"]["Event"]; + }; + }; + }; + }; + createFeedback: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FeedbackCreate"]; + }; + }; + responses: { + /** @description 已提交 */ + 201: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 422: components["responses"]["ValidationFailed"]; + }; + }; +} diff --git a/packages/api-client/tsconfig.json b/packages/api-client/tsconfig.json new file mode 100644 index 0000000..7459db0 --- /dev/null +++ b/packages/api-client/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "strict": true, + "noUncheckedIndexedAccess": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..24d1246 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1472 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + apps/web: + dependencies: + '@bid-doctor/api-client': + specifier: workspace:* + version: link:../../packages/api-client + '@fontsource/barlow-semi-condensed': + specifier: 5.3.0 + version: 5.3.0 + '@fontsource/noto-serif-sc': + specifier: 5.3.0 + version: 5.3.0 + '@lucide/vue': + specifier: 1.50.0 + version: 1.50.0(vue@3.5.43(typescript@5.8.3)) + '@tanstack/vue-query': + specifier: 5.104.1 + version: 5.104.1(vue@3.5.43(typescript@5.8.3)) + reka-ui: + specifier: 2.10.5 + version: 2.10.5(vue@3.5.43(typescript@5.8.3)) + vue: + specifier: 3.5.43 + version: 3.5.43(typescript@5.8.3) + vue-router: + specifier: 4.6.4 + version: 4.6.4(vue@3.5.43(typescript@5.8.3)) + devDependencies: + '@types/node': + specifier: 24.19.2 + version: 24.19.2 + '@vitejs/plugin-vue': + specifier: 6.0.9 + version: 6.0.9(vite@7.3.7(@types/node@24.19.2))(vue@3.5.43(typescript@5.8.3)) + typescript: + specifier: 5.8.3 + version: 5.8.3 + vite: + specifier: 7.3.7 + version: 7.3.7(@types/node@24.19.2) + vue-tsc: + specifier: 3.3.12 + version: 3.3.12(typescript@5.8.3) + + packages/api-client: + dependencies: + openapi-fetch: + specifier: 0.13.8 + version: 0.13.8 + devDependencies: + openapi-typescript: + specifier: 7.8.0 + version: 7.8.0(typescript@5.8.3) + typescript: + specifier: 5.8.3 + version: 5.8.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.11': + resolution: {integrity: sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.12': + resolution: {integrity: sha512-WJ7JBp/Q4+z/ImydtI7jS6NVco5++632ExBemSX+Ysb+G1lCwQ5BVo3SY+jGGOcZXI4ZccbZSUSJLNBW6eEpJg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.11': + resolution: {integrity: sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@floating-ui/vue@1.1.11': + resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==} + + '@fontsource/barlow-semi-condensed@5.3.0': + resolution: {integrity: sha512-EdpQ1GlLMofGqDsQhEw+g6DonNKmzt21p8y/oNbTl4BFzEkqYkFyE2fj25YGxmZBL/LZZGAxe74FlvEWcgc31w==} + + '@fontsource/noto-serif-sc@5.3.0': + resolution: {integrity: sha512-0/zaEFkidiWldE62rTeD74x8ygUsQvejiSNtO0LQxQk3qpaHnlMZ3w4C7yH80B4KTIg8VKeFP4oSgwWMchY9+g==} + + '@internationalized/date@3.12.4': + resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==} + + '@internationalized/number@3.6.9': + resolution: {integrity: sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@lucide/vue@1.50.0': + resolution: {integrity: sha512-/cYAYqHIMUcVbhGYPk+GqgPqlqTKCFfyGrpXvH/6z5v7sWRoWBHViYwO3cNgmupZCBY+4WB016wN6CkXNcy0Yw==} + peerDependencies: + vue: '>=3.0.1' + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.23': + resolution: {integrity: sha512-si5RAEgXylHljgfvUr5k3b+doRmLcWaF8X5RLtPmZSBt3paskSXauqJ6DDx7IChInlUnXz0SHQSXI/5xff61oA==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/rollup-android-arm-eabi@4.67.4': + resolution: {integrity: sha512-/v/pUAPOcwp+r1Hjjdg0cBV1TE89CzGKrKh2FgkIQw2QBcIMmVPOCSfuOZStCn4Xh/98HXfo/ht6ztcssctPNg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.67.4': + resolution: {integrity: sha512-6tmmB/P52Vt167FXXEw5auiEUgTtZz7aqUgWHqYXToIUE0HFDY6fTdAyeekjjsNk6O/8dtpSKtClxSj1mMneKQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-android-x64@4.67.4': + resolution: {integrity: sha512-8Z9Q4RmotVmuL4llOvqX814JNiOK1oqoBv+glG5KbpRfUiypqzz2/fxIzK4Pq129l4dT5a8Gszmpc0/XPYoFuQ==} + cpu: [x64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.67.4': + resolution: {integrity: sha512-pnq6hssxTtwuRAl6Kdi/+IDY+b1IhkUetjhUkdLAAJCssjfqtL2Oqy/FXJVJFtAWOu1oRlArIvf0Yk1O92x2HQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.67.4': + resolution: {integrity: sha512-BeIrGZvHMVdJg/N+ohZfFywG/gF41684YhDj2wBvouj7m9ziP1tZDUNwzXRiDY+3WIKngaC4hiUBqVtBPGP5og==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.67.4': + resolution: {integrity: sha512-fRFX/oqNg/g780DKoS8RIfYcZD59Ru5O/bSeK1N7gQljBFrTxJY/q5EX4eMs5SrJf32VgVBVVYCdfPpzAwPPnQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.67.4': + resolution: {integrity: sha512-bSrBRopG1q+0RuOsW0NtYTtIODlvNA3T3ebx5WnQVdSxWIoXFmj+rZdHUHdSLgFdZ5M6VGmekYE7r4RMGQXjfg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.67.4': + resolution: {integrity: sha512-L8IvdrJVtl65g1ozwDSyjGPG+FmibhCdFUwmqTItJLw0S3+Oj4QzFs7rfgTUiK7rsPjkCLdG1/GApFxoynXFJQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.67.4': + resolution: {integrity: sha512-h7PcoQOzC31jshNpQ48J0akB9tsghjkSjSl5AZ6XsVGTxVsln7t7C4SQyw62tNVSaz2SeHgk4VT2ELbL17BSHQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.67.4': + resolution: {integrity: sha512-KSPuOBNHoOl7AsVjeY1KZci8+0up0U4TFAHU5IzBYN8P+Z6cB9DZq28+JQycZW7f6WLLa/tSzhV7Rdy95bVi+g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.67.4': + resolution: {integrity: sha512-0E/mKmVk1PlQeQYygIwQm0jlwjANTzGSiNBMPrFx+rdFGFj/2Axh4p6XwcrystQIqXZVudTaGsdbDpzYgJ4wSA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.67.4': + resolution: {integrity: sha512-Fvw7dOSc2Sa9UOipRZVI0zOgdLCzsjVyD/6JuHXzUHTXFu7mMgmeX/0/bCVBIut1dz+nWz3pV4WpS/Qg0XVnxw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.67.4': + resolution: {integrity: sha512-jzxbVHaE7V3F+P0wEK7UydQtvv7Twfdtx58jI5/3VRx1jJxyrn+qFwfXmqjVFxPhBl+zjaRs+ZJMFJw7vdHaQA==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.67.4': + resolution: {integrity: sha512-fhtrzp9p4GjEJVaigaap9g6X9nP6xgJtKiLLXSDgpE18HXufA5aqfltGn+KAqMtVZReJZfOrXm//y5YSA9BWbA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.67.4': + resolution: {integrity: sha512-uynaF7IUHwAckV0S+yC1f5esy9gB+aqCBsPH5BOck32TYdCuAn2lVG5tvUcFnEsrviGEmRZxE1NHSOieUPYJEg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.67.4': + resolution: {integrity: sha512-uaqRFdfb8eIa2bVHN/FE3ndCvR0zV2xv0AwjCfxrqSu6l6fp/54VhjfhslKlf2RIik9L1jwlt4E56PeVX6tQtQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.67.4': + resolution: {integrity: sha512-JZRow7kmxUDEASnxHuVG5itTm+nTXi9BQUABcVUnUEngC+OPwMrpx1uJ9hIA/v+IcAEBL52ktCSPXk5OJn9Jqg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.67.4': + resolution: {integrity: sha512-rdrh4e+Lq9fCNKoDPaCqeHXbQCe7yl77Ukl45tlSClwYdZ1gpFGZz1XLJdYtY++FRlfL5aHvb6/0n3GDW/Nxsw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.67.4': + resolution: {integrity: sha512-wL7MS4r0fYcxluF3ne2plofHZiB/LsqNCnyaUlyiKpAJLb6agPSFz7DEFCfkonWAZXMrLwCmC08b2ZEnEdJkog==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.67.4': + resolution: {integrity: sha512-4H0XHfd2VufGjX7NjUyeue78sD0bRIoKSGjGqO+F5nSvL6jN48NAU7esdmtFEYxZXwObLtt3MJ/kHS0jASMW3g==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.67.4': + resolution: {integrity: sha512-osRHQbE55pZpCLetpMsFmhkapyZnAUH73qRKMh+npi3boUom6u6hdaTTPSxlInXxwJHsn9r2uDhdz0V7Yoxx2A==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.67.4': + resolution: {integrity: sha512-95+exYEyOwrZePsE0czi4q7X5lxKPa21+lmyAZlwbYENITQ0Unk4HAQAFp/p+KhXBHBx9mUABXRYjOC5A1wY3Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.67.4': + resolution: {integrity: sha512-FOXOcKhNQpO4Po+Ynan6IbtM67SrXZbgpRJWVYwz53CT5veI7sVzDwVBmWS65N5Imv+wK3SVhIPAl+C5DXXNCg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.67.4': + resolution: {integrity: sha512-cklKh0gJOkAZQcWziQa3hrjxZKN+PCHZ7SPTyGJDAiBVr05ZXlEjGFCG7zFJMaVibxsmzAe1FimN4u/HBns9+Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.67.4': + resolution: {integrity: sha512-AegwxJPc9bvUp8fQtlsUHkFMOMkGylWU7glyL+YD4vwG+QaYoGXYLSsQoeTD7kvoLZrVlMWNVhTg1dedY5C1VQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.67.4': + resolution: {integrity: sha512-RJZnrwTWj7Iq2ooBwoGbDdC65tDwYawneGcAfFMJqVUrRZMcskka6nTkBpXR/47G7kzn3EW2qupExnaBy11vwg==} + cpu: [x64] + os: [win32] + + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} + + '@tanstack/match-sorter-utils@9.2.6': + resolution: {integrity: sha512-DIpUSOhPfQ7u/qfUi/Wgm0wBO56MyBYXGvnlI0NcauDRPqdBUlAZZjW2Dxm92m+k1UWVhKw8YJ4f47+75Aujyw==} + engines: {node: '>=20'} + + '@tanstack/query-core@5.104.1': + resolution: {integrity: sha512-TiRghcrGkUTE+M4JBxvpPSpBhxvIzQairw+cksrHYGr+2uwC77yW1SY9nYxnYRYOppvucf8rEg6b5byoz4rTpw==} + + '@tanstack/virtual-core@3.19.10': + resolution: {integrity: sha512-/ltpH0qUCs5XQYE7fIhgA+aqgC5NzSfLTDQkC+N1wOOdvIYx1FH/kz2LGfSeanNbekqy3WBu6mNIbqXhxE/pew==} + + '@tanstack/vue-query@5.104.1': + resolution: {integrity: sha512-3858wOmIoIazR9mZ2g8/QsKNccQlzxIY+4/g55OHbhhMeBEi+ig/8vne2e5N+Xx9g5MXuB7Nscfb6B4REPDWlw==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.6.0 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + '@tanstack/vue-virtual@3.13.51': + resolution: {integrity: sha512-0Q57TrTPrNBhQsURZzbDQ4HVC+ukhX/7Ma2h1UXNECyRAHFFRrrEiETtH6TyUHm6E1fmei4HHhu4yAYiTkuviQ==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + + '@types/estree@1.0.10': + resolution: {integrity: sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==} + + '@types/node@24.19.2': + resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@vitejs/plugin-vue@6.0.9': + resolution: {integrity: sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue/compiler-core@3.5.43': + resolution: {integrity: sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==} + + '@vue/compiler-core@3.5.44': + resolution: {integrity: sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ==} + + '@vue/compiler-dom@3.5.43': + resolution: {integrity: sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==} + + '@vue/compiler-dom@3.5.44': + resolution: {integrity: sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg==} + + '@vue/compiler-sfc@3.5.43': + resolution: {integrity: sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==} + + '@vue/compiler-ssr@3.5.43': + resolution: {integrity: sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@3.3.12': + resolution: {integrity: sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA==} + + '@vue/reactivity@3.5.43': + resolution: {integrity: sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==} + + '@vue/runtime-core@3.5.43': + resolution: {integrity: sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==} + + '@vue/runtime-dom@3.5.43': + resolution: {integrity: sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==} + + '@vue/server-renderer@3.5.43': + resolution: {integrity: sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==} + + '@vue/shared@3.5.43': + resolution: {integrity: sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==} + + '@vue/shared@3.5.44': + resolution: {integrity: sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg==} + + '@vueuse/core@14.4.0': + resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@14.4.0': + resolution: {integrity: sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==} + + '@vueuse/shared@14.4.0': + resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==} + peerDependencies: + vue: ^3.5.0 + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.1.7: + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + ohash@2.0.12: + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + + openapi-fetch@0.13.8: + resolution: {integrity: sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ==} + + openapi-typescript-helpers@0.0.15: + resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} + + openapi-typescript@7.8.0: + resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss@8.5.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + engines: {node: ^10 || ^12 || >=14} + + reka-ui@2.10.5: + resolution: {integrity: sha512-/8o0y9BThE0hcHKFg+3770tWcJh3qKdOEFxLOPb9HIajXeFoH/J530cZH62jUJunbqwBgpSBL9v7nB8LJ5pDXA==} + peerDependencies: + vue: '>= 3.4.0' + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rollup@4.67.4: + resolution: {integrity: sha512-Lsv3oKcz5jgOQJej/p7nfwbVEdAbDT+z6OPf/SCdI+nF+q2ENp0pTKT35ZwXAmH8g45mfrIiJeEyAqKYSYoL9Q==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tslib@2.9.1: + resolution: {integrity: sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + uri-js-replace@1.0.2: + resolution: {integrity: sha512-VeBBt/nMxedczht3KDL2yBBMJqIxpCQLMZp3UJ1otcWwUHdQW4IiyHpJivExzSK+FdxTowRfGL4FmwDqH9+S8Q==} + + vite@7.3.7: + resolution: {integrity: sha512-AHEzTORMbi7/7aXp7w7KZ5+DCUdSeLsJz0grY8xHpc9DHdYb0vQu6DqJfm2tOw3gDExNgPgnJTXxM6syYFHaCA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.2.0: + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} + engines: {node: '>=12'} + hasBin: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@3.3.12: + resolution: {integrity: sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.43: + resolution: {integrity: sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.29.11': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.12': + dependencies: + '@babel/types': 7.29.11 + + '@babel/types@7.29.11': + dependencies: + '@babel/helper-string-parser': 7.29.11 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@floating-ui/vue@1.1.11(vue@3.5.43(typescript@5.8.3))': + dependencies: + '@floating-ui/dom': 1.8.0 + '@floating-ui/utils': 0.2.12 + vue-demi: 0.14.10(vue@3.5.43(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + + '@fontsource/barlow-semi-condensed@5.3.0': {} + + '@fontsource/noto-serif-sc@5.3.0': {} + + '@internationalized/date@3.12.4': + dependencies: + '@swc/helpers': 0.5.23 + + '@internationalized/number@3.6.9': + dependencies: + '@swc/helpers': 0.5.23 + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@lucide/vue@1.50.0(vue@3.5.43(typescript@5.8.3))': + dependencies: + vue: 3.5.43(typescript@5.8.3) + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.2 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.23(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.3.2 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/rollup-android-arm-eabi@4.67.4': + optional: true + + '@rollup/rollup-android-arm64@4.67.4': + optional: true + + '@rollup/rollup-android-x64@4.67.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.67.4': + optional: true + + '@rollup/rollup-darwin-x64@4.67.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.67.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.67.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.67.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.67.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.67.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.67.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.67.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.67.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.67.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.67.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.67.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.67.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.67.4': + optional: true + + '@swc/helpers@0.5.23': + dependencies: + tslib: 2.9.1 + + '@tanstack/match-sorter-utils@9.2.6': + dependencies: + remove-accents: 0.5.0 + + '@tanstack/query-core@5.104.1': {} + + '@tanstack/virtual-core@3.19.10': {} + + '@tanstack/vue-query@5.104.1(vue@3.5.43(typescript@5.8.3))': + dependencies: + '@tanstack/match-sorter-utils': 9.2.6 + '@tanstack/query-core': 5.104.1 + '@vue/devtools-api': 6.6.4 + vue: 3.5.43(typescript@5.8.3) + vue-demi: 0.14.10(vue@3.5.43(typescript@5.8.3)) + + '@tanstack/vue-virtual@3.13.51(vue@3.5.43(typescript@5.8.3))': + dependencies: + '@tanstack/virtual-core': 3.19.10 + vue: 3.5.43(typescript@5.8.3) + + '@types/estree@1.0.10': {} + + '@types/node@24.19.2': + dependencies: + undici-types: 7.24.6 + + '@types/web-bluetooth@0.0.21': {} + + '@vitejs/plugin-vue@6.0.9(vite@7.3.7(@types/node@24.19.2))(vue@3.5.43(typescript@5.8.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 7.3.7(@types/node@24.19.2) + vue: 3.5.43(typescript@5.8.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.2.0 + + '@vue/compiler-core@3.5.43': + dependencies: + '@babel/parser': 7.29.12 + '@vue/shared': 3.5.43 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.2 + + '@vue/compiler-core@3.5.44': + dependencies: + '@babel/parser': 7.29.12 + '@vue/shared': 3.5.44 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.2 + + '@vue/compiler-dom@3.5.43': + dependencies: + '@vue/compiler-core': 3.5.43 + '@vue/shared': 3.5.43 + + '@vue/compiler-dom@3.5.44': + dependencies: + '@vue/compiler-core': 3.5.44 + '@vue/shared': 3.5.44 + + '@vue/compiler-sfc@3.5.43': + dependencies: + '@babel/parser': 7.29.12 + '@vue/compiler-core': 3.5.43 + '@vue/compiler-dom': 3.5.43 + '@vue/compiler-ssr': 3.5.43 + '@vue/shared': 3.5.43 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.32 + source-map-js: 1.2.2 + + '@vue/compiler-ssr@3.5.43': + dependencies: + '@vue/compiler-dom': 3.5.43 + '@vue/shared': 3.5.43 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@3.3.12': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.44 + '@vue/shared': 3.5.44 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.7 + + '@vue/reactivity@3.5.43': + dependencies: + '@vue/shared': 3.5.43 + + '@vue/runtime-core@3.5.43': + dependencies: + '@vue/reactivity': 3.5.43 + '@vue/shared': 3.5.43 + + '@vue/runtime-dom@3.5.43': + dependencies: + '@vue/reactivity': 3.5.43 + '@vue/runtime-core': 3.5.43 + '@vue/shared': 3.5.43 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.43': + dependencies: + '@vue/compiler-ssr': 3.5.43 + '@vue/runtime-dom': 3.5.43 + '@vue/shared': 3.5.43 + + '@vue/shared@3.5.43': {} + + '@vue/shared@3.5.44': {} + + '@vueuse/core@14.4.0(vue@3.5.43(typescript@5.8.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.4.0 + '@vueuse/shared': 14.4.0(vue@3.5.43(typescript@5.8.3)) + vue: 3.5.43(typescript@5.8.3) + + '@vueuse/metadata@14.4.0': {} + + '@vueuse/shared@14.4.0(vue@3.5.43(typescript@5.8.3))': + dependencies: + vue: 3.5.43(typescript@5.8.3) + + agent-base@7.1.4: {} + + alien-signals@3.2.1: {} + + ansi-colors@4.1.3: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.9.1 + + balanced-match@1.0.2: {} + + brace-expansion@2.1.7: + dependencies: + balanced-match: 1.0.2 + + change-case@5.4.4: {} + + colorette@1.4.0: {} + + csstype@3.2.3: {} + + debug@4.4.3(supports-color@10.2.2): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + defu@6.1.7: {} + + entities@7.0.1: {} + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + estree-walker@2.0.2: {} + + fast-deep-equal@3.1.3: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + index-to-position@1.2.0: {} + + js-levenshtein@1.1.6: {} + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + json-schema-traverse@1.0.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.7 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.20: {} + + ohash@2.0.12: {} + + openapi-fetch@0.13.8: + dependencies: + openapi-typescript-helpers: 0.0.15 + + openapi-typescript-helpers@0.0.15: {} + + openapi-typescript@7.8.0(typescript@5.8.3): + dependencies: + '@redocly/openapi-core': 1.34.23(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.8.3 + yargs-parser: 21.1.1 + + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + + path-browserify@1.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + pluralize@8.0.0: {} + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + reka-ui@2.10.5(vue@3.5.43(typescript@5.8.3)): + dependencies: + '@floating-ui/dom': 1.8.0 + '@floating-ui/vue': 1.1.11(vue@3.5.43(typescript@5.8.3)) + '@internationalized/date': 3.12.4 + '@internationalized/number': 3.6.9 + '@tanstack/vue-virtual': 3.13.51(vue@3.5.43(typescript@5.8.3)) + '@vueuse/core': 14.4.0(vue@3.5.43(typescript@5.8.3)) + '@vueuse/shared': 14.4.0(vue@3.5.43(typescript@5.8.3)) + aria-hidden: 1.2.6 + defu: 6.1.7 + ohash: 2.0.12 + vue: 3.5.43(typescript@5.8.3) + transitivePeerDependencies: + - '@vue/composition-api' + + remove-accents@0.5.0: {} + + require-from-string@2.0.2: {} + + rollup@4.67.4: + dependencies: + '@types/estree': 1.0.10 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.67.4 + '@rollup/rollup-android-arm64': 4.67.4 + '@rollup/rollup-android-x64': 4.67.4 + '@rollup/rollup-darwin-arm64': 4.67.4 + '@rollup/rollup-darwin-x64': 4.67.4 + '@rollup/rollup-freebsd-arm64': 4.67.4 + '@rollup/rollup-freebsd-x64': 4.67.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.67.4 + '@rollup/rollup-linux-arm-musleabihf': 4.67.4 + '@rollup/rollup-linux-arm64-gnu': 4.67.4 + '@rollup/rollup-linux-arm64-musl': 4.67.4 + '@rollup/rollup-linux-loong64-gnu': 4.67.4 + '@rollup/rollup-linux-loong64-musl': 4.67.4 + '@rollup/rollup-linux-ppc64-gnu': 4.67.4 + '@rollup/rollup-linux-ppc64-musl': 4.67.4 + '@rollup/rollup-linux-riscv64-gnu': 4.67.4 + '@rollup/rollup-linux-riscv64-musl': 4.67.4 + '@rollup/rollup-linux-s390x-gnu': 4.67.4 + '@rollup/rollup-linux-x64-gnu': 4.67.4 + '@rollup/rollup-linux-x64-musl': 4.67.4 + '@rollup/rollup-openbsd-x64': 4.67.4 + '@rollup/rollup-openharmony-arm64': 4.67.4 + '@rollup/rollup-win32-arm64-msvc': 4.67.4 + '@rollup/rollup-win32-ia32-msvc': 4.67.4 + '@rollup/rollup-win32-x64-gnu': 4.67.4 + '@rollup/rollup-win32-x64-msvc': 4.67.4 + fsevents: 2.3.3 + + source-map-js@1.2.2: {} + + supports-color@10.2.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tslib@2.9.1: {} + + type-fest@4.41.0: {} + + typescript@5.8.3: {} + + undici-types@7.24.6: {} + + uri-js-replace@1.0.2: {} + + vite@7.3.7(@types/node@24.19.2): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + postcss: 8.5.32 + rollup: 4.67.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.19.2 + fsevents: 2.3.3 + + vscode-uri@3.2.0: {} + + vue-demi@0.14.10(vue@3.5.43(typescript@5.8.3)): + dependencies: + vue: 3.5.43(typescript@5.8.3) + + vue-router@4.6.4(vue@3.5.43(typescript@5.8.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.43(typescript@5.8.3) + + vue-tsc@3.3.12(typescript@5.8.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.12 + typescript: 5.8.3 + + vue@3.5.43(typescript@5.8.3): + dependencies: + '@vue/compiler-dom': 3.5.43 + '@vue/compiler-sfc': 3.5.43 + '@vue/runtime-dom': 3.5.43 + '@vue/server-renderer': 3.5.43 + '@vue/shared': 3.5.43 + optionalDependencies: + typescript: 5.8.3 + + yaml-ast-parser@0.0.43: {} + + yargs-parser@21.1.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..286cf7f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - apps/* + - packages/*