374 lines
9.3 KiB
TypeScript
374 lines
9.3 KiB
TypeScript
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<string, unknown>;
|
||
openingStatement: string;
|
||
suggestedQuestions: string[];
|
||
suggestedQuestionsAfterAnswer: { enabled: boolean };
|
||
systemParameters: Record<string, unknown>;
|
||
userInputForm: Array<Record<string, unknown>>;
|
||
}
|
||
|
||
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<string, unknown>;
|
||
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<Record<string, unknown>>;
|
||
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<CubeReportScope>('/llm/cube-report/scope');
|
||
}
|
||
|
||
export async function getCubeReportMetaStatus() {
|
||
return pyRequestClient.get<CubeReportMetaStatus>(
|
||
'/llm/cube-report/meta/status',
|
||
);
|
||
}
|
||
|
||
export async function refreshCubeReportMeta() {
|
||
return pyRequestClient.post<CubeReportMetaStatus>(
|
||
'/llm/cube-report/meta/refresh',
|
||
);
|
||
}
|
||
|
||
export async function getCubeReportAppParameters() {
|
||
return pyRequestClient.get<CubeReportAppParameters>(
|
||
'/llm/cube-report/app-parameters',
|
||
);
|
||
}
|
||
|
||
export async function getCubeReportSessions(params: {
|
||
cursor?: string;
|
||
keyword?: string;
|
||
pageSize?: number;
|
||
tenantId: string;
|
||
}) {
|
||
return pyRequestClient.get<CubeReportSessionPage>(
|
||
'/llm/cube-report/sessions',
|
||
{ params },
|
||
);
|
||
}
|
||
|
||
export async function getCubeReports(params: {
|
||
keyword?: string;
|
||
page: number;
|
||
pageSize: number;
|
||
tenantId: string;
|
||
}) {
|
||
return pyRequestClient.get<CubeSavedReportPage>('/llm/cube-report/reports', {
|
||
params,
|
||
});
|
||
}
|
||
|
||
export async function getCubeReport(id: string) {
|
||
return pyRequestClient.get<CubeSavedReport>(`/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<CubeReportPage>(
|
||
`/llm/cube-report/sessions/${id}/data`,
|
||
{ params },
|
||
);
|
||
}
|
||
|
||
export async function getCubeSavedReportData(
|
||
id: string,
|
||
params: { page: number },
|
||
) {
|
||
return pyRequestClient.get<CubeReportPage>(
|
||
`/llm/cube-report/reports/${id}/data`,
|
||
{ params },
|
||
);
|
||
}
|
||
|
||
export async function exportCubeReportSession(id: string) {
|
||
return pyRequestClient.post<CubeReportExportResult>(
|
||
`/llm/cube-report/sessions/${id}/export`,
|
||
);
|
||
}
|
||
|
||
export async function exportCubeSavedReport(id: string) {
|
||
return pyRequestClient.post<CubeReportExportResult>(
|
||
`/llm/cube-report/reports/${id}/export`,
|
||
);
|
||
}
|
||
|
||
export async function favoriteCubeReportSession(id: string) {
|
||
return pyRequestClient.post<CubeSavedReport>(
|
||
`/llm/cube-report/sessions/${id}/favorite`,
|
||
);
|
||
}
|
||
|
||
export async function renameCubeReportSession(id: string, name: string) {
|
||
return pyRequestClient.post<CubeReportSession>(
|
||
`/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<CubeSavedReport>(
|
||
`/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);
|
||
}
|