Commit 9d890eb8 authored by David Yang's avatar David Yang

chore: 完善审计配置文档

parent 5919a2cf
UMI_APP_LLM_API_KEY=your_api_key_here
# Optional overrides. Defaults are shown below.
# UMI_APP_LLM_API_BASE=/llm-api
# UMI_APP_LLM_CHAT_PATH=/v1/chat/completions
# UMI_APP_LLM_MODEL=gpt-5.5
......@@ -40,7 +40,7 @@ export default defineConfig({
pathRewrite: { '^/api': '' },
},
'/llm-api': {
target: 'https://lingsuan.top',
target: 'https://lingsuan.org',
changeOrigin: true,
pathRewrite: { '^/llm-api': '' },
},
......
# README
`@umijs/max` 模板项目,更多功能参考 [Umi Max 简介](https://umijs.org/docs/max/introduce)
## 本地启动
如果本地模型代理需要浏览器携带 API Key,先创建本地环境变量文件:
```bash
cp .env.example .env.local
```
然后把 `.env.local` 里的 `UMI_APP_LLM_API_KEY` 替换成线上模型服务 API Key。如果 `/llm-api` 代理已在服务端处理鉴权,可以不创建该文件。
```bash
pnpm install
pnpm dev
```
可选配置:
- `UMI_APP_LLM_API_BASE`:模型服务地址,默认走 Umi 代理 `/llm-api`
- `UMI_APP_LLM_CHAT_PATH`:聊天接口路径,默认 `/v1/chat/completions`
- `UMI_APP_LLM_MODEL`:默认模型,默认 `gpt-5.5`
......@@ -4,17 +4,33 @@ export const AUDIT_PAGE_SIZE = 2000;
export const AUDIT_PROMPT_RECORD_LIMIT = 80;
export const AUDIT_FIELD_ALIASES = {
businessSegment: ['business_segment', '业务板块'],
level1Project: ['level1_project', '一级项目'],
level2Project: ['level2_project', '二级项目'],
riskDescription: ['risk_description', '具体风险点描述'],
regulationBasis: ['regulation_basis', '法规依据'],
riskSource: ['risk_source', '风险点来源'],
riskLevel: ['risk_level', '风险等级'],
businessSegment: ['business_segment', 'businessSegment', '业务板块'],
level1Project: ['level1_project', 'level1Project', '一级项目'],
level2Project: ['level2_project', 'level2Project', '二级项目'],
riskDescription: [
'risk_description',
'riskDescription',
'description',
'content',
'text',
'具体风险点描述',
'风险点描述',
],
regulationBasis: ['regulation_basis', 'regulationBasis', '法规依据'],
riskSource: ['risk_source', 'riskSource', '风险点来源'],
riskLevel: ['risk_level', 'riskLevel', '风险等级'],
keywords: ['keywords', '关键词'],
mdContent: ['md_content', 'Markdown 内容', 'markdown_content'],
mdContent: [
'md_content',
'mdContent',
'markdown_content',
'markdownContent',
'markdown',
'Markdown 内容',
'内容',
],
enabled: ['enabled', '是否启用'],
sourceRow: ['source_row', '来源行号', '_source_row'],
sourceRow: ['source_row', 'sourceRow', '来源行号', '_source_row'],
};
export const AUDIT_EXCEL_FIELDS = [
......
......@@ -51,6 +51,9 @@ const REFINE_PATTERNS = [
'最低分',
];
const AUDIT_FIRST_PAGE = 0;
const AUDIT_MAX_PAGES = 20;
function toText(value) {
if (Array.isArray(value)) {
return value.join(' ');
......@@ -65,11 +68,20 @@ export function normalizeRecordList(res) {
if (Array.isArray(res)) return res;
if (Array.isArray(res?.records)) return res.records;
if (Array.isArray(res?.items)) return res.items;
if (Array.isArray(res?.list)) return res.list;
if (Array.isArray(res?.result)) return res.result;
if (Array.isArray(res?.result?.records)) return res.result.records;
if (Array.isArray(res?.result?.items)) return res.result.items;
if (Array.isArray(res?.result?.list)) return res.result.list;
if (Array.isArray(res?.data)) return res.data;
if (Array.isArray(res?.data?.records)) return res.data.records;
if (Array.isArray(res?.data?.items)) return res.data.items;
if (Array.isArray(res?.data?.list)) return res.data.list;
if (Array.isArray(res?.hits?.hits)) {
return res.hits.hits.map((item) => item._source || item);
return res.hits.hits.map((item) => ({
...(item._source || item),
id: item._id || item.id,
}));
}
return [];
}
......@@ -78,8 +90,16 @@ function getResponseTotal(res) {
const values = [
res?.total,
res?.count,
res?.total_records,
res?.total_count,
res?.data?.total,
res?.data?.count,
res?.data?.total_records,
res?.data?.total_count,
res?.result?.total,
res?.result?.count,
res?.result?.total_records,
res?.result?.total_count,
res?.hits?.total?.value,
res?.hits?.total,
];
......@@ -88,6 +108,10 @@ function getResponseTotal(res) {
function getAuditField(record, aliases = []) {
const sources = [
record?._source?.metadata,
record?._source?.fields,
record?._source?.data,
record?._source,
record?.metadata,
record?.fields,
record?.data,
......@@ -103,6 +127,12 @@ function getAuditField(record, aliases = []) {
}
export function normalizeAuditRecord(record) {
const riskDescription = toText(
getAuditField(record, AUDIT_FIELD_ALIASES.riskDescription),
);
const mdContent = toText(
getAuditField(record, AUDIT_FIELD_ALIASES.mdContent),
);
const normalized = {
id: record?.name || record?._id || record?.id || '',
title: record?.title || '',
......@@ -115,16 +145,14 @@ export function normalizeAuditRecord(record) {
level2Project: toText(
getAuditField(record, AUDIT_FIELD_ALIASES.level2Project),
),
riskDescription: toText(
getAuditField(record, AUDIT_FIELD_ALIASES.riskDescription),
),
riskDescription: riskDescription || toText(record?.title),
regulationBasis: toText(
getAuditField(record, AUDIT_FIELD_ALIASES.regulationBasis),
),
riskSource: toText(getAuditField(record, AUDIT_FIELD_ALIASES.riskSource)),
riskLevel: toText(getAuditField(record, AUDIT_FIELD_ALIASES.riskLevel)),
keywords: getAuditField(record, AUDIT_FIELD_ALIASES.keywords),
mdContent: toText(getAuditField(record, AUDIT_FIELD_ALIASES.mdContent)),
mdContent,
enabled: getAuditField(record, AUDIT_FIELD_ALIASES.enabled),
sourceRow: toText(getAuditField(record, AUDIT_FIELD_ALIASES.sourceRow)),
};
......@@ -137,6 +165,8 @@ export function normalizeAuditRecord(record) {
normalized.riskSource,
normalized.riskLevel,
toText(normalized.keywords),
normalized.mdContent,
normalized.title,
]
.filter(Boolean)
.join(' ');
......@@ -159,15 +189,19 @@ export function isAuditRecordEnabled(record) {
export async function fetchAllAuditRiskPoints(queryRecords) {
const records = [];
const seenIds = new Set();
let page = 1;
let page = AUDIT_FIRST_PAGE;
let expectedTotal = null;
while (page <= 20) {
while (page < AUDIT_FIRST_PAGE + AUDIT_MAX_PAGES) {
const res = await queryRecords({
subject: AUDIT_RISK_POINT_SUBJECT,
page,
page_size: AUDIT_PAGE_SIZE,
filter: {},
keyword: '',
full: false,
mode: 'basic',
select: null,
});
const pageRecords = normalizeRecordList(res);
expectedTotal = Number(getResponseTotal(res)) || expectedTotal;
......@@ -199,8 +233,7 @@ export async function fetchAllAuditRiskPoints(queryRecords) {
return records.filter(
(record) =>
isAuditRecordEnabled(record) &&
(record.riskDescription || record.mdContent) &&
(record.businessSegment || record.level1Project || record.level2Project),
(record.riskDescription || record.mdContent || record.title),
);
}
......@@ -232,6 +265,7 @@ export function buildRiskPointMarkdown(record, index = null) {
record.level2Project || '未分类二级项目',
].join(' / ')}`,
`- 来源行号:${record.sourceRow || '未知'}`,
`- 标题:${record.title || ''}`,
`- 业务板块:${record.businessSegment || ''}`,
`- 一级项目:${record.level1Project || ''}`,
`- 二级项目:${record.level2Project || ''}`,
......
......@@ -268,15 +268,13 @@ export async function callMcpTool(mcpName, toolName, data = {}) {
}
export async function llmChat(data, signal = undefined) {
if (!LLM_API_KEY) {
throw new Error('缺少 UMI_APP_LLM_API_KEY,请配置线上模型服务 API Key。');
}
const headers = new Headers({
Accept: 'text/event-stream',
Authorization: `Bearer ${LLM_API_KEY}`,
'Content-Type': 'application/json',
});
if (LLM_API_KEY) {
headers.set('Authorization', `Bearer ${LLM_API_KEY}`);
}
const response = await fetch(joinUrl(LLM_API_BASE, LLM_CHAT_PATH), {
method: 'POST',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment