From 5e81a86e7a5e0088d1c21e2d559910057a7133a4 Mon Sep 17 00:00:00 2001 From: Ahmet Kilinc Date: Thu, 3 Jul 2025 00:05:25 +0100 Subject: [PATCH 1/4] update posthog (#1586) Updates the PostHog JS dependency from version 1.236.6 to 1.256.0 in the mail application ## Summary by CodeRabbit * **Chores** * Updated "posthog-js" to the latest version. * Added and reorganized development dependencies for improved project maintenance. --- apps/mail/package.json | 8 ++++---- pnpm-lock.yaml | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/mail/package.json b/apps/mail/package.json index 34e979a7d7..d433fa75be 100644 --- a/apps/mail/package.json +++ b/apps/mail/package.json @@ -84,7 +84,7 @@ "nuqs": "2.4.0", "partysocket": "^1.1.4", "pluralize": "^8.0.0", - "posthog-js": "1.236.6", + "posthog-js": "1.256.0", "prosemirror-model": "1.25.1", "prosemirror-state": "1.4.3", "prosemirror-view": "1.39.3", @@ -117,6 +117,8 @@ }, "devDependencies": { "@cloudflare/vite-plugin": "^1.3.1", + "@inlang/cli": "^3.0.0", + "@inlang/paraglide-js": "2.1.0", "@tailwindcss/typography": "0.5.16", "@types/accept-language-parser": "^1.5.8", "@types/canvas-confetti": "1.9.0", @@ -137,8 +139,6 @@ "typescript": "catalog:", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4", - "wrangler": "catalog:", - "@inlang/paraglide-js": "2.1.0", - "@inlang/cli": "^3.0.0" + "wrangler": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6e425e8c0..5a300f3660 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,8 +302,8 @@ importers: specifier: ^8.0.0 version: 8.0.0 posthog-js: - specifier: 1.236.6 - version: 1.236.6 + specifier: 1.256.0 + version: 1.256.0 prosemirror-model: specifier: 1.25.1 version: 1.25.1 @@ -6246,8 +6246,8 @@ packages: resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==} engines: {node: '>=12'} - posthog-js@1.236.6: - resolution: {integrity: sha512-IX4fkn3HCK+ObdHr/AuWd+Ks7bgMpRpOQB93b5rDJAWkG4if4xFVUn5pgEjyCNeOO2GM1ECnp08q9tYNYEfwbA==} + posthog-js@1.256.0: + resolution: {integrity: sha512-LJSj4VcuLQGlsk4aQDOydx+oSj5+nlXHvHPDcAmzLucfoJEUi5CmluUU78V5+MwswoUHYZdGf5TPae3EXQk3FQ==} peerDependencies: '@rrweb/types': 2.0.0-alpha.17 rrweb-snapshot: 2.0.0-alpha.17 @@ -13539,7 +13539,7 @@ snapshots: postgres@3.4.5: {} - posthog-js@1.236.6: + posthog-js@1.256.0: dependencies: core-js: 3.43.0 fflate: 0.4.8 From 92fce9ea04afdc07acfc0f3a6bdfaacd65a9cb5d Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Jul 2025 22:07:38 -0700 Subject: [PATCH 2/4] faster maybe (#1606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # READ CAREFULLY THEN REMOVE Remove bullet points that are not relevant. PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI. - Pull requests that do not follow these guidelines will be closed without review or comment. - If you use AI to write your PR description your pr will be close without review or comment. - If you are unsure about anything, feel free to ask for clarification. ## Description Please provide a clear description of your changes. --- ## Type of Change Please delete options that are not relevant. - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 💥 Breaking change (fix or feature with breaking changes) - [ ] 📝 Documentation update - [ ] 🎨 UI/UX improvement - [ ] 🔒 Security enhancement - [ ] ⚡ Performance improvement ## Areas Affected Please check all that apply: - [ ] Email Integration (Gmail, IMAP, etc.) - [ ] User Interface/Experience - [ ] Authentication/Authorization - [ ] Data Storage/Management - [ ] API Endpoints - [ ] Documentation - [ ] Testing Infrastructure - [ ] Development Workflow - [ ] Deployment/Infrastructure ## Testing Done Describe the tests you've done: - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] Cross-browser testing (if UI changes) - [ ] Mobile responsiveness verified (if UI changes) ## Security Considerations For changes involving data or authentication: - [ ] No sensitive data is exposed - [ ] Authentication checks are in place - [ ] Input validation is implemented - [ ] Rate limiting is considered (if applicable) ## Checklist - [ ] I have read the [CONTRIBUTING](https://github.com/Mail-0/Zero/blob/staging/.github/CONTRIBUTING.md) document - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in complex areas - [ ] I have updated the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix/feature works - [ ] All tests pass locally - [ ] Any dependent changes are merged and published ## Additional Notes Add any other context about the pull request here. ## Screenshots/Recordings Add screenshots or recordings here if applicable. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ ## Summary by CodeRabbit * **Refactor** * Increased the maximum number of requests that can be batched together, potentially improving performance and reducing network overhead for users. --- apps/mail/app/root.tsx | 2 +- apps/mail/lib/trpc.server.ts | 2 +- apps/mail/providers/query-provider.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/mail/app/root.tsx b/apps/mail/app/root.tsx index 22acfdacd1..c36afb2739 100644 --- a/apps/mail/app/root.tsx +++ b/apps/mail/app/root.tsx @@ -32,7 +32,7 @@ export const getServerTrpc = (req: Request) => createTRPCClient({ links: [ httpBatchLink({ - maxItems: 1, + maxItems: 8, url: getUrl(), transformer: superjson, headers: req.headers, diff --git a/apps/mail/lib/trpc.server.ts b/apps/mail/lib/trpc.server.ts index 78a5b127a7..6210a468ac 100644 --- a/apps/mail/lib/trpc.server.ts +++ b/apps/mail/lib/trpc.server.ts @@ -8,7 +8,7 @@ export const getServerTrpc = (req: Request) => createTRPCClient({ links: [ httpBatchLink({ - maxItems: 1, + maxItems: 8, url: getUrl(), transformer: superjson, headers: req.headers, diff --git a/apps/mail/providers/query-provider.tsx b/apps/mail/providers/query-provider.tsx index 2c0f429955..0e498c1789 100644 --- a/apps/mail/providers/query-provider.tsx +++ b/apps/mail/providers/query-provider.tsx @@ -90,7 +90,7 @@ export const trpcClient = createTRPCClient({ transformer: superjson, url: getUrl(), methodOverride: 'POST', - maxItems: 1, + maxItems: 8, fetch: (url, options) => fetch(url, { ...options, credentials: 'include' }).then((res) => { const currentPath = new URL(window.location.href).pathname; From f564423ab44ee80bef8ae54d879dd22102b31d78 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Jul 2025 22:20:12 -0700 Subject: [PATCH 3/4] Slight rollback (#1607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # READ CAREFULLY THEN REMOVE Remove bullet points that are not relevant. PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI. - Pull requests that do not follow these guidelines will be closed without review or comment. - If you use AI to write your PR description your pr will be close without review or comment. - If you are unsure about anything, feel free to ask for clarification. ## Description Please provide a clear description of your changes. --- ## Type of Change Please delete options that are not relevant. - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 💥 Breaking change (fix or feature with breaking changes) - [ ] 📝 Documentation update - [ ] 🎨 UI/UX improvement - [ ] 🔒 Security enhancement - [ ] ⚡ Performance improvement ## Areas Affected Please check all that apply: - [ ] Email Integration (Gmail, IMAP, etc.) - [ ] User Interface/Experience - [ ] Authentication/Authorization - [ ] Data Storage/Management - [ ] API Endpoints - [ ] Documentation - [ ] Testing Infrastructure - [ ] Development Workflow - [ ] Deployment/Infrastructure ## Testing Done Describe the tests you've done: - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] Cross-browser testing (if UI changes) - [ ] Mobile responsiveness verified (if UI changes) ## Security Considerations For changes involving data or authentication: - [ ] No sensitive data is exposed - [ ] Authentication checks are in place - [ ] Input validation is implemented - [ ] Rate limiting is considered (if applicable) ## Checklist - [ ] I have read the [CONTRIBUTING](https://github.com/Mail-0/Zero/blob/staging/.github/CONTRIBUTING.md) document - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in complex areas - [ ] I have updated the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix/feature works - [ ] All tests pass locally - [ ] Any dependent changes are merged and published ## Additional Notes Add any other context about the pull request here. ## Screenshots/Recordings Add screenshots or recordings here if applicable. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ --- apps/server/src/pipelines.ts | 23 ++++--- apps/server/src/routes/chat.ts | 100 ++++++++++++++-------------- apps/server/src/trpc/routes/mail.ts | 38 +++++------ 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/apps/server/src/pipelines.ts b/apps/server/src/pipelines.ts index 18ef0b4725..6a19acf62e 100644 --- a/apps/server/src/pipelines.ts +++ b/apps/server/src/pipelines.ts @@ -59,7 +59,7 @@ export class MainWorkflow extends WorkflowEntrypoint { const match = subscriptionName.toString().match(regex); if (!match) { log('[MAIN_WORKFLOW] Invalid subscription name:', subscriptionName); - throw new Error('Invalid subscription name'); + throw new Error(`Invalid subscription name ${subscriptionName}`); } const [, connectionId] = match; log('[MAIN_WORKFLOW] Extracted connectionId:', connectionId); @@ -69,11 +69,11 @@ export class MainWorkflow extends WorkflowEntrypoint { const status = await env.subscribed_accounts.get(`${connectionId}__${providerId}`); if (!status || status === 'pending') { log('[MAIN_WORKFLOW] Connection id is missing or not enabled %s', connectionId); - throw new Error('Connection is not enabled'); + return 'Connection is not enabled'; } if (!isValidUUID(connectionId)) { log('[MAIN_WORKFLOW] Invalid connection id format:', connectionId); - throw new Error('Invalid connection id'); + return 'Invalid connection id'; } const previousHistoryId = await env.gmail_history_id.get(connectionId); if (providerId === EProviders.google) { @@ -164,9 +164,9 @@ export class ZeroWorkflow extends WorkflowEntrypoint { .select() .from(connection) .where(eq(connection.id, connectionId.toString())); - if (!foundConnection) throw new Error('Connection not found'); + if (!foundConnection) throw new Error(`Connection not found ${connectionId}`); if (!foundConnection.accessToken || !foundConnection.refreshToken) - throw new Error('Connection is not authorized'); + throw new Error(`Connection is not authorized ${connectionId}`); log('[ZERO_WORKFLOW] Found connection:', foundConnection.id); return foundConnection; }); @@ -182,7 +182,7 @@ export class ZeroWorkflow extends WorkflowEntrypoint { const { history } = await driver.listHistory( historyId.toString(), ); - if (!history.length) throw new Error('No history found'); + if (!history.length) throw new Error(`No history found ${historyId} ${connectionId}`); log('[ZERO_WORKFLOW] Found history entries:', history.length); return history; } catch (error) { @@ -386,9 +386,9 @@ export class ThreadWorkflow extends WorkflowEntrypoint { .from(connection) .where(eq(connection.id, connectionId.toString())); this.ctx.waitUntil(conn.end()); - if (!foundConnection) throw new Error('Connection not found'); + if (!foundConnection) throw new Error(`Connection not found ${connectionId}`); if (!foundConnection.accessToken || !foundConnection.refreshToken) - throw new Error('Connection is not authorized'); + throw new Error(`Connection is not authorized ${connectionId}`); log('[THREAD_WORKFLOW] Found connection:', foundConnection.id); return foundConnection; }, @@ -441,7 +441,7 @@ export class ThreadWorkflow extends WorkflowEntrypoint { return step.do(`[ZERO] Vectorize Message ${message.id}`, async () => { log('[THREAD_WORKFLOW] Converting message to XML:', message.id); const prompt = await messageToXML(message); - if (!prompt) throw new Error('Message has no prompt'); + if (!prompt) throw new Error(`Message has no prompt ${message.id}`); log('[THREAD_WORKFLOW] Got XML prompt for message:', message.id); log('[THREAD_WORKFLOW] Message:', message); @@ -512,7 +512,8 @@ export class ThreadWorkflow extends WorkflowEntrypoint { }, ); - if (!embeddingVector) throw new Error('Message Embedding vector is null'); + if (!embeddingVector) + throw new Error(`Message Embedding vector is null ${message.id}`); return { id: message.id, @@ -688,7 +689,7 @@ export class ThreadWorkflow extends WorkflowEntrypoint { }, ); - if (!embeddingVector) throw new Error('Thread Embedding vector is null'); + if (!embeddingVector) return console.error('Thread Embedding vector is null'); try { log('[THREAD_WORKFLOW] Upserting thread vector'); diff --git a/apps/server/src/routes/chat.ts b/apps/server/src/routes/chat.ts index 35d36bd758..75df711a59 100644 --- a/apps/server/src/routes/chat.ts +++ b/apps/server/src/routes/chat.ts @@ -189,19 +189,19 @@ export class AgentRpcDO extends RpcTarget { async markThreadsRead(threadIds: string[]) { const result = await this.mainDo.markThreadsRead(threadIds); - await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); + // await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); return result; } async markThreadsUnread(threadIds: string[]) { const result = await this.mainDo.markThreadsUnread(threadIds); - await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); + // await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); return result; } async modifyLabels(threadIds: string[], addLabelIds: string[], removeLabelIds: string[]) { const result = await this.mainDo.modifyLabels(threadIds, addLabelIds, removeLabelIds); - await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); + // await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); return result; } @@ -233,13 +233,13 @@ export class AgentRpcDO extends RpcTarget { async markAsRead(threadIds: string[]) { const result = await this.mainDo.markAsRead(threadIds); - await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); + // await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); return result; } async markAsUnread(threadIds: string[]) { const result = await this.mainDo.markAsUnread(threadIds); - await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); + // await Promise.all(threadIds.map((id) => this.mainDo.syncThread(id))); return result; } @@ -310,19 +310,19 @@ export class ZeroAgent extends AIChatAgent { constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); if (shouldDropTables) this.dropTables(); - this.sql` - CREATE TABLE IF NOT EXISTS threads ( - id TEXT PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - thread_id TEXT NOT NULL, - provider_id TEXT NOT NULL, - latest_sender TEXT, - latest_received_on TEXT, - latest_subject TEXT, - latest_label_ids TEXT - ); - `; + // this.sql` + // CREATE TABLE IF NOT EXISTS threads ( + // id TEXT PRIMARY KEY, + // created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + // updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + // thread_id TEXT NOT NULL, + // provider_id TEXT NOT NULL, + // latest_sender TEXT, + // latest_received_on TEXT, + // latest_subject TEXT, + // latest_label_ids TEXT + // ); + // `; } async dropTables() { @@ -388,10 +388,10 @@ export class ZeroAgent extends AIChatAgent { }); if (_connection) this.driver = connectionToDriver(_connection); this.ctx.waitUntil(conn.end()); - this.ctx.waitUntil(this.syncThreads('inbox')); - this.ctx.waitUntil(this.syncThreads('sent')); - this.ctx.waitUntil(this.syncThreads('spam')); - this.ctx.waitUntil(this.syncThreads('archive')); + // this.ctx.waitUntil(this.syncThreads('inbox')); + // this.ctx.waitUntil(this.syncThreads('sent')); + // this.ctx.waitUntil(this.syncThreads('spam')); + // this.ctx.waitUntil(this.syncThreads('archive')); } } @@ -515,34 +515,34 @@ export class ZeroAgent extends AIChatAgent { this.cancelChatRequest(data.id); break; } - case IncomingMessageType.Mail_List: { - const result = await this.getThreadsFromDB({ - labelIds: data.labelIds, - folder: data.folder, - q: data.query, - max: data.maxResults, - cursor: data.pageToken, - }); - this.currentFolder = data.folder; - connection.send( - JSON.stringify({ - type: OutgoingMessageType.Mail_List, - result, - }), - ); - break; - } - case IncomingMessageType.Mail_Get: { - const result = await this.getThreadFromDB(data.threadId); - connection.send( - JSON.stringify({ - type: OutgoingMessageType.Mail_Get, - result, - threadId: data.threadId, - }), - ); - break; - } + // case IncomingMessageType.Mail_List: { + // const result = await this.getThreadsFromDB({ + // labelIds: data.labelIds, + // folder: data.folder, + // q: data.query, + // max: data.maxResults, + // cursor: data.pageToken, + // }); + // this.currentFolder = data.folder; + // connection.send( + // JSON.stringify({ + // type: OutgoingMessageType.Mail_List, + // result, + // }), + // ); + // break; + // } + // case IncomingMessageType.Mail_Get: { + // const result = await this.getThreadFromDB(data.threadId); + // connection.send( + // JSON.stringify({ + // type: OutgoingMessageType.Mail_Get, + // result, + // threadId: data.threadId, + // }), + // ); + // break; + // } } } } diff --git a/apps/server/src/trpc/routes/mail.ts b/apps/server/src/trpc/routes/mail.ts index 2450d62bdd..623ffcb2db 100644 --- a/apps/server/src/trpc/routes/mail.ts +++ b/apps/server/src/trpc/routes/mail.ts @@ -36,7 +36,7 @@ export const mailRouter = router({ .query(async ({ input, ctx }) => { const { activeConnection } = ctx; const agent = await getZeroAgent(activeConnection.id); - return await agent.getThreadFromDB(input.id); + return await agent.getThread(input.id); }), count: activeDriverProcedure .output( @@ -75,24 +75,24 @@ export const mailRouter = router({ }); return drafts; } - if (q) { - const threadsResponse = await agent.listThreads({ - labelIds: labelIds, - maxResults: max, - pageToken: cursor, - query: q, - folder, - }); - return threadsResponse; - } - const folderLabelId = getFolderLabelId(folder); - const labelIdsToUse = folderLabelId ? [...labelIds, folderLabelId] : labelIds; - const threadsResponse = await agent.getThreadsFromDB({ - labelIds: labelIdsToUse, - max: max, - cursor: cursor, + // if (q) { + const threadsResponse = await agent.listThreads({ + labelIds: labelIds, + maxResults: max, + pageToken: cursor, + query: q, + folder, }); return threadsResponse; + // } + // const folderLabelId = getFolderLabelId(folder); + // const labelIdsToUse = folderLabelId ? [...labelIds, folderLabelId] : labelIds; + // const threadsResponse = await agent.getThreadsFromDB({ + // labelIds: labelIdsToUse, + // max: max, + // cursor: cursor, + // }); + // return threadsResponse; }), markAsRead: activeDriverProcedure .input( @@ -173,7 +173,7 @@ export const mailRouter = router({ } const threadResults: PromiseSettledResult<{ messages: { tags: { name: string }[] }[] }>[] = - await Promise.allSettled(threadIds.map((id) => agent.getThreadFromDB(id))); + await Promise.allSettled(threadIds.map((id) => agent.getThread(id))); let anyStarred = false; let processedThreads = 0; @@ -217,7 +217,7 @@ export const mailRouter = router({ } const threadResults: PromiseSettledResult<{ messages: { tags: { name: string }[] }[] }>[] = - await Promise.allSettled(threadIds.map((id) => agent.getThreadFromDB(id))); + await Promise.allSettled(threadIds.map((id) => agent.getThread(id))); let anyImportant = false; let processedThreads = 0; From 142bbaac06da0f820e3f24941a98e5a87e669eb1 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 2 Jul 2025 22:44:30 -0700 Subject: [PATCH 4/4] Typo (#1608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # READ CAREFULLY THEN REMOVE Remove bullet points that are not relevant. PLEASE REFRAIN FROM USING AI TO WRITE YOUR CODE AND PR DESCRIPTION. IF YOU DO USE AI TO WRITE YOUR CODE PLEASE PROVIDE A DESCRIPTION AND REVIEW IT CAREFULLY. MAKE SURE YOU UNDERSTAND THE CODE YOU ARE SUBMITTING USING AI. - Pull requests that do not follow these guidelines will be closed without review or comment. - If you use AI to write your PR description your pr will be close without review or comment. - If you are unsure about anything, feel free to ask for clarification. ## Description Please provide a clear description of your changes. --- ## Type of Change Please delete options that are not relevant. - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (non-breaking change which adds functionality) - [ ] 💥 Breaking change (fix or feature with breaking changes) - [ ] 📝 Documentation update - [ ] 🎨 UI/UX improvement - [ ] 🔒 Security enhancement - [ ] ⚡ Performance improvement ## Areas Affected Please check all that apply: - [ ] Email Integration (Gmail, IMAP, etc.) - [ ] User Interface/Experience - [ ] Authentication/Authorization - [ ] Data Storage/Management - [ ] API Endpoints - [ ] Documentation - [ ] Testing Infrastructure - [ ] Development Workflow - [ ] Deployment/Infrastructure ## Testing Done Describe the tests you've done: - [ ] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] Cross-browser testing (if UI changes) - [ ] Mobile responsiveness verified (if UI changes) ## Security Considerations For changes involving data or authentication: - [ ] No sensitive data is exposed - [ ] Authentication checks are in place - [ ] Input validation is implemented - [ ] Rate limiting is considered (if applicable) ## Checklist - [ ] I have read the [CONTRIBUTING](https://github.com/Mail-0/Zero/blob/staging/.github/CONTRIBUTING.md) document - [ ] My code follows the project's style guidelines - [ ] I have performed a self-review of my code - [ ] I have commented my code, particularly in complex areas - [ ] I have updated the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix/feature works - [ ] All tests pass locally - [ ] Any dependent changes are merged and published ## Additional Notes Add any other context about the pull request here. ## Screenshots/Recordings Add screenshots or recordings here if applicable. --- _By submitting this pull request, I confirm that my contribution is made under the terms of the project's license._ --- apps/server/src/routes/chat.ts | 448 ++++++++++++++++----------------- 1 file changed, 224 insertions(+), 224 deletions(-) diff --git a/apps/server/src/routes/chat.ts b/apps/server/src/routes/chat.ts index 75df711a59..6df33dc171 100644 --- a/apps/server/src/routes/chat.ts +++ b/apps/server/src/routes/chat.ts @@ -180,11 +180,11 @@ export class AgentRpcDO extends RpcTarget { labelIds?: string[]; pageToken?: string; }) { - return await this.mainDo.listThreads(params); + return await this.mainDo.list(params); } async getThread(threadId: string) { - return await this.mainDo.getThreadFromDB(threadId); + return await this.mainDo.get(threadId); } async markThreadsRead(threadIds: string[]) { @@ -279,19 +279,19 @@ export class AgentRpcDO extends RpcTarget { return this.mainDo.broadcast(message); } - async getThreadsFromDB(params: { - labelIds?: string[]; - folder?: string; - q?: string; - max?: number; - cursor?: string; - }) { - return await this.mainDo.getThreadsFromDB(params); - } + // async getThreadsFromDB(params: { + // labelIds?: string[]; + // folder?: string; + // q?: string; + // max?: number; + // cursor?: string; + // }) { + // return await this.mainDo.getThreadsFromDB(params); + // } - async getThreadFromDB(id: string) { - return await this.mainDo.getThreadFromDB(id); - } + // async getThreadFromDB(id: string) { + // return await this.mainDo.getThreadFromDB(id); + // } async syncThreads(folder: string) { return await this.mainDo.syncThreads(folder); @@ -942,217 +942,217 @@ export class ZeroAgent extends AIChatAgent { } } - async getThreadsFromDB(params: { - labelIds?: string[]; - folder?: string; - q?: string; - max?: number; - cursor?: string; - }) { - const { labelIds = [], folder, q, max = 50, cursor } = params; - - try { - // Build WHERE conditions - const whereConditions: string[] = []; - - // Add folder condition (maps to specific label) - if (folder) { - const folderLabel = folder.toUpperCase(); - whereConditions.push(`EXISTS ( - SELECT 1 FROM json_each(latest_label_ids) WHERE value = '${folderLabel}' - )`); - } - - // Add label conditions (OR logic for multiple labels) - if (labelIds.length > 0) { - if (labelIds.length === 1) { - whereConditions.push(`EXISTS ( - SELECT 1 FROM json_each(latest_label_ids) WHERE value = '${labelIds[0]}' - )`); - } else { - // Multiple labels with OR logic - const multiLabelCondition = labelIds - .map( - (labelId) => - `EXISTS (SELECT 1 FROM json_each(latest_label_ids) WHERE value = '${labelId}')`, - ) - .join(' OR '); - whereConditions.push(`(${multiLabelCondition})`); - } - } - - // // Add search query condition - // if (q) { - // const searchTerm = q.replace(/'/g, "''"); // Escape single quotes - // whereConditions.push(`( - // latest_subject LIKE '%${searchTerm}%' OR - // latest_sender LIKE '%${searchTerm}%' OR - // messages LIKE '%${searchTerm}%' - // )`); - // } - - // Add cursor condition - if (cursor) { - whereConditions.push(`latest_received_on < '${cursor}'`); - } - - // Execute query based on conditions - let result; - - if (whereConditions.length === 0) { - // No conditions - result = await this.sql` - SELECT id, latest_received_on - FROM threads - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } else if (whereConditions.length === 1) { - // Single condition - const condition = whereConditions[0]; - if (condition.includes('latest_received_on <')) { - const cursorValue = cursor!; - result = await this.sql` - SELECT id, latest_received_on - FROM threads - WHERE latest_received_on < ${cursorValue} - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } else if (folder) { - // Folder condition - const folderLabel = folder.toUpperCase(); - result = await this.sql` - SELECT id, latest_received_on - FROM threads - WHERE EXISTS ( - SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${folderLabel} - ) - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } else { - // Single label condition - const labelId = labelIds[0]; - result = await this.sql` - SELECT id, latest_received_on - FROM threads - WHERE EXISTS ( - SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${labelId} - ) - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } - } else { - // Multiple conditions - handle combinations - if (folder && labelIds.length === 0 && cursor) { - // Folder + cursor - const folderLabel = folder.toUpperCase(); - result = await this.sql` - SELECT id, latest_received_on - FROM threads - WHERE EXISTS ( - SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${folderLabel} - ) AND latest_received_on < ${cursor} - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } else if (labelIds.length === 1 && cursor && !folder) { - // Single label + cursor - const labelId = labelIds[0]; - result = await this.sql` - SELECT id, latest_received_on - FROM threads - WHERE EXISTS ( - SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${labelId} - ) AND latest_received_on < ${cursor} - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } else { - // For now, fallback to just cursor if complex combinations - const cursorValue = cursor || ''; - result = await this.sql` - SELECT id, latest_received_on - FROM threads - WHERE latest_received_on < ${cursorValue} - ORDER BY latest_received_on DESC - LIMIT ${max} - `; - } - } - - const threads = result.map((row: any) => ({ - id: row.id, - historyId: null, - })); - - // Use latest_received_on for pagination cursor - const nextPageToken = - threads.length === max && result.length > 0 - ? result[result.length - 1].latest_received_on - : null; - - return { - threads, - nextPageToken, - }; - } catch (error) { - console.error('Failed to get threads from database:', error); - throw error; - } - } - - async getThreadFromDB(id: string): Promise { - try { - const result = this.sql` - SELECT - id, - thread_id, - provider_id, - latest_sender, - latest_received_on, - latest_subject, - latest_label_ids, - created_at, - updated_at - FROM threads - WHERE id = ${id} - LIMIT 1 - `; - - if (result.length === 0) { - this.ctx.waitUntil(this.syncThread(id)); - return { - messages: [], - latest: undefined, - hasUnread: false, - totalReplies: 0, - labels: [], - } satisfies IGetThreadResponse; - } - - const row = result[0] as any; - const storedMessages = await env.THREADS_BUCKET.get(this.getThreadKey(id)); - const latestLabelIds = JSON.parse(row.latest_label_ids || '[]'); - - const messages: ParsedMessage[] = storedMessages - ? JSON.parse(await storedMessages.text()) - : []; + // async getThreadsFromDB(params: { + // labelIds?: string[]; + // folder?: string; + // q?: string; + // max?: number; + // cursor?: string; + // }) { + // const { labelIds = [], folder, q, max = 50, cursor } = params; + + // try { + // // Build WHERE conditions + // const whereConditions: string[] = []; + + // // Add folder condition (maps to specific label) + // if (folder) { + // const folderLabel = folder.toUpperCase(); + // whereConditions.push(`EXISTS ( + // SELECT 1 FROM json_each(latest_label_ids) WHERE value = '${folderLabel}' + // )`); + // } + + // // Add label conditions (OR logic for multiple labels) + // if (labelIds.length > 0) { + // if (labelIds.length === 1) { + // whereConditions.push(`EXISTS ( + // SELECT 1 FROM json_each(latest_label_ids) WHERE value = '${labelIds[0]}' + // )`); + // } else { + // // Multiple labels with OR logic + // const multiLabelCondition = labelIds + // .map( + // (labelId) => + // `EXISTS (SELECT 1 FROM json_each(latest_label_ids) WHERE value = '${labelId}')`, + // ) + // .join(' OR '); + // whereConditions.push(`(${multiLabelCondition})`); + // } + // } + + // // // Add search query condition + // // if (q) { + // // const searchTerm = q.replace(/'/g, "''"); // Escape single quotes + // // whereConditions.push(`( + // // latest_subject LIKE '%${searchTerm}%' OR + // // latest_sender LIKE '%${searchTerm}%' OR + // // messages LIKE '%${searchTerm}%' + // // )`); + // // } + + // // Add cursor condition + // if (cursor) { + // whereConditions.push(`latest_received_on < '${cursor}'`); + // } + + // // Execute query based on conditions + // let result; + + // if (whereConditions.length === 0) { + // // No conditions + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } else if (whereConditions.length === 1) { + // // Single condition + // const condition = whereConditions[0]; + // if (condition.includes('latest_received_on <')) { + // const cursorValue = cursor!; + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // WHERE latest_received_on < ${cursorValue} + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } else if (folder) { + // // Folder condition + // const folderLabel = folder.toUpperCase(); + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // WHERE EXISTS ( + // SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${folderLabel} + // ) + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } else { + // // Single label condition + // const labelId = labelIds[0]; + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // WHERE EXISTS ( + // SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${labelId} + // ) + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } + // } else { + // // Multiple conditions - handle combinations + // if (folder && labelIds.length === 0 && cursor) { + // // Folder + cursor + // const folderLabel = folder.toUpperCase(); + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // WHERE EXISTS ( + // SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${folderLabel} + // ) AND latest_received_on < ${cursor} + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } else if (labelIds.length === 1 && cursor && !folder) { + // // Single label + cursor + // const labelId = labelIds[0]; + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // WHERE EXISTS ( + // SELECT 1 FROM json_each(latest_label_ids) WHERE value = ${labelId} + // ) AND latest_received_on < ${cursor} + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } else { + // // For now, fallback to just cursor if complex combinations + // const cursorValue = cursor || ''; + // result = await this.sql` + // SELECT id, latest_received_on + // FROM threads + // WHERE latest_received_on < ${cursorValue} + // ORDER BY latest_received_on DESC + // LIMIT ${max} + // `; + // } + // } + + // const threads = result.map((row: any) => ({ + // id: row.id, + // historyId: null, + // })); + + // // Use latest_received_on for pagination cursor + // const nextPageToken = + // threads.length === max && result.length > 0 + // ? result[result.length - 1].latest_received_on + // : null; + + // return { + // threads, + // nextPageToken, + // }; + // } catch (error) { + // console.error('Failed to get threads from database:', error); + // throw error; + // } + // } - return { - messages, - latest: messages.length > 0 ? messages[messages.length - 1] : undefined, - hasUnread: latestLabelIds.includes('UNREAD'), - totalReplies: messages.length, - labels: latestLabelIds.map((id: string) => ({ id, name: id })), - } satisfies IGetThreadResponse; - } catch (error) { - console.error('Failed to get thread from database:', error); - throw error; - } - } + // async getThreadFromDB(id: string): Promise { + // try { + // const result = this.sql` + // SELECT + // id, + // thread_id, + // provider_id, + // latest_sender, + // latest_received_on, + // latest_subject, + // latest_label_ids, + // created_at, + // updated_at + // FROM threads + // WHERE id = ${id} + // LIMIT 1 + // `; + + // if (result.length === 0) { + // this.ctx.waitUntil(this.syncThread(id)); + // return { + // messages: [], + // latest: undefined, + // hasUnread: false, + // totalReplies: 0, + // labels: [], + // } satisfies IGetThreadResponse; + // } + + // const row = result[0] as any; + // const storedMessages = await env.THREADS_BUCKET.get(this.getThreadKey(id)); + // const latestLabelIds = JSON.parse(row.latest_label_ids || '[]'); + + // const messages: ParsedMessage[] = storedMessages + // ? JSON.parse(await storedMessages.text()) + // : []; + + // return { + // messages, + // latest: messages.length > 0 ? messages[messages.length - 1] : undefined, + // hasUnread: latestLabelIds.includes('UNREAD'), + // totalReplies: messages.length, + // labels: latestLabelIds.map((id: string) => ({ id, name: id })), + // } satisfies IGetThreadResponse; + // } catch (error) { + // console.error('Failed to get thread from database:', error); + // throw error; + // } + // } } export class ZeroMCP extends McpAgent {