|
|
@@ -1,453 +0,0 @@
|
|
|
-import { defHttp } from '/@/utils/http/axios/index';
|
|
|
-import { getToken } from '/@/utils/auth/index';
|
|
|
-import { consumeSse } from './sse';
|
|
|
-import type { SseEvent } from '/@/views/ventAI/manageAssistent/types/index';
|
|
|
-
|
|
|
-enum Api {
|
|
|
- usageToday = '/ventAI/api/usage/today',
|
|
|
- unified = '/ventAI/api/chat',
|
|
|
- getHistoryList = '/ventAI/api/sessions',
|
|
|
- getDetail = '/ventAI/api/chat/history/',
|
|
|
- deleteSession = '/ventAI/api/sessions/',
|
|
|
- deleteMessage = '/ventAI/api/messages/',
|
|
|
- chatResume = '/ventAI/api/chat/resume',
|
|
|
- getModelMsg = '/ventAI/api/model',
|
|
|
- switchThinking = '/ventAI/api/model/thinking',
|
|
|
- getSkills = '/ventAI/api/skills',
|
|
|
- schedules = '/ventAI/api/schedules',
|
|
|
- downloadReport = '/ventAI/api/download/report/',
|
|
|
- saveReport = '/ventAI/api/report/save/',
|
|
|
- editWord = '/ventAI/api/report/edit-word',
|
|
|
-}
|
|
|
-
|
|
|
-export interface UsageByModel {
|
|
|
- model: string;
|
|
|
- cache_hit_tokens: number;
|
|
|
- cache_miss_tokens: number;
|
|
|
- output_tokens: number;
|
|
|
- tokens: number;
|
|
|
- request_count: number;
|
|
|
-}
|
|
|
-
|
|
|
-export interface TodayUsage {
|
|
|
- date: string;
|
|
|
- limit_tokens: number;
|
|
|
- spent_tokens: number;
|
|
|
- remaining_tokens: number;
|
|
|
- request_count: number;
|
|
|
- by_model: UsageByModel[];
|
|
|
-}
|
|
|
-
|
|
|
-interface ApiErrorDetail {
|
|
|
- code?: string;
|
|
|
- message?: string;
|
|
|
- retry_after?: number;
|
|
|
- limit_tokens?: number;
|
|
|
- spent_tokens?: number;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 流式请求被服务端以普通 JSON 拒绝时抛出的错误(429 额度耗尽 / 500 计费异常等)
|
|
|
- */
|
|
|
-export class StreamError extends Error {
|
|
|
- status: number;
|
|
|
- code?: string;
|
|
|
- retryAfter?: number;
|
|
|
- limitTokens?: number;
|
|
|
- spentTokens?: number;
|
|
|
-
|
|
|
- constructor(status: number, message: string, detail?: ApiErrorDetail) {
|
|
|
- super(message);
|
|
|
- this.name = 'StreamError';
|
|
|
- this.status = status;
|
|
|
- this.code = detail?.code;
|
|
|
- this.retryAfter = detail?.retry_after;
|
|
|
- this.limitTokens = detail?.limit_tokens;
|
|
|
- this.spentTokens = detail?.spent_tokens;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const throwStreamHttpError = async (response: Response): Promise<never> => {
|
|
|
- let detail: ApiErrorDetail | undefined;
|
|
|
- let message = `HTTP错误: ${response.status}`;
|
|
|
- try {
|
|
|
- const body = await response.json();
|
|
|
- if (body?.detail) {
|
|
|
- if (typeof body.detail === 'string') {
|
|
|
- message = body.detail;
|
|
|
- } else {
|
|
|
- detail = body.detail as ApiErrorDetail;
|
|
|
- message = detail.message || message;
|
|
|
- }
|
|
|
- }
|
|
|
- } catch {
|
|
|
- // 响应体不是 JSON 时保留默认错误信息
|
|
|
- }
|
|
|
- throw new StreamError(response.status, message, detail);
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 查询今日用量与额度
|
|
|
- */
|
|
|
-export const getTodayUsage = () => defHttp.get({ url: Api.usageToday }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 历史会话接口
|
|
|
- */
|
|
|
-export const getHistoryList = (params) => defHttp.get({ url: Api.getHistoryList, params }, { joinParamsToUrl: true, isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 获取历史会话详情
|
|
|
- * @param session_id - 会话ID
|
|
|
- */
|
|
|
-export const getDetail = (session_id: string) => defHttp.get({ url: Api.getDetail + session_id }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 删除会话
|
|
|
- * @param session_id - 会话ID
|
|
|
- */
|
|
|
-export const deleteSession = (session_id: string) => defHttp.delete({ url: Api.deleteSession + session_id }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 删除单条消息(按消息主键 id)
|
|
|
- * 删除后服务端会清空该会话的内存线程,下轮对话从数据库重建最近 40 条历史
|
|
|
- * @param message_id - 消息主键 id(来自历史详情 messages[].id)
|
|
|
- */
|
|
|
-export const deleteMessage = (message_id: number) => defHttp.delete({ url: Api.deleteMessage + message_id }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 置顶会话
|
|
|
- * @param session_id - 会话ID
|
|
|
- */
|
|
|
-export const pinSession = (session_id: string) => defHttp.put({ url: `/ventAI/api/sessions/${session_id}/pin` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 编辑会话名称
|
|
|
- * @param session_id - 会话ID
|
|
|
- * @param title - 新标题
|
|
|
- */
|
|
|
-export const editSessionTitle = (session_id: string, title: string) =>
|
|
|
- defHttp.put({ url: `/ventAI/api/sessions/${session_id}/title`, data: { title } }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 获取对话权限模式
|
|
|
- * @param session_id - 会话ID
|
|
|
- */
|
|
|
-export const getSessionMode = (session_id: string) => defHttp.get({ url: `/ventAI/api/sessions/${session_id}/mode` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 设置对话权限模式
|
|
|
- * @param session_id - 会话ID
|
|
|
- */
|
|
|
-export const putSessionMode = (session_id: string, mode: string) =>
|
|
|
- defHttp.put({ url: `/ventAI/api/sessions/${session_id}/mode`, data: { mode } }, { isTransformResponse: false });
|
|
|
-/**
|
|
|
- * 上下文用量查询
|
|
|
- * @param session_id - 会话ID
|
|
|
- */
|
|
|
-export const getContext = (session_id: string) => defHttp.get({ url: `/ventAI/api/sessions/${session_id}/context` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 查询模型信息
|
|
|
- */
|
|
|
-export const getModelMsg = () => defHttp.get({ url: Api.getModelMsg }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 切换模型思考深度
|
|
|
- * @param level - 思考级别 off/high/highest
|
|
|
- */
|
|
|
-export const switchThinking = (level: string) => defHttp.post({ url: Api.switchThinking, data: { level } }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 查询模型技能
|
|
|
- */
|
|
|
-export const getSkills = () => defHttp.get({ url: Api.getSkills }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 查询模型技能详情
|
|
|
- */
|
|
|
-export const getSkillDetails = (name: string) => defHttp.get({ url: Api.getSkills + '/' + name }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 启用/禁用模型技能
|
|
|
- */
|
|
|
-export const toggleSkill = (name: string) => defHttp.post({ url: Api.getSkills + '/' + name + '/toggle' }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 上传新技能
|
|
|
- * @param file - zip附件
|
|
|
- */
|
|
|
-export const uploadSkill = (file: File) => {
|
|
|
- const formData = new FormData();
|
|
|
- formData.append('file', file);
|
|
|
- return defHttp.post(
|
|
|
- { url: Api.getSkills + '/upload', data: formData, headers: { 'Content-Type': 'multipart/form-data' } },
|
|
|
- { isTransformResponse: false }
|
|
|
- );
|
|
|
-};
|
|
|
-/**
|
|
|
- * 统一对话接口(SSE流式响应)
|
|
|
- * @param params.message - 用户输入的文本,必填
|
|
|
- * @param params.session_id - 会话唯一标识ID,不传参时服务端自动生成全新会话ID
|
|
|
- * @param params.file - 上传的PDF文件,可选
|
|
|
- * @param onChunk - 流式数据回调函数
|
|
|
- * @returns Promise<{ session_id: string; parse_errors: number }>
|
|
|
- * parse_errors 大于 0 表示这条流里有事件被丢弃(正文可能不完整)
|
|
|
- */
|
|
|
-export const unifiedStream = async (
|
|
|
- params: { message: string; session_id?: string; file?: File; mode?: string; signal?: AbortSignal },
|
|
|
- onChunk: (data: SseEvent) => void
|
|
|
-): Promise<{ session_id: string; parse_errors: number }> => {
|
|
|
- try {
|
|
|
- const formData = new FormData();
|
|
|
- formData.append('message', params.message);
|
|
|
- if (params.session_id) {
|
|
|
- formData.append('session_id', params.session_id);
|
|
|
- }
|
|
|
- if (params.file) {
|
|
|
- formData.append('file', params.file);
|
|
|
- }
|
|
|
- if (params.mode) {
|
|
|
- formData.append('mode', params.mode);
|
|
|
- }
|
|
|
-
|
|
|
- const response = await fetch(Api.unified, {
|
|
|
- method: 'POST',
|
|
|
- body: formData,
|
|
|
- headers: { 'X-Access-Token': getToken() },
|
|
|
- signal: params.signal,
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- await throwStreamHttpError(response);
|
|
|
- }
|
|
|
-
|
|
|
- const { sessionId, parseErrors } = await consumeSse<SseEvent>(response, onChunk);
|
|
|
-
|
|
|
- // parseErrors > 0 表示这条流里有事件被丢弃,正文可能不完整
|
|
|
- return { session_id: sessionId, parse_errors: parseErrors };
|
|
|
- } catch (error) {
|
|
|
- if ((error as Error)?.name !== 'AbortError') {
|
|
|
- console.error('统一对话流式请求失败:', error);
|
|
|
- }
|
|
|
- throw error;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * PDF审查接口(SSE流式响应)
|
|
|
- * @param params.file - 配风计划PDF文件,必填
|
|
|
- * @param params.session_id - 会话唯一标识ID,可选
|
|
|
- * @param params.message - 附加消息/审查指令,可选
|
|
|
- * @param onChunk - 流式数据回调函数
|
|
|
- * @returns Promise<{ session_id: string; parse_errors: number }>
|
|
|
- * parse_errors 大于 0 表示这条流里有事件被丢弃(正文可能不完整)
|
|
|
- */
|
|
|
-export const reviewPdfStream = async (
|
|
|
- params: { file: File; session_id?: string; message?: string; mode?: string; signal?: AbortSignal },
|
|
|
- onChunk: (data: SseEvent) => void
|
|
|
-): Promise<{ session_id: string; parse_errors: number }> => {
|
|
|
- try {
|
|
|
- const formData = new FormData();
|
|
|
- formData.append('file', params.file);
|
|
|
- if (params.session_id) {
|
|
|
- formData.append('session_id', params.session_id);
|
|
|
- }
|
|
|
- if (params.message) {
|
|
|
- formData.append('message', params.message);
|
|
|
- }
|
|
|
- if (params.mode) {
|
|
|
- formData.append('mode', params.mode);
|
|
|
- }
|
|
|
-
|
|
|
- const response = await fetch(Api.unified, {
|
|
|
- method: 'POST',
|
|
|
- body: formData,
|
|
|
- headers: { 'X-Access-Token': getToken() },
|
|
|
- signal: params.signal,
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- await throwStreamHttpError(response);
|
|
|
- }
|
|
|
-
|
|
|
- const { sessionId, parseErrors } = await consumeSse<SseEvent>(response, onChunk);
|
|
|
-
|
|
|
- // parseErrors > 0 表示这条流里有事件被丢弃,正文可能不完整
|
|
|
- return { session_id: sessionId, parse_errors: parseErrors };
|
|
|
- } catch (error) {
|
|
|
- if ((error as Error)?.name !== 'AbortError') {
|
|
|
- console.error('PDF审查流式请求失败:', error);
|
|
|
- }
|
|
|
- throw error;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 恢复会话接口(SSE流式响应)
|
|
|
- * @param params.session_id - 会话ID,必填
|
|
|
- * @param params.thread_id - LangGraph线程ID,可选
|
|
|
- * @param params.status - ask_user响应状态:"answered"(默认,消费answers)/ "cancelled"(取消)/ "error"(出错)
|
|
|
- * @param params.answers - ask_user答案列表,与中断事件ask_user.questions一一对应(status=answered时必填)
|
|
|
- * @param onChunk - 流式数据回调函数
|
|
|
- * @returns Promise<{ session_id: string; parse_errors: number }>
|
|
|
- * parse_errors 大于 0 表示这条流里有事件被丢弃(正文可能不完整)
|
|
|
- */
|
|
|
-export const chatResumeStream = async (
|
|
|
- params: { session_id: string; thread_id?: string; status?: string; answers?: string[]; signal?: AbortSignal },
|
|
|
- onChunk: (data: SseEvent) => void
|
|
|
-): Promise<{ session_id: string; parse_errors: number }> => {
|
|
|
- try {
|
|
|
- const body: Record<string, any> = {
|
|
|
- session_id: params.session_id,
|
|
|
- status: params.status || 'answered',
|
|
|
- };
|
|
|
- if (params.thread_id) {
|
|
|
- body.thread_id = params.thread_id;
|
|
|
- }
|
|
|
- if (params.answers) {
|
|
|
- body.answers = params.answers;
|
|
|
- }
|
|
|
-
|
|
|
- const response = await fetch(Api.chatResume, {
|
|
|
- method: 'POST',
|
|
|
- body: JSON.stringify(body),
|
|
|
- headers: {
|
|
|
- 'Content-Type': 'application/json',
|
|
|
- 'X-Access-Token': getToken(),
|
|
|
- },
|
|
|
- signal: params.signal,
|
|
|
- });
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- await throwStreamHttpError(response);
|
|
|
- }
|
|
|
-
|
|
|
- const { sessionId, parseErrors } = await consumeSse<SseEvent>(response, onChunk);
|
|
|
-
|
|
|
- // parseErrors > 0 表示这条流里有事件被丢弃,正文可能不完整
|
|
|
- return { session_id: sessionId, parse_errors: parseErrors };
|
|
|
- } catch (error) {
|
|
|
- if ((error as Error)?.name !== 'AbortError') {
|
|
|
- console.error('恢复会话流式请求失败:', error);
|
|
|
- }
|
|
|
- throw error;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 定时任务接口
|
|
|
- */
|
|
|
-export const createSchedule = (data: {
|
|
|
- name: string;
|
|
|
- schedule?: { freq: string; hour?: number; minute?: number; day_of_week?: number; day_of_month?: number };
|
|
|
- cron_expr?: string;
|
|
|
- prompt: string;
|
|
|
- notify_webhook?: string;
|
|
|
-}) => defHttp.post({ url: Api.schedules, data }, { isTransformResponse: false });
|
|
|
-
|
|
|
-export const getScheduleList = (include_disabled = true) =>
|
|
|
- defHttp.get({ url: Api.schedules, params: { include_disabled } }, { joinParamsToUrl: true, isTransformResponse: false });
|
|
|
-
|
|
|
-export const getScheduleDetail = (task_id: number) => defHttp.get({ url: `${Api.schedules}/${task_id}` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-export const updateSchedule = (
|
|
|
- task_id: number,
|
|
|
- data: { name?: string; schedule?: Record<string, any>; cron_expr?: string; prompt?: string; notify_webhook?: string }
|
|
|
-) => defHttp.put({ url: `${Api.schedules}/${task_id}`, data }, { isTransformResponse: false });
|
|
|
-
|
|
|
-export const pauseSchedule = (task_id: number) => defHttp.post({ url: `${Api.schedules}/${task_id}/pause` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-export const resumeSchedule = (task_id: number) => defHttp.post({ url: `${Api.schedules}/${task_id}/resume` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-export const deleteSchedule = (id: number) => defHttp.delete({ url: `${Api.schedules}/${id}` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-export const getScheduleRuns = (task_id: number, limit = 50) =>
|
|
|
- defHttp.get({ url: `${Api.schedules}/${task_id}/runs`, params: { limit } }, { joinParamsToUrl: true, isTransformResponse: false });
|
|
|
-
|
|
|
-export const runScheduleNow = (task_id: number) => defHttp.post({ url: `${Api.schedules}/${task_id}/run` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 获取子智能体列表
|
|
|
- */
|
|
|
-export const getSubAgents = () => defHttp.get({ url: '/ventAI/api/subagents' }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 获取工具注册表与模型列表(表单用)
|
|
|
- */
|
|
|
-export const getSubAgentTools = () => defHttp.get({ url: '/ventAI/api/subagents/tools' }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 创建子智能体
|
|
|
- */
|
|
|
-export const createSubAgent = (data: {
|
|
|
- name: string;
|
|
|
- description: string;
|
|
|
- system_prompt: string;
|
|
|
- tools?: string[];
|
|
|
- model?: string;
|
|
|
- enabled?: boolean;
|
|
|
-}) => defHttp.post({ url: '/ventAI/api/subagents', data }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 获取子智能体详情
|
|
|
- */
|
|
|
-export const getSubAgentDetail = (name: string) => defHttp.get({ url: `/ventAI/api/subagents/${name}` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 更新子智能体
|
|
|
- */
|
|
|
-export const updateSubAgent = (
|
|
|
- name: string,
|
|
|
- data: { description?: string; system_prompt?: string; tools?: string[]; model?: string; enabled?: boolean }
|
|
|
-) => defHttp.put({ url: `/ventAI/api/subagents/${name}`, data }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 删除子智能体
|
|
|
- */
|
|
|
-export const deleteSubAgent = (name: string) => defHttp.delete({ url: `/ventAI/api/subagents/${name}` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 启用/禁用子智能体
|
|
|
- */
|
|
|
-export const toggleSubAgent = (name: string) => defHttp.post({ url: `/ventAI/api/subagents/${name}/toggle` }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 报告下载
|
|
|
- * 从 URL 或文件名中提取纯文件名,构建 /api/download/report/{filename} 地址
|
|
|
- */
|
|
|
-export const buildReportDownloadUrl = (urlOrFilename: string) => {
|
|
|
- const filename = urlOrFilename.split('/').pop() || urlOrFilename;
|
|
|
- return `${Api.downloadReport}${encodeURIComponent(filename)}`;
|
|
|
-};
|
|
|
-
|
|
|
-/**
|
|
|
- * 覆盖保存报告(文本)
|
|
|
- * @param filename - 文件名,如 report_20260810_150000.md
|
|
|
- * @param content - 文本内容
|
|
|
- */
|
|
|
-export const saveReportText = (filename: string, content: string) =>
|
|
|
- defHttp.put({ url: `${Api.saveReport}${encodeURIComponent(filename)}`, data: { content } }, { isTransformResponse: false });
|
|
|
-
|
|
|
-/**
|
|
|
- * 覆盖保存报告(二进制,如 xlsx)
|
|
|
- * @param filename - 文件名
|
|
|
- * @param data - ArrayBuffer / Blob / Uint8Array
|
|
|
- */
|
|
|
-export const saveReportBinary = (filename: string, data: ArrayBuffer | Blob | Uint8Array) =>
|
|
|
- defHttp.put(
|
|
|
- {
|
|
|
- url: `${Api.saveReport}${encodeURIComponent(filename)}`,
|
|
|
- data,
|
|
|
- headers: { 'Content-Type': 'application/octet-stream' },
|
|
|
- },
|
|
|
- { isTransformResponse: false }
|
|
|
- );
|
|
|
-
|
|
|
-/**
|
|
|
- * 对话式修改 Word 报告
|
|
|
- * @param filename - docx 文件名
|
|
|
- * @param instruction - 修改要求(自然语言)
|
|
|
- */
|
|
|
-export const editWordReport = (filename: string, instruction: string) =>
|
|
|
- defHttp.post({ url: Api.editWord, data: { filename, instruction } }, { isTransformResponse: false });
|