Skip to content

Commit 15b27bd

Browse files
committed
fix(dashboard): resolve chat attachment 401 by restoring axios blob URL fetch
1 parent cb91dfb commit 15b27bd

1 file changed

Lines changed: 53 additions & 2 deletions

File tree

dashboard/src/composables/useMessages.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export function useMessages(options: UseMessagesOptions) {
8686
const messagesBySession = reactive<Record<string, ChatRecord[]>>({});
8787
const loadedSessions = reactive<Record<string, boolean>>({});
8888
const activeConnections = reactive<Record<string, ActiveConnection>>({});
89+
const attachmentBlobCache = new Map<string, string>();
8990
const sessionProjects = reactive<Record<string, ChatSessionProject | null>>(
9091
{},
9192
);
@@ -129,6 +130,47 @@ export function useMessages(options: UseMessagesOptions) {
129130
return !isUserMessage(msg) && msgIndex === activeMessages.value.length - 1;
130131
}
131132

133+
async function resolvePartMedia(part: MessagePart): Promise<void> {
134+
if (part.embedded_url) return;
135+
let url: string;
136+
let cacheKey: string;
137+
if (part.attachment_id) {
138+
cacheKey = `att:${part.attachment_id}`;
139+
url = `/api/chat/get_attachment?attachment_id=${encodeURIComponent(part.attachment_id)}`;
140+
} else if (part.filename) {
141+
cacheKey = `file:${part.filename}`;
142+
url = `/api/chat/get_file?filename=${encodeURIComponent(part.filename)}`;
143+
} else {
144+
return;
145+
}
146+
const cached = attachmentBlobCache.get(cacheKey);
147+
if (cached) {
148+
part.embedded_url = cached;
149+
return;
150+
}
151+
try {
152+
const resp = await axios.get(url, { responseType: "blob" });
153+
const blobUrl = URL.createObjectURL(resp.data);
154+
attachmentBlobCache.set(cacheKey, blobUrl);
155+
part.embedded_url = blobUrl;
156+
} catch (e) {
157+
console.error("Failed to resolve media:", cacheKey, e);
158+
}
159+
}
160+
161+
async function resolveRecordMedia(records: ChatRecord[]) {
162+
const mediaTypes = ["image", "record", "video"];
163+
const tasks: Promise<void>[] = [];
164+
for (const record of records) {
165+
for (const part of record.content?.message || []) {
166+
if (mediaTypes.includes(part.type) && !part.embedded_url && (part.attachment_id || part.filename)) {
167+
tasks.push(resolvePartMedia(part));
168+
}
169+
}
170+
}
171+
await Promise.all(tasks);
172+
}
173+
132174
async function loadSessionMessages(sessionId: string) {
133175
if (!sessionId) return;
134176
loadingMessages.value = true;
@@ -138,7 +180,9 @@ export function useMessages(options: UseMessagesOptions) {
138180
});
139181
const payload = response.data?.data || {};
140182
const history = payload.history || [];
141-
messagesBySession[sessionId] = history.map(normalizeHistoryRecord);
183+
const records = history.map(normalizeHistoryRecord);
184+
await resolveRecordMedia(records);
185+
messagesBySession[sessionId] = records;
142186
sessionProjects[sessionId] = normalizeSessionProject(payload.project);
143187
loadedSessions[sessionId] = true;
144188
} catch (error) {
@@ -438,7 +482,14 @@ export function useMessages(options: UseMessagesOptions) {
438482
.replace("[FILE]", "")
439483
.replace("[VIDEO]", "")
440484
.split("|", 1)[0];
441-
messageContent(botRecord).message.push({ type: msgType, filename });
485+
const mediaPart: MessagePart = { type: msgType, filename };
486+
if (msgType !== "file") {
487+
resolvePartMedia(mediaPart).then(() => {
488+
messageContent(botRecord).message.push(mediaPart);
489+
});
490+
} else {
491+
messageContent(botRecord).message.push(mediaPart);
492+
}
442493
}
443494
}
444495

0 commit comments

Comments
 (0)