完善细节
This commit is contained in:
@@ -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) {
|
||||
const text = await response.text();
|
||||
try {
|
||||
@@ -272,12 +298,10 @@ export async function stopCubeReportTask(taskId: string) {
|
||||
}
|
||||
|
||||
export async function uploadCubeReportFile(file: File) {
|
||||
const { authorization, baseURL } = getFetchConfig();
|
||||
const form = new FormData();
|
||||
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',
|
||||
headers: { Authorization: authorization },
|
||||
body: form,
|
||||
});
|
||||
if (!response.ok) throw new Error(await responseError(response));
|
||||
@@ -291,14 +315,13 @@ export async function downloadCubeReportFile(
|
||||
conversationId: string,
|
||||
file: CubeReportFile,
|
||||
) {
|
||||
const { authorization, baseURL } = getFetchConfig();
|
||||
const params = new URLSearchParams({
|
||||
conversationId,
|
||||
asAttachment: 'true',
|
||||
});
|
||||
const response = await fetch(
|
||||
`${baseURL}/llm/cube-report/files/${file.id}/preview?${params}`,
|
||||
{ headers: { Authorization: authorization } },
|
||||
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();
|
||||
@@ -314,12 +337,10 @@ export async function streamCubeReportMessage(
|
||||
payload: SendCubeReportMessage,
|
||||
onEvent: (event: CubeReportStreamEvent) => void,
|
||||
) {
|
||||
const { authorization, baseURL } = getFetchConfig();
|
||||
const response = await fetch(`${baseURL}/llm/cube-report/messages/stream`, {
|
||||
const response = await authorizedFetch('/llm/cube-report/messages/stream', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'text/event-stream',
|
||||
Authorization: authorization,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
|
||||
@@ -381,49 +381,64 @@ async function sendMessage() {
|
||||
let completedConversationId = currentSessionId.value;
|
||||
let completedWithData = false;
|
||||
try {
|
||||
await api.streamCubeReportMessage(
|
||||
{
|
||||
content,
|
||||
conversationId: currentSessionId.value,
|
||||
files: sentFiles,
|
||||
tenantId: currentSession.value?.tenantId || selectedCompany.value?.id,
|
||||
tenantName:
|
||||
currentSession.value?.tenantName || selectedCompany.value?.name,
|
||||
},
|
||||
(event: CubeReportStreamEvent) => {
|
||||
switch (event.type) {
|
||||
case 'complete': {
|
||||
completedConversationId =
|
||||
event.conversationId || completedConversationId;
|
||||
completedWithData = event.hasData;
|
||||
assistantMessage.status = 'completed';
|
||||
if (event.hasData) activeView.value = 'split';
|
||||
break;
|
||||
try {
|
||||
await api.streamCubeReportMessage(
|
||||
{
|
||||
content,
|
||||
conversationId: currentSessionId.value,
|
||||
files: sentFiles,
|
||||
tenantId: currentSession.value?.tenantId || selectedCompany.value?.id,
|
||||
tenantName:
|
||||
currentSession.value?.tenantName || selectedCompany.value?.name,
|
||||
},
|
||||
(event: CubeReportStreamEvent) => {
|
||||
switch (event.type) {
|
||||
case 'complete': {
|
||||
completedConversationId =
|
||||
event.conversationId || completedConversationId;
|
||||
completedWithData = event.hasData;
|
||||
assistantMessage.status = 'completed';
|
||||
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();
|
||||
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
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} 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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assistantMessage.content) {
|
||||
assistantMessage.content = stopRequested.value
|
||||
@@ -431,30 +446,24 @@ async function sendMessage() {
|
||||
: '查询已完成。';
|
||||
}
|
||||
if (completedConversationId) {
|
||||
currentSessionId.value = completedConversationId;
|
||||
const detail = await api.getCubeReportSession(completedConversationId);
|
||||
currentSession.value = detail.session;
|
||||
messages.value = detail.messages;
|
||||
if (currentReportId.value) {
|
||||
currentReport.value = await api.getCubeReport(currentReportId.value);
|
||||
try {
|
||||
currentSessionId.value = completedConversationId;
|
||||
const detail = await api.getCubeReportSession(completedConversationId);
|
||||
currentSession.value = detail.session;
|
||||
messages.value = detail.messages;
|
||||
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 {
|
||||
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() {
|
||||
if (!currentTaskId.value || stopping.value) return;
|
||||
stopping.value = true;
|
||||
@@ -874,11 +889,11 @@ onBeforeUnmount(() => {
|
||||
v-for="session in sessions"
|
||||
:key="session.id"
|
||||
type="button"
|
||||
class="session-item"
|
||||
:class="{
|
||||
'session-item-active':
|
||||
!currentReportId && currentSessionId === session.id,
|
||||
}"
|
||||
class="session-item"
|
||||
:class="{
|
||||
'session-item-active':
|
||||
!currentReportId && currentSessionId === session.id,
|
||||
}"
|
||||
@click="loadConversation(session.id)"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
@@ -1183,17 +1198,14 @@ onBeforeUnmount(() => {
|
||||
v-for="(question, index) in suggestedQuestions"
|
||||
:key="question"
|
||||
type="button"
|
||||
@click="inputMessage = question"
|
||||
:disabled="sending"
|
||||
@click="sendSuggestedQuestion(question)"
|
||||
>
|
||||
<IconifyIcon
|
||||
:icon="suggestionIcons[index % suggestionIcons.length]!"
|
||||
/>
|
||||
<span>{{ question }}</span>
|
||||
</button>
|
||||
<button type="button" @click="fileInput?.click()">
|
||||
<IconifyIcon icon="lucide:file-up" />
|
||||
<span>上传 Excel 辅助分析</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
|
||||
Reference in New Issue
Block a user