feat: file upload retry, secure file tokens, UI polish
- Add HMAC-based file token auth for secure AI model file access - Add file upload retry with exponential backoff (max 3 retries) - File endpoint now requires session auth or signed token - Support assistant role messages in chat input - Optimistic UI for attachments on message send - Verify topic ownership before allowing messages - Switch web scraping to Firecrawl API - Agent profile page layout fixes (proper flex overflow) - Add quick switcher (Ctrl+K) to sidenav - Clean up longcat.ts and stale comments
This commit is contained in:
+88
-26
@@ -6,6 +6,13 @@ import { buildMessageTree } from '~~/utils/message';
|
||||
export type BaseMessage = {
|
||||
content: string;
|
||||
fileIds: string[];
|
||||
files?: {
|
||||
id: string;
|
||||
name: string;
|
||||
mimeType: string;
|
||||
url: string;
|
||||
size?: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type ToolCall = typeof schema.toolCalls.$inferSelect
|
||||
@@ -263,12 +270,6 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
||||
const { data: ssrData } = await useFetch<Topic & { messages: Message[] }>(`/api/topic/${id}`);
|
||||
data.value = ssrData.value;
|
||||
} else {
|
||||
// if (data.value === undefined) {
|
||||
// const id = unref(topicId);
|
||||
// const { data: ssrData } = await useFetch<Topic & { messages: Message[] }>(`/api/topic/${id}`);
|
||||
// data.value = ssrData.value;
|
||||
// }
|
||||
|
||||
await connectSSE();
|
||||
}
|
||||
}
|
||||
@@ -305,6 +306,7 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
||||
const sendMessage = async (
|
||||
baseMessage: BaseMessage,
|
||||
onRequest?: () => void,
|
||||
role: 'user' | 'assistant' = 'user',
|
||||
): Promise<Result<void, ChatErrorType>> => {
|
||||
const { user } = useAuth();
|
||||
if (!user.value) return Err(ChatErrorType.NoUser);
|
||||
@@ -312,34 +314,94 @@ export const useChat = async (topicId: MaybeRef<string>, connect = true) => {
|
||||
try {
|
||||
const message = {
|
||||
id: nanoid(),
|
||||
role: 'user',
|
||||
content: baseMessage.content,
|
||||
fileIds: baseMessage.fileIds,
|
||||
role,
|
||||
}
|
||||
|
||||
await $fetch(`/api/topic/${unref(topicId)}/message`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
message
|
||||
message: {
|
||||
...message,
|
||||
content: baseMessage.content,
|
||||
fileIds: baseMessage.fileIds,
|
||||
}
|
||||
},
|
||||
onRequest() {
|
||||
if (data.value) {
|
||||
data.value!.messages.push({
|
||||
topicId: unref(topicId),
|
||||
userId: user.value!.id,
|
||||
// TODO
|
||||
attachments: [],
|
||||
parts: undefined,
|
||||
generation: null,
|
||||
parentMessageId: null,
|
||||
generationId: null,
|
||||
activeChildId: null,
|
||||
deleted: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
children: [],
|
||||
...message
|
||||
} as Message);
|
||||
switch (role) {
|
||||
case 'user': {
|
||||
const optimisticAttachments = baseMessage.files?.map(file => ({
|
||||
id: nanoid(),
|
||||
userId: user.value!.id,
|
||||
topicId: unref(topicId),
|
||||
messageId: message.id,
|
||||
fileId: file.id,
|
||||
createdAt: new Date(),
|
||||
file: {
|
||||
id: file.id,
|
||||
userId: user.value!.id,
|
||||
name: file.name,
|
||||
mimeType: file.mimeType,
|
||||
size: file.size ?? 0,
|
||||
url: file.url,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
})) ?? [];
|
||||
|
||||
data.value!.messages.push({
|
||||
topicId: unref(topicId),
|
||||
userId: user.value!.id,
|
||||
attachments: optimisticAttachments,
|
||||
parts: undefined,
|
||||
generation: null,
|
||||
parentMessageId: null,
|
||||
generationId: null,
|
||||
activeChildId: null,
|
||||
children: [],
|
||||
deleted: false,
|
||||
content: baseMessage.content,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
...message
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'assistant': {
|
||||
data.value!.messages.push({
|
||||
topicId: unref(topicId),
|
||||
userId: user.value!.id,
|
||||
// TODO
|
||||
attachments: [],
|
||||
generation: null,
|
||||
parentMessageId: null,
|
||||
generationId: null,
|
||||
activeChildId: null,
|
||||
children: [],
|
||||
deleted: false,
|
||||
content: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
parts: [
|
||||
{
|
||||
id: 'bogus-id',
|
||||
userId: user.value!.id,
|
||||
topicId: unref(topicId),
|
||||
messageId: message.id,
|
||||
type: 'text',
|
||||
content: baseMessage.content,
|
||||
finished: true,
|
||||
createdAt: new Date(),
|
||||
lastUpdatedAt: new Date(),
|
||||
providerOptions: null,
|
||||
toolCallId: null,
|
||||
toolCall: null,
|
||||
}
|
||||
],
|
||||
...message
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
onRequest?.();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user