import { useAppConfig } from '@vben/hooks'; import { useAccessStore } from '@vben/stores'; import { pyRequestClient } from '#/api/request'; export interface CubeReportScope { canRefreshMetadata: boolean; companies: Array<{ id: string; name: string }>; } export interface CubeReportMetaStatus { available: boolean; changed?: boolean; cubeCount: number; memberCount: number; refreshedAt: null | string; refreshing: boolean; stale: boolean; } export interface CubeReportAppParameters { fileUpload: Record; openingStatement: string; suggestedQuestions: string[]; suggestedQuestionsAfterAnswer: { enabled: boolean }; systemParameters: Record; userInputForm: Array>; } export interface CubeReportSession { createdAt: null | string; hasData?: boolean; id: string; reportId?: null | string; tenantId: string; tenantName: string; title: string; updatedAt: null | string; } export interface CubeReportSessionPage { hasMore: boolean; items: CubeReportSession[]; nextCursor: null | string; } export interface CubeSavedReport { conversationId: string; createdAt: null | string; id: string; limitSource: 'system' | 'user'; message: string; query: Record; requirement: string; tenantId: string; tenantName: string; title: string; updatedAt: null | string; } export interface CubeSavedReportPage { items: CubeSavedReport[]; page: number; pageSize: number; total: number; } export interface CubeReportFile { belongsTo?: 'assistant' | 'user'; id: string; name: string; type: string; } export interface CubeReportMessage { content: string; createdAt: null | string; difyMessageId: null | string; files: CubeReportFile[]; id: string; role: 'assistant' | 'user'; status: 'completed' | 'failed' | 'streaming'; } export interface CubeReportColumn { key: string; title: string; type: string; } export interface CubeReportPage { columns: CubeReportColumn[]; limitSource?: 'system' | 'user'; page: number; pageSize: number; rows: Array>; sql?: string; title?: string; total: number; } export interface CubeReportExportResult { bucketName: string; filename: string; objectName: string; rowCount: number; sheetCount: number; url: string; } export type CubeReportStreamEvent = | { content: string; type: 'message_delta' | 'message_replace' } | { conversationId?: string; hasData: boolean; type: 'complete'; } | { message: string; type: 'error' } | { taskId: string; type: 'task' } | { text: string; type: 'status' }; export interface SendCubeReportMessage { content: string; conversationId?: null | string; files: CubeReportFile[]; tenantId?: string; tenantName?: string; } function getFetchConfig() { const { pyApiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); const accessStore = useAccessStore(); return { baseURL: pyApiURL.replace(/\/$/, ''), authorization: accessStore.accessToken ? `Bearer ${accessStore.accessToken}` : '', }; } async function authorizedFetch(path: string, init: RequestInit) { const request = () => { const { authorization, baseURL } = getFetchConfig(); const headers = new Headers(init.headers); if (authorization) { headers.set('Authorization', authorization); } else { headers.delete('Authorization'); } return fetch(`${baseURL}${path}`, { ...init, headers }); }; let response = await request(); if (response.status !== 401) return response; try { // 标准请求客户端会使用项目现有的 refresh token 机制更新令牌。 await getCubeReportScope(); } catch { return response; } await response.body?.cancel(); response = await request(); return response; } async function responseError(response: Response) { const text = await response.text(); try { const payload = JSON.parse(text) as { detail?: string; message?: string }; return payload.detail || payload.message || text; } catch { return text || `请求失败(${response.status})`; } } export async function getCubeReportScope() { return pyRequestClient.get('/llm/cube-report/scope'); } export async function getCubeReportMetaStatus() { return pyRequestClient.get( '/llm/cube-report/meta/status', ); } export async function refreshCubeReportMeta() { return pyRequestClient.post( '/llm/cube-report/meta/refresh', ); } export async function getCubeReportAppParameters() { return pyRequestClient.get( '/llm/cube-report/app-parameters', ); } export async function getCubeReportSessions(params: { cursor?: string; keyword?: string; pageSize?: number; tenantId: string; }) { return pyRequestClient.get( '/llm/cube-report/sessions', { params }, ); } export async function getCubeReports(params: { keyword?: string; page: number; pageSize: number; tenantId: string; }) { return pyRequestClient.get('/llm/cube-report/reports', { params, }); } export async function getCubeReport(id: string) { return pyRequestClient.get(`/llm/cube-report/reports/${id}`); } export async function getCubeReportSession(id: string) { return pyRequestClient.get<{ messages: CubeReportMessage[]; session: CubeReportSession; }>(`/llm/cube-report/sessions/${id}`); } export async function getCubeReportData(id: string, params: { page: number }) { return pyRequestClient.get( `/llm/cube-report/sessions/${id}/data`, { params }, ); } export async function getCubeSavedReportData( id: string, params: { page: number }, ) { return pyRequestClient.get( `/llm/cube-report/reports/${id}/data`, { params }, ); } export async function exportCubeReportSession(id: string) { return pyRequestClient.post( `/llm/cube-report/sessions/${id}/export`, ); } export async function exportCubeSavedReport(id: string) { return pyRequestClient.post( `/llm/cube-report/reports/${id}/export`, ); } export async function favoriteCubeReportSession(id: string) { return pyRequestClient.post( `/llm/cube-report/sessions/${id}/favorite`, ); } export async function renameCubeReportSession(id: string, name: string) { return pyRequestClient.post( `/llm/cube-report/sessions/${id}/name`, { name }, ); } export async function deleteCubeReportSession(id: string) { return pyRequestClient.delete<{ result: string }>( `/llm/cube-report/sessions/${id}`, ); } export async function renameCubeSavedReport(id: string, name: string) { return pyRequestClient.post( `/llm/cube-report/reports/${id}/name`, { name }, ); } export async function deleteCubeSavedReport(id: string) { return pyRequestClient.delete<{ result: string }>( `/llm/cube-report/reports/${id}`, ); } export async function stopCubeReportTask(taskId: string) { return pyRequestClient.post<{ result: string }>( `/llm/cube-report/tasks/${taskId}/stop`, ); } export async function uploadCubeReportFile(file: File) { const form = new FormData(); form.append('file', file); const response = await authorizedFetch('/llm/cube-report/files/upload', { method: 'POST', body: form, }); if (!response.ok) throw new Error(await responseError(response)); const payload = (await response.json()) as { data: CubeReportFile; }; return payload.data; } export async function downloadCubeReportFile( conversationId: string, file: CubeReportFile, ) { const params = new URLSearchParams({ conversationId, asAttachment: 'true', }); const response = await authorizedFetch( `/llm/cube-report/files/${file.id}/preview?${params}`, {}, ); if (!response.ok) throw new Error(await responseError(response)); const blob = await response.blob(); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = file.name; anchor.click(); URL.revokeObjectURL(url); } export async function streamCubeReportMessage( payload: SendCubeReportMessage, onEvent: (event: CubeReportStreamEvent) => void, ) { const response = await authorizedFetch('/llm/cube-report/messages/stream', { method: 'POST', headers: { Accept: 'text/event-stream', 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (!response.ok) throw new Error(await responseError(response)); if (!response.body) throw new Error('当前浏览器不支持流式响应'); const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; const consumePacket = (packet: string) => { const data = packet .split(/\r?\n/) .filter((line) => line.startsWith('data: ')) .map((line) => line.slice(6)) .join('\n'); if (data) onEvent(JSON.parse(data) as CubeReportStreamEvent); }; while (true) { const { done, value } = await reader.read(); buffer += decoder.decode(value, { stream: !done }); const packets = buffer.split(/\r?\n\r?\n/); buffer = packets.pop() ?? ''; packets.forEach((packet) => consumePacket(packet)); if (done) break; } if (buffer.trim()) consumePacket(buffer); }