完善细节
This commit is contained in:
@@ -20,7 +20,7 @@ def get_cube_report_settings() -> CubeReportSettings:
|
|||||||
"DIFY_DATABASE_ASSISTANT_API_BASE", "https://chat.bbitcn.net/v1"
|
"DIFY_DATABASE_ASSISTANT_API_BASE", "https://chat.bbitcn.net/v1"
|
||||||
).rstrip("/"),
|
).rstrip("/"),
|
||||||
dify_api_key=os.getenv(
|
dify_api_key=os.getenv(
|
||||||
"DIFY_DATABASE_ASSISTANT_API_KEY", "app-uibWo8ZEpqHCsWXREPTCBDH6"
|
"DIFY_DATABASE_ASSISTANT_API_KEY", "app-Ojd3zT2wBsO2Bop575ZiHQOD"
|
||||||
),
|
),
|
||||||
cube_api_base=os.getenv(
|
cube_api_base=os.getenv(
|
||||||
"CUBE_API_BASE_URL", "http://10.10.12.101:4001/cubejs-api/v1"
|
"CUBE_API_BASE_URL", "http://10.10.12.101:4001/cubejs-api/v1"
|
||||||
|
|||||||
@@ -138,6 +138,32 @@ function getFetchConfig() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
async function responseError(response: Response) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
try {
|
try {
|
||||||
@@ -272,12 +298,10 @@ export async function stopCubeReportTask(taskId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadCubeReportFile(file: File) {
|
export async function uploadCubeReportFile(file: File) {
|
||||||
const { authorization, baseURL } = getFetchConfig();
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append('file', file);
|
form.append('file', file);
|
||||||
const response = await fetch(`${baseURL}/llm/cube-report/files/upload`, {
|
const response = await authorizedFetch('/llm/cube-report/files/upload', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { Authorization: authorization },
|
|
||||||
body: form,
|
body: form,
|
||||||
});
|
});
|
||||||
if (!response.ok) throw new Error(await responseError(response));
|
if (!response.ok) throw new Error(await responseError(response));
|
||||||
@@ -291,14 +315,13 @@ export async function downloadCubeReportFile(
|
|||||||
conversationId: string,
|
conversationId: string,
|
||||||
file: CubeReportFile,
|
file: CubeReportFile,
|
||||||
) {
|
) {
|
||||||
const { authorization, baseURL } = getFetchConfig();
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
conversationId,
|
conversationId,
|
||||||
asAttachment: 'true',
|
asAttachment: 'true',
|
||||||
});
|
});
|
||||||
const response = await fetch(
|
const response = await authorizedFetch(
|
||||||
`${baseURL}/llm/cube-report/files/${file.id}/preview?${params}`,
|
`/llm/cube-report/files/${file.id}/preview?${params}`,
|
||||||
{ headers: { Authorization: authorization } },
|
{},
|
||||||
);
|
);
|
||||||
if (!response.ok) throw new Error(await responseError(response));
|
if (!response.ok) throw new Error(await responseError(response));
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
@@ -314,12 +337,10 @@ export async function streamCubeReportMessage(
|
|||||||
payload: SendCubeReportMessage,
|
payload: SendCubeReportMessage,
|
||||||
onEvent: (event: CubeReportStreamEvent) => void,
|
onEvent: (event: CubeReportStreamEvent) => void,
|
||||||
) {
|
) {
|
||||||
const { authorization, baseURL } = getFetchConfig();
|
const response = await authorizedFetch('/llm/cube-report/messages/stream', {
|
||||||
const response = await fetch(`${baseURL}/llm/cube-report/messages/stream`, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'text/event-stream',
|
Accept: 'text/event-stream',
|
||||||
Authorization: authorization,
|
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
|
|||||||
@@ -381,49 +381,64 @@ async function sendMessage() {
|
|||||||
let completedConversationId = currentSessionId.value;
|
let completedConversationId = currentSessionId.value;
|
||||||
let completedWithData = false;
|
let completedWithData = false;
|
||||||
try {
|
try {
|
||||||
await api.streamCubeReportMessage(
|
try {
|
||||||
{
|
await api.streamCubeReportMessage(
|
||||||
content,
|
{
|
||||||
conversationId: currentSessionId.value,
|
content,
|
||||||
files: sentFiles,
|
conversationId: currentSessionId.value,
|
||||||
tenantId: currentSession.value?.tenantId || selectedCompany.value?.id,
|
files: sentFiles,
|
||||||
tenantName:
|
tenantId: currentSession.value?.tenantId || selectedCompany.value?.id,
|
||||||
currentSession.value?.tenantName || selectedCompany.value?.name,
|
tenantName:
|
||||||
},
|
currentSession.value?.tenantName || selectedCompany.value?.name,
|
||||||
(event: CubeReportStreamEvent) => {
|
},
|
||||||
switch (event.type) {
|
(event: CubeReportStreamEvent) => {
|
||||||
case 'complete': {
|
switch (event.type) {
|
||||||
completedConversationId =
|
case 'complete': {
|
||||||
event.conversationId || completedConversationId;
|
completedConversationId =
|
||||||
completedWithData = event.hasData;
|
event.conversationId || completedConversationId;
|
||||||
assistantMessage.status = 'completed';
|
completedWithData = event.hasData;
|
||||||
if (event.hasData) activeView.value = 'split';
|
assistantMessage.status = 'completed';
|
||||||
break;
|
if (event.hasData) activeView.value = 'split';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'message_delta': {
|
||||||
|
assistantMessage.content += event.content;
|
||||||
|
void scrollToBottom();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'message_replace': {
|
||||||
|
assistantMessage.content = event.content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'status': {
|
||||||
|
streamStatus.value = event.text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'task': {
|
||||||
|
currentTaskId.value = event.taskId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'error': {
|
||||||
|
throw new Error(event.message);
|
||||||
|
}
|
||||||
|
// No default
|
||||||
}
|
}
|
||||||
case 'message_delta': {
|
},
|
||||||
assistantMessage.content += event.content;
|
);
|
||||||
void scrollToBottom();
|
} catch (error) {
|
||||||
break;
|
const text =
|
||||||
}
|
error instanceof Error ? error.message : '请求失败,请稍后重试';
|
||||||
case 'message_replace': {
|
assistantMessage.status = stopRequested.value ? 'completed' : 'failed';
|
||||||
assistantMessage.content = event.content;
|
assistantMessage.content = stopRequested.value
|
||||||
break;
|
? assistantMessage.content || '响应已停止。'
|
||||||
}
|
: `请求失败:${text}`;
|
||||||
case 'status': {
|
if (!stopRequested.value) {
|
||||||
streamStatus.value = event.text;
|
inputMessage.value = content;
|
||||||
break;
|
pendingFiles.value = sentFiles;
|
||||||
}
|
message.error(text);
|
||||||
case 'task': {
|
}
|
||||||
currentTaskId.value = event.taskId;
|
return;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
case 'error': {
|
|
||||||
throw new Error(event.message);
|
|
||||||
}
|
|
||||||
// No default
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!assistantMessage.content) {
|
if (!assistantMessage.content) {
|
||||||
assistantMessage.content = stopRequested.value
|
assistantMessage.content = stopRequested.value
|
||||||
@@ -431,30 +446,24 @@ async function sendMessage() {
|
|||||||
: '查询已完成。';
|
: '查询已完成。';
|
||||||
}
|
}
|
||||||
if (completedConversationId) {
|
if (completedConversationId) {
|
||||||
currentSessionId.value = completedConversationId;
|
try {
|
||||||
const detail = await api.getCubeReportSession(completedConversationId);
|
currentSessionId.value = completedConversationId;
|
||||||
currentSession.value = detail.session;
|
const detail = await api.getCubeReportSession(completedConversationId);
|
||||||
messages.value = detail.messages;
|
currentSession.value = detail.session;
|
||||||
if (currentReportId.value) {
|
messages.value = detail.messages;
|
||||||
currentReport.value = await api.getCubeReport(currentReportId.value);
|
if (currentReportId.value) {
|
||||||
|
currentReport.value = await api.getCubeReport(currentReportId.value);
|
||||||
|
}
|
||||||
|
await reloadNavigation();
|
||||||
|
if (completedWithData || detail.session.hasData) {
|
||||||
|
activeView.value = 'split';
|
||||||
|
await loadData(1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const text =
|
||||||
|
error instanceof Error ? error.message : '会话和数据刷新失败';
|
||||||
|
message.warning(`回复已完成,但数据刷新失败:${text}`);
|
||||||
}
|
}
|
||||||
await reloadNavigation();
|
|
||||||
if (completedWithData || detail.session.hasData) {
|
|
||||||
activeView.value = 'split';
|
|
||||||
await loadData(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const text =
|
|
||||||
error instanceof Error ? error.message : '请求失败,请稍后重试';
|
|
||||||
assistantMessage.status = stopRequested.value ? 'completed' : 'failed';
|
|
||||||
assistantMessage.content = stopRequested.value
|
|
||||||
? assistantMessage.content || '响应已停止。'
|
|
||||||
: `请求失败:${text}`;
|
|
||||||
if (!stopRequested.value) {
|
|
||||||
inputMessage.value = content;
|
|
||||||
pendingFiles.value = sentFiles;
|
|
||||||
message.error(text);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
sending.value = false;
|
sending.value = false;
|
||||||
@@ -465,6 +474,12 @@ async function sendMessage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendSuggestedQuestion(question: string) {
|
||||||
|
if (sending.value) return;
|
||||||
|
inputMessage.value = question;
|
||||||
|
void sendMessage();
|
||||||
|
}
|
||||||
|
|
||||||
async function stopResponse() {
|
async function stopResponse() {
|
||||||
if (!currentTaskId.value || stopping.value) return;
|
if (!currentTaskId.value || stopping.value) return;
|
||||||
stopping.value = true;
|
stopping.value = true;
|
||||||
@@ -874,11 +889,11 @@ onBeforeUnmount(() => {
|
|||||||
v-for="session in sessions"
|
v-for="session in sessions"
|
||||||
:key="session.id"
|
:key="session.id"
|
||||||
type="button"
|
type="button"
|
||||||
class="session-item"
|
class="session-item"
|
||||||
:class="{
|
:class="{
|
||||||
'session-item-active':
|
'session-item-active':
|
||||||
!currentReportId && currentSessionId === session.id,
|
!currentReportId && currentSessionId === session.id,
|
||||||
}"
|
}"
|
||||||
@click="loadConversation(session.id)"
|
@click="loadConversation(session.id)"
|
||||||
>
|
>
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
@@ -1183,17 +1198,14 @@ onBeforeUnmount(() => {
|
|||||||
v-for="(question, index) in suggestedQuestions"
|
v-for="(question, index) in suggestedQuestions"
|
||||||
:key="question"
|
:key="question"
|
||||||
type="button"
|
type="button"
|
||||||
@click="inputMessage = question"
|
:disabled="sending"
|
||||||
|
@click="sendSuggestedQuestion(question)"
|
||||||
>
|
>
|
||||||
<IconifyIcon
|
<IconifyIcon
|
||||||
:icon="suggestionIcons[index % suggestionIcons.length]!"
|
:icon="suggestionIcons[index % suggestionIcons.length]!"
|
||||||
/>
|
/>
|
||||||
<span>{{ question }}</span>
|
<span>{{ question }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" @click="fileInput?.click()">
|
|
||||||
<IconifyIcon icon="lucide:file-up" />
|
|
||||||
<span>上传 Excel 辅助分析</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
|
|||||||
+2716
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user