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({ ...@@ -40,7 +40,7 @@ export default defineConfig({
pathRewrite: { '^/api': '' }, pathRewrite: { '^/api': '' },
}, },
'/llm-api': { '/llm-api': {
target: 'https://lingsuan.top', target: 'https://lingsuan.org',
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^/llm-api': '' }, pathRewrite: { '^/llm-api': '' },
}, },
......
# README # README
`@umijs/max` 模板项目,更多功能参考 [Umi Max 简介](https://umijs.org/docs/max/introduce) `@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; ...@@ -4,17 +4,33 @@ export const AUDIT_PAGE_SIZE = 2000;
export const AUDIT_PROMPT_RECORD_LIMIT = 80; export const AUDIT_PROMPT_RECORD_LIMIT = 80;
export const AUDIT_FIELD_ALIASES = { export const AUDIT_FIELD_ALIASES = {
businessSegment: ['business_segment', '业务板块'], businessSegment: ['business_segment', 'businessSegment', '业务板块'],
level1Project: ['level1_project', '一级项目'], level1Project: ['level1_project', 'level1Project', '一级项目'],
level2Project: ['level2_project', '二级项目'], level2Project: ['level2_project', 'level2Project', '二级项目'],
riskDescription: ['risk_description', '具体风险点描述'], riskDescription: [
regulationBasis: ['regulation_basis', '法规依据'], 'risk_description',
riskSource: ['risk_source', '风险点来源'], 'riskDescription',
riskLevel: ['risk_level', '风险等级'], 'description',
'content',
'text',
'具体风险点描述',
'风险点描述',
],
regulationBasis: ['regulation_basis', 'regulationBasis', '法规依据'],
riskSource: ['risk_source', 'riskSource', '风险点来源'],
riskLevel: ['risk_level', 'riskLevel', '风险等级'],
keywords: ['keywords', '关键词'], keywords: ['keywords', '关键词'],
mdContent: ['md_content', 'Markdown 内容', 'markdown_content'], mdContent: [
'md_content',
'mdContent',
'markdown_content',
'markdownContent',
'markdown',
'Markdown 内容',
'内容',
],
enabled: ['enabled', '是否启用'], enabled: ['enabled', '是否启用'],
sourceRow: ['source_row', '来源行号', '_source_row'], sourceRow: ['source_row', 'sourceRow', '来源行号', '_source_row'],
}; };
export const AUDIT_EXCEL_FIELDS = [ export const AUDIT_EXCEL_FIELDS = [
......
...@@ -51,6 +51,9 @@ const REFINE_PATTERNS = [ ...@@ -51,6 +51,9 @@ const REFINE_PATTERNS = [
'最低分', '最低分',
]; ];
const AUDIT_FIRST_PAGE = 0;
const AUDIT_MAX_PAGES = 20;
function toText(value) { function toText(value) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value.join(' '); return value.join(' ');
...@@ -65,11 +68,20 @@ export function normalizeRecordList(res) { ...@@ -65,11 +68,20 @@ export function normalizeRecordList(res) {
if (Array.isArray(res)) return res; if (Array.isArray(res)) return res;
if (Array.isArray(res?.records)) return res.records; if (Array.isArray(res?.records)) return res.records;
if (Array.isArray(res?.items)) return res.items; 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)) return res.data;
if (Array.isArray(res?.data?.records)) return res.data.records; 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?.items)) return res.data.items;
if (Array.isArray(res?.data?.list)) return res.data.list;
if (Array.isArray(res?.hits?.hits)) { 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 []; return [];
} }
...@@ -78,8 +90,16 @@ function getResponseTotal(res) { ...@@ -78,8 +90,16 @@ function getResponseTotal(res) {
const values = [ const values = [
res?.total, res?.total,
res?.count, res?.count,
res?.total_records,
res?.total_count,
res?.data?.total, res?.data?.total,
res?.data?.count, 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?.value,
res?.hits?.total, res?.hits?.total,
]; ];
...@@ -88,6 +108,10 @@ function getResponseTotal(res) { ...@@ -88,6 +108,10 @@ function getResponseTotal(res) {
function getAuditField(record, aliases = []) { function getAuditField(record, aliases = []) {
const sources = [ const sources = [
record?._source?.metadata,
record?._source?.fields,
record?._source?.data,
record?._source,
record?.metadata, record?.metadata,
record?.fields, record?.fields,
record?.data, record?.data,
...@@ -103,6 +127,12 @@ function getAuditField(record, aliases = []) { ...@@ -103,6 +127,12 @@ function getAuditField(record, aliases = []) {
} }
export function normalizeAuditRecord(record) { export function normalizeAuditRecord(record) {
const riskDescription = toText(
getAuditField(record, AUDIT_FIELD_ALIASES.riskDescription),
);
const mdContent = toText(
getAuditField(record, AUDIT_FIELD_ALIASES.mdContent),
);
const normalized = { const normalized = {
id: record?.name || record?._id || record?.id || '', id: record?.name || record?._id || record?.id || '',
title: record?.title || '', title: record?.title || '',
...@@ -115,16 +145,14 @@ export function normalizeAuditRecord(record) { ...@@ -115,16 +145,14 @@ export function normalizeAuditRecord(record) {
level2Project: toText( level2Project: toText(
getAuditField(record, AUDIT_FIELD_ALIASES.level2Project), getAuditField(record, AUDIT_FIELD_ALIASES.level2Project),
), ),
riskDescription: toText( riskDescription: riskDescription || toText(record?.title),
getAuditField(record, AUDIT_FIELD_ALIASES.riskDescription),
),
regulationBasis: toText( regulationBasis: toText(
getAuditField(record, AUDIT_FIELD_ALIASES.regulationBasis), getAuditField(record, AUDIT_FIELD_ALIASES.regulationBasis),
), ),
riskSource: toText(getAuditField(record, AUDIT_FIELD_ALIASES.riskSource)), riskSource: toText(getAuditField(record, AUDIT_FIELD_ALIASES.riskSource)),
riskLevel: toText(getAuditField(record, AUDIT_FIELD_ALIASES.riskLevel)), riskLevel: toText(getAuditField(record, AUDIT_FIELD_ALIASES.riskLevel)),
keywords: getAuditField(record, AUDIT_FIELD_ALIASES.keywords), keywords: getAuditField(record, AUDIT_FIELD_ALIASES.keywords),
mdContent: toText(getAuditField(record, AUDIT_FIELD_ALIASES.mdContent)), mdContent,
enabled: getAuditField(record, AUDIT_FIELD_ALIASES.enabled), enabled: getAuditField(record, AUDIT_FIELD_ALIASES.enabled),
sourceRow: toText(getAuditField(record, AUDIT_FIELD_ALIASES.sourceRow)), sourceRow: toText(getAuditField(record, AUDIT_FIELD_ALIASES.sourceRow)),
}; };
...@@ -137,6 +165,8 @@ export function normalizeAuditRecord(record) { ...@@ -137,6 +165,8 @@ export function normalizeAuditRecord(record) {
normalized.riskSource, normalized.riskSource,
normalized.riskLevel, normalized.riskLevel,
toText(normalized.keywords), toText(normalized.keywords),
normalized.mdContent,
normalized.title,
] ]
.filter(Boolean) .filter(Boolean)
.join(' '); .join(' ');
...@@ -159,15 +189,19 @@ export function isAuditRecordEnabled(record) { ...@@ -159,15 +189,19 @@ export function isAuditRecordEnabled(record) {
export async function fetchAllAuditRiskPoints(queryRecords) { export async function fetchAllAuditRiskPoints(queryRecords) {
const records = []; const records = [];
const seenIds = new Set(); const seenIds = new Set();
let page = 1; let page = AUDIT_FIRST_PAGE;
let expectedTotal = null; let expectedTotal = null;
while (page <= 20) { while (page < AUDIT_FIRST_PAGE + AUDIT_MAX_PAGES) {
const res = await queryRecords({ const res = await queryRecords({
subject: AUDIT_RISK_POINT_SUBJECT, subject: AUDIT_RISK_POINT_SUBJECT,
page, page,
page_size: AUDIT_PAGE_SIZE, page_size: AUDIT_PAGE_SIZE,
filter: {},
keyword: '',
full: false,
mode: 'basic', mode: 'basic',
select: null,
}); });
const pageRecords = normalizeRecordList(res); const pageRecords = normalizeRecordList(res);
expectedTotal = Number(getResponseTotal(res)) || expectedTotal; expectedTotal = Number(getResponseTotal(res)) || expectedTotal;
...@@ -199,8 +233,7 @@ export async function fetchAllAuditRiskPoints(queryRecords) { ...@@ -199,8 +233,7 @@ export async function fetchAllAuditRiskPoints(queryRecords) {
return records.filter( return records.filter(
(record) => (record) =>
isAuditRecordEnabled(record) && isAuditRecordEnabled(record) &&
(record.riskDescription || record.mdContent) && (record.riskDescription || record.mdContent || record.title),
(record.businessSegment || record.level1Project || record.level2Project),
); );
} }
...@@ -232,6 +265,7 @@ export function buildRiskPointMarkdown(record, index = null) { ...@@ -232,6 +265,7 @@ export function buildRiskPointMarkdown(record, index = null) {
record.level2Project || '未分类二级项目', record.level2Project || '未分类二级项目',
].join(' / ')}`, ].join(' / ')}`,
`- 来源行号:${record.sourceRow || '未知'}`, `- 来源行号:${record.sourceRow || '未知'}`,
`- 标题:${record.title || ''}`,
`- 业务板块:${record.businessSegment || ''}`, `- 业务板块:${record.businessSegment || ''}`,
`- 一级项目:${record.level1Project || ''}`, `- 一级项目:${record.level1Project || ''}`,
`- 二级项目:${record.level2Project || ''}`, `- 二级项目:${record.level2Project || ''}`,
......
...@@ -268,15 +268,13 @@ export async function callMcpTool(mcpName, toolName, data = {}) { ...@@ -268,15 +268,13 @@ export async function callMcpTool(mcpName, toolName, data = {}) {
} }
export async function llmChat(data, signal = undefined) { 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({ const headers = new Headers({
Accept: 'text/event-stream', Accept: 'text/event-stream',
Authorization: `Bearer ${LLM_API_KEY}`,
'Content-Type': 'application/json', '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), { const response = await fetch(joinUrl(LLM_API_BASE, LLM_CHAT_PATH), {
method: 'POST', 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