import type { InternalAxiosRequestConfig } from '@vben/request';
|
|
import {
|
defaultResponseInterceptor,
|
RequestClient,
|
} from '@vben/request';
|
import { useAccessStore } from '@vben/stores';
|
|
import {
|
bindImportAttachment,
|
createImportSession,
|
} from '#/api/mdm/item/agent';
|
import { uploadFile } from '#/api/system/storage';
|
|
/**
|
* 模块数据补录会话服务(Python)不在 /admin-api 下,
|
* 开发期由 Vite 代理 /agent-api 转发到 127.0.0.1:8091,且不返回 CommonResult 包装。
|
*/
|
const agentClient = new RequestClient({
|
baseURL: '/agent-api',
|
responseReturn: 'body',
|
timeout: 120_000,
|
});
|
|
agentClient.addRequestInterceptor({
|
fulfilled: (config: InternalAxiosRequestConfig) => {
|
const token = useAccessStore().accessToken;
|
if (token) {
|
config.headers.Authorization = `Bearer ${token}`;
|
}
|
return config;
|
},
|
});
|
|
// Python 服务直接返回业务 JSON,不套 CommonResult;responseReturn: 'body' 时原样返回响应体。
|
agentClient.addResponseInterceptor(
|
defaultResponseInterceptor({
|
codeField: 'code',
|
dataField: 'data',
|
successCode: 0,
|
}),
|
);
|
|
export namespace AgentConversationApi {
|
export interface FieldSpec {
|
name: string;
|
label: string;
|
required: boolean;
|
kind: string;
|
}
|
|
export interface ModuleSpec {
|
module_id: string;
|
title: string;
|
capability: string;
|
bridge: string;
|
permission: string;
|
description: string;
|
required_fields: string[];
|
fields: FieldSpec[];
|
}
|
|
export interface Capabilities {
|
version: string;
|
formats: string[];
|
ocr: boolean;
|
cross_file_dedupe: boolean;
|
import_modes: string[];
|
notes: string[];
|
modules: ModuleSpec[];
|
durable_tasks?: {
|
enabled: boolean;
|
events: string;
|
fact_source: string;
|
transport: string;
|
};
|
}
|
|
export interface AuthorizedCapability {
|
tool: string;
|
module: string;
|
operation: string;
|
allowed_fields: string[];
|
scope_summary: string;
|
can_execute: boolean;
|
}
|
|
export interface AuthorizedCapabilityCatalog {
|
policy_revision: string;
|
authorization_scope: string;
|
scope_summary: string;
|
capabilities: AuthorizedCapability[];
|
}
|
|
export interface BusinessModelInfo {
|
id: number;
|
name: string;
|
model: string;
|
platform: string;
|
temperature?: number | null;
|
max_tokens?: number | null;
|
max_contexts?: number | null;
|
}
|
|
export interface Issue {
|
code?: string;
|
field?: string;
|
message: string;
|
candidate_id?: string;
|
}
|
|
export interface Summary {
|
total?: number;
|
create?: number;
|
skip?: number;
|
blocked?: number;
|
success?: number;
|
failed?: number;
|
skipped?: number;
|
unknown?: number;
|
}
|
|
export interface ReceiptRow {
|
candidate_id: string;
|
row_no?: number;
|
code?: string;
|
name?: string;
|
action: string;
|
status: string;
|
target_item_id?: number;
|
error_code?: string;
|
error_message?: string;
|
}
|
|
export interface Receipt {
|
command_id: string;
|
idempotency_key: string;
|
status: string;
|
attachment_id: number;
|
source_sha256: string;
|
preflight_hash: string;
|
summary: Summary;
|
rows: ReceiptRow[];
|
create_time?: string;
|
completed_time?: string;
|
error_code?: string;
|
error_message?: string;
|
}
|
|
export interface ModuleCandidate {
|
module_id: string;
|
title: string;
|
capability: string;
|
description: string;
|
}
|
|
export interface MissingField {
|
name: string;
|
label: string;
|
}
|
|
/** 卡片字段按 kind 复用,未使用的字段为空。 */
|
export interface Card {
|
kind: string;
|
title?: string;
|
body?: string;
|
module_id?: string;
|
module_title?: string;
|
capability?: string;
|
revision?: number;
|
issues?: Issue[];
|
warnings?: Issue[];
|
repair_guidance?: Array<{ field?: string; guidance: string }>;
|
summary?: Summary;
|
can_commit?: boolean;
|
side_effects?: string[];
|
sync_mes?: boolean | null;
|
total?: number;
|
actions?: string[];
|
notice?: string;
|
candidates?: ModuleCandidate[];
|
missing_fields?: MissingField[];
|
columns?: string[];
|
command_id?: string;
|
status?: string;
|
rows?: ReceiptRow[];
|
}
|
|
export interface Message {
|
message_id: string;
|
role: string;
|
type: string;
|
text: string;
|
cards?: Card[];
|
}
|
|
export interface Interrupt {
|
interrupt_id: string;
|
kind: string;
|
revision: number;
|
validation_revision?: number | null;
|
card: Card;
|
}
|
|
export interface RowView {
|
candidate_id: string;
|
values: Record<string, string>;
|
issues: unknown[];
|
warnings: unknown[];
|
}
|
|
export interface ConversationAttachment {
|
attachment_id: string;
|
/** 仅最新附件解析出文件名;历史附件可能为空。 */
|
filename: string;
|
}
|
|
export interface Conversation {
|
conversation_id: string;
|
title: string;
|
status: string;
|
module_id: string;
|
module_title: string;
|
capability: string;
|
data_scope_summary: string;
|
model_id?: number | null;
|
policy_revision: string;
|
revision: number;
|
validation_revision?: number | null;
|
sync_mes?: boolean;
|
total: number;
|
summary: Summary;
|
issues: Issue[];
|
warnings: Issue[];
|
missing_fields: string[];
|
columns: string[];
|
/** 候选行实际有值的字段,用于渲染明细表。 */
|
row_fields: Array<{ name: string; label: string }>;
|
attachments: ConversationAttachment[];
|
messages: Message[];
|
interrupts: Interrupt[];
|
/** 未提交时后端返回空对象 {},提交后才有完整回执。 */
|
receipt?: null | Partial<Receipt>;
|
last_error: string;
|
rows: RowView[];
|
created_at: string;
|
updated_at: string;
|
finished: boolean;
|
commit_enabled: boolean;
|
mes_sync_default: boolean;
|
}
|
|
export interface ConversationSummary {
|
conversation_id: string;
|
title: string;
|
status: string;
|
module_id: string;
|
module_title: string;
|
revision: number;
|
created_at: string;
|
updated_at: string;
|
}
|
|
export interface PageResult {
|
items: ConversationSummary[];
|
total: number;
|
page: number;
|
page_size: number;
|
}
|
|
export interface ConversationEvent {
|
conversation_id: string;
|
payload: Record<string, unknown>;
|
seq: number;
|
type:
|
| 'card'
|
| 'error'
|
| 'message'
|
| 'progress'
|
| 'receipt'
|
| 'resync'
|
| 'status';
|
}
|
|
export interface CreateReq {
|
title?: string;
|
text?: string;
|
attachment_id?: string;
|
module_id?: string;
|
model_id?: number;
|
constraints?: ConversationConstraints;
|
}
|
|
export interface ConversationConstraints {
|
scenario_paths: string[];
|
time_start?: string;
|
time_end?: string;
|
record_limit?: number;
|
industry?: string;
|
skip_rest_days?: boolean;
|
rest_day_description?: string;
|
region?: string;
|
instruction?: string;
|
priority_fields: string[];
|
}
|
|
export interface ActionReq {
|
interrupt_id: string;
|
revision: number;
|
action: string;
|
payload: Record<string, unknown>;
|
model_id?: number;
|
}
|
|
export interface MessageReq {
|
text?: string;
|
attachment_id?: string;
|
model_id?: number;
|
}
|
}
|
|
export namespace AgentTaskApi {
|
export interface Progress {
|
mode: 'determinate' | 'indeterminate';
|
completed: number;
|
total: null | number;
|
percent: null | number;
|
unit?: string;
|
}
|
|
export interface Result {
|
attachment_id?: number;
|
attachment_ids?: number[];
|
candidate_count?: number;
|
continuation_supported?: boolean;
|
evidence_ref?: {
|
backend: string;
|
candidate_count?: number;
|
revision: string;
|
task_id: string;
|
} | null;
|
filename?: string;
|
filenames?: string[];
|
module_id?: string;
|
module_title?: string;
|
result_kind?: string;
|
source_sha256?: string;
|
source_sha256s?: string[];
|
review_summary?: {
|
conflict_groups: number;
|
exact_duplicates_merged: number;
|
incomplete_keys: number;
|
requires_review: boolean;
|
};
|
warnings?: string[];
|
}
|
|
export interface Task {
|
task_id: string;
|
owner_id: string;
|
kind: string;
|
status: string;
|
stage: string;
|
attempt: number;
|
sequence: number;
|
progress: Progress;
|
message: string;
|
result: null | Result;
|
error: null | { code?: string; message?: string };
|
allowed_actions: string[];
|
created_at: null | string;
|
updated_at: null | string;
|
started_at: null | string;
|
finished_at: null | string;
|
}
|
|
export interface PageResult {
|
items: Task[];
|
total: number;
|
page: number;
|
page_size: number;
|
}
|
|
export interface CreateReq {
|
attachment_id?: number;
|
attachment_ids?: number[];
|
module_id?: string;
|
constraints?: AgentConversationApi.ConversationConstraints;
|
}
|
}
|
|
/** 能力声明:支持的格式、模块与限制 */
|
export function getAgentCapabilities() {
|
return agentClient.get<AgentConversationApi.Capabilities>('/v1/capabilities');
|
}
|
|
/** 读取当前登录用户经 Java 后端裁剪后的可查询、可操作能力。 */
|
export function getAuthorizedAgentCapabilities() {
|
return agentClient.get<AgentConversationApi.AuthorizedCapabilityCatalog>(
|
'/v1/authorization/capabilities',
|
);
|
}
|
|
/** 当前数据补录链路实际使用的业务模型;不返回 API 密钥或服务地址。 */
|
export function getActiveBusinessModel() {
|
return agentClient.get<AgentConversationApi.BusinessModelInfo>('/v1/model');
|
}
|
|
/** 可切换的业务模型列表,按后端排序返回,默认使用第一个。 */
|
export function getAgentModels() {
|
return agentClient.get<AgentConversationApi.BusinessModelInfo[]>('/v1/models');
|
}
|
|
/** 分页读取当前用户的持久后台任务。 */
|
export function listAgentTasks(params?: {
|
page?: number;
|
page_size?: number;
|
status?: string;
|
}) {
|
return agentClient.get<AgentTaskApi.PageResult>('/v1/tasks', { params });
|
}
|
|
/** 创建文档后台分析任务,服务端立即返回 HTTP 202 快照。 */
|
export function createDocumentTask(data: AgentTaskApi.CreateReq) {
|
return agentClient.post<AgentTaskApi.Task>('/v1/tasks/document-onboarding', data);
|
}
|
|
/** 读取任务事实快照。 */
|
export function getAgentTask(taskId: string) {
|
return agentClient.get<AgentTaskApi.Task>(`/v1/tasks/${taskId}`);
|
}
|
|
/** 取消任务并撤销短期委托票据。 */
|
export function cancelAgentTask(taskId: string) {
|
return agentClient.delete<AgentTaskApi.Task>(`/v1/tasks/${taskId}`);
|
}
|
|
/** 使用认证请求读取任务 SSE;游标用于断线续传。 */
|
export async function streamAgentTaskEvents(
|
taskId: string,
|
since: number,
|
signal: AbortSignal,
|
onEvent: (event: AgentTaskApi.Task) => void,
|
) {
|
const token = useAccessStore().accessToken;
|
const response = await fetch(
|
`/agent-api/v1/tasks/${encodeURIComponent(taskId)}/events?since=${since}`,
|
{
|
credentials: 'same-origin',
|
headers: {
|
Accept: 'text/event-stream',
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
},
|
signal,
|
},
|
);
|
if (!response.ok) throw new Error(`任务事件连接失败(HTTP ${response.status})`);
|
if (!response.body) throw new Error('当前浏览器不支持流式事件');
|
const reader = response.body.getReader();
|
const decoder = new TextDecoder();
|
let buffer = '';
|
let cursor = since;
|
while (true) {
|
const { done, value } = await reader.read();
|
buffer += decoder.decode(value, { stream: !done }).replaceAll('\r\n', '\n');
|
const frames = buffer.split('\n\n');
|
buffer = frames.pop() ?? '';
|
for (const frame of frames) {
|
const data = frame
|
.split('\n')
|
.filter((line) => line.startsWith('data:'))
|
.map((line) => line.slice(5).trimStart())
|
.join('\n');
|
if (!data) continue;
|
const event = JSON.parse(data) as AgentTaskApi.Task;
|
if (!Number.isSafeInteger(event.sequence) || event.sequence <= cursor) continue;
|
cursor = event.sequence;
|
onEvent(event);
|
}
|
if (done) break;
|
}
|
return cursor;
|
}
|
|
/** 会话列表 */
|
export function listAgentConversations(params?: {
|
page?: number;
|
page_size?: number;
|
status?: string;
|
}) {
|
return agentClient.get<AgentConversationApi.PageResult>(
|
'/v1/conversations',
|
{ params },
|
);
|
}
|
|
/** 新建补录会话 */
|
export function createAgentConversation(data: AgentConversationApi.CreateReq) {
|
return agentClient.post<AgentConversationApi.Conversation>(
|
'/v1/conversations',
|
data,
|
);
|
}
|
|
/** 读取会话 */
|
export function getAgentConversation(conversationId: string) {
|
return agentClient.get<AgentConversationApi.Conversation>(
|
`/v1/conversations/${conversationId}`,
|
);
|
}
|
|
/** 删除会话:同时清理检查点、事件与 Java 侧补录附件 */
|
export function deleteAgentConversation(conversationId: string) {
|
return agentClient.delete<{
|
attachments_deleted: number;
|
conversation_id: string;
|
deleted: boolean;
|
}>(`/v1/conversations/${conversationId}`);
|
}
|
|
/**
|
* 读取会话附件。mode 为 inline 时 PDF/图片由浏览器直接预览,
|
* 其余类型服务端会自动降级为下载。
|
*/
|
export function downloadAgentConversationAttachment(
|
conversationId: string,
|
attachmentId: string,
|
mode: 'download' | 'inline',
|
) {
|
return agentClient.download<{
|
data: Blob;
|
headers: Record<string, string>;
|
}>(`/v1/conversations/${conversationId}/attachment`, {
|
params: { attachment_id: attachmentId, mode },
|
responseReturn: 'raw',
|
});
|
}
|
|
/**
|
* 使用 fetch 读取带认证头的 SSE。EventSource 不能设置 Authorization,不能用于本接口。
|
* 服务端会主动结束短连接;调用方负责携带返回游标重连。
|
*/
|
export async function streamAgentConversationEvents(
|
conversationId: string,
|
since: number,
|
signal: AbortSignal,
|
onEvent: (event: AgentConversationApi.ConversationEvent) => void,
|
onOpen?: () => void,
|
) {
|
const token = useAccessStore().accessToken;
|
const response = await fetch(
|
`/agent-api/v1/conversations/${encodeURIComponent(conversationId)}/events?since=${since}`,
|
{
|
credentials: 'same-origin',
|
headers: {
|
Accept: 'text/event-stream',
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
},
|
signal,
|
},
|
);
|
if (!response.ok) {
|
throw new Error(`事件连接失败(HTTP ${response.status})`);
|
}
|
if (!response.body) {
|
throw new Error('当前浏览器不支持流式事件');
|
}
|
onOpen?.();
|
const reader = response.body.getReader();
|
const decoder = new TextDecoder();
|
let buffer = '';
|
let cursor = since;
|
while (true) {
|
const { done, value } = await reader.read();
|
buffer += decoder.decode(value, { stream: !done }).replaceAll('\r\n', '\n');
|
const frames = buffer.split('\n\n');
|
buffer = frames.pop() ?? '';
|
for (const frame of frames) {
|
const data = frame
|
.split('\n')
|
.filter((line) => line.startsWith('data:'))
|
.map((line) => line.slice(5).trimStart())
|
.join('\n');
|
if (!data) continue;
|
const event = JSON.parse(data) as AgentConversationApi.ConversationEvent;
|
if (!Number.isSafeInteger(event.seq) || event.seq <= cursor) continue;
|
cursor = event.seq;
|
onEvent(event);
|
}
|
if (done) break;
|
}
|
return cursor;
|
}
|
|
/** 发送文本或附件(用于补充上传文件) */
|
export function sendAgentMessage(
|
conversationId: string,
|
data: AgentConversationApi.MessageReq,
|
) {
|
return agentClient.post<AgentConversationApi.Conversation>(
|
`/v1/conversations/${conversationId}/messages`,
|
data,
|
);
|
}
|
|
/** 卡片操作:选择场景、确认映射、修订、确认或取消 */
|
export function sendAgentAction(
|
conversationId: string,
|
data: AgentConversationApi.ActionReq,
|
) {
|
return agentClient.post<AgentConversationApi.Conversation>(
|
`/v1/conversations/${conversationId}/actions`,
|
data,
|
);
|
}
|
|
/** 下载导入报表(xlsx) */
|
export function downloadAgentReport(conversationId: string) {
|
return agentClient.download<{
|
data: Blob;
|
headers: Record<string, string>;
|
}>(`/v1/conversations/${conversationId}/report`, {
|
responseReturn: 'raw',
|
});
|
}
|
|
/**
|
* 上传附件到 Java 存储并绑定补录会话,返回 attachmentId。
|
* Python 侧通过 Java Bridge 按 attachmentId 读取文件。
|
*/
|
export async function uploadAgentAttachment(file: File) {
|
const sessionId = await createImportSession();
|
const blobs = await uploadFile([file]);
|
const blobId = blobs?.[0]?.id;
|
if (!blobId) {
|
throw new Error('文件上传失败,未取得 blobId');
|
}
|
return await bindImportAttachment(sessionId, blobId);
|
}
|
|
export { agentImportErrorMessage } from '#/api/mdm/item/agent';
|