Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 38 additions & 17 deletions convex/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,25 +473,46 @@ export const getUserChats = query({
.paginate(args.paginationOpts);

// Enhance chats with branched_from_title
const enhancedChats = await Promise.all(
result.page.map(async (chat) => {
if (chat.branched_from_chat_id) {
const branchedFromChat = await ctx.db
.query("chats")
.withIndex("by_chat_id", (q) =>
q.eq("id", chat.branched_from_chat_id!),
)
.first();

return {
...chat,
branched_from_title: branchedFromChat?.title,
};
}
return chat;
}),
// Step 1: Collect unique branched_from_chat_ids
const branchedIds = [
...new Set(
result.page
.map((chat) => chat.branched_from_chat_id)
.filter((id): id is string => id != null)
),
];

// Step 2: Batch fetch all branched chats in parallel
const branchedChats = await Promise.all(
branchedIds.map((id) =>
ctx.db
.query("chats")
.withIndex("by_chat_id", (q) => q.eq("id", id))
.first()
)
);

// Step 3: Build lookup map for O(1) access
const branchedChatMap = new Map(
branchedChats
.filter((chat): chat is NonNullable<typeof chat> => chat != null)
.map((chat) => [chat.id, chat])
);

// Step 4: Enhance chats using the map (no async needed)
const enhancedChats = result.page.map((chat) => {
if (chat.branched_from_chat_id) {
const branchedFromChat = branchedChatMap.get(
chat.branched_from_chat_id
);
return {
...chat,
branched_from_title: branchedFromChat?.title,
};
}
return chat;
});

return {
...result,
page: enhancedChats,
Expand Down
16 changes: 9 additions & 7 deletions convex/fileStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
mutation,
query,
} from "./_generated/server";
import { Id } from "./_generated/dataModel";
import { v, ConvexError } from "convex/values";
import { validateServiceKey } from "./chats";
import { internal } from "./_generated/api";
Expand Down Expand Up @@ -33,15 +34,16 @@ export const getFileDownloadUrl = query({
}

try {
// Query all user's files and find the one with matching storage_id
const userFiles = await ctx.db
// Direct lookup by storage_id using index
const file = await ctx.db
.query("files")
.withIndex("by_user_id", (q) => q.eq("user_id", user.subject))
.collect();
.withIndex("by_storage_id", (q) =>
q.eq("storage_id", args.storageId as Id<"_storage">),
)
.first();

const file = userFiles.find((f) => f.storage_id === args.storageId);

if (!file) {
// Verify file exists and belongs to user
if (!file || file.user_id !== user.subject) {
throw new ConvexError({
code: "FILE_NOT_FOUND",
message: "File not found",
Expand Down
3 changes: 2 additions & 1 deletion convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export default defineSchema({
})
.index("by_user_id", ["user_id"])
.index("by_is_attached", ["is_attached"])
.index("by_s3_key", ["s3_key"]),
.index("by_s3_key", ["s3_key"])
.index("by_storage_id", ["storage_id"]),

feedback: defineTable({
feedback_type: v.union(v.literal("positive"), v.literal("negative")),
Expand Down
46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@
"desktop:build": "cd packages/desktop && pnpm build"
},
"dependencies": {
"@ai-sdk/openai": "^3.0.9",
"@ai-sdk/react": "^3.0.32",
"@ai-sdk/xai": "^3.0.20",
"@aws-sdk/client-s3": "^3.966.0",
"@aws-sdk/s3-request-presigner": "^3.966.0",
"@ai-sdk/openai": "^3.0.12",
"@ai-sdk/react": "^3.0.43",
"@ai-sdk/xai": "^3.0.26",
"@aws-sdk/client-s3": "^3.971.0",
"@aws-sdk/s3-request-presigner": "^3.971.0",
"@convex-dev/aggregate": "^0.2.1",
"@convex-dev/workos": "^0.0.1",
"@e2b/code-interpreter": "2.3.3",
"@langchain/community": "^1.1.3",
"@langchain/community": "^1.1.4",
"@openrouter/ai-sdk-provider": "^1.5.4",
"@posthog/ai": "^7.4.2",
"@posthog/ai": "^7.4.3",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-collapsible": "^1.1.12",
Expand All @@ -70,43 +70,43 @@
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@upstash/ratelimit": "^2.0.8",
"@upstash/redis": "^1.36.1",
"@vercel/functions": "^3.3.5",
"@vercel/functions": "^3.3.6",
"@workos-inc/authkit-nextjs": "^2.13.0",
"@workos-inc/node": "^8.0.0",
"ai": "^6.0.30",
"ai-elements": "^1.6.3",
"ai": "^6.0.41",
"ai-elements": "^1.7.0",
"chalk": "^5.6.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"convex": "^1.31.3",
"convex": "^1.31.5",
"date-fns": "^4.1.0",
"e2b": "^2.10.1",
"e2b": "^2.10.2",
"gpt-tokenizer": "^3.4.0",
"iron-session": "^8.0.4",
"isbinaryfile": "^6.0.0",
"jszip": "^3.10.1",
"langchain": "^1.2.7",
"langchain": "^1.2.10",
"lucide-react": "^0.562.0",
"mammoth": "^1.11.0",
"marked": "^17.0.1",
"motion": "^12.26.1",
"next": "16.1.1",
"motion": "^12.27.1",
"next": "16.1.3",
"next-themes": "^0.4.6",
"openai": "^6.16.0",
"pdfjs-serverless": "^1.1.0",
"posthog-js": "^1.319.0",
"posthog-node": "^5.20.0",
"posthog-js": "^1.329.0",
"posthog-node": "^5.21.2",
"react": "^19.2.3",
"react-dom": "19.2.3",
"react-hotkeys-hook": "^5.2.1",
"react-hotkeys-hook": "^5.2.3",
"react-shiki": "^0.9.1",
"react-textarea-autosize": "^8.5.9",
"redis": "^5.10.0",
"resumable-stream": "^2.2.10",
"shiki": "^3.21.0",
"sonner": "^2.0.7",
"streamdown": "^2.0.1",
"stripe": "^20.1.2",
"streamdown": "^2.1.0",
"stripe": "^20.2.0",
"tailwind-merge": "^3.4.0",
"use-stick-to-bottom": "^1.1.1",
"uuid": "^13.0.0",
Expand All @@ -122,7 +122,7 @@
"@tauri-apps/plugin-opener": "^2.5.3",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^30.0.0",
"@types/jszip": "^3.4.1",
Expand All @@ -133,12 +133,12 @@
"concurrently": "^9.2.1",
"dotenv": "^17.2.3",
"eslint": "^9",
"eslint-config-next": "16.1.1",
"eslint-config-next": "16.1.3",
"husky": "^9.1.7",
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"playwright": "^1.57.0",
"prettier": "^3.7.4",
"prettier": "^3.8.0",
"tailwindcss": "^4",
"ts-node": "^10.9.2",
"tw-animate-css": "^1.4.0",
Expand Down
Loading