-
Notifications
You must be signed in to change notification settings - Fork 835
Knowledge for drafts and clean schema clean up #385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
a48a5b9
add basic knowledge page
elie222 22ceedd
Merge branch 'main' into knowledge
elie222 919067c
fetch knowledge
elie222 bf5e582
mutate on add entry
elie222 18c285e
Delete knowledge item
elie222 5747320
clean up code
elie222 385534c
add knowledge mdc
elie222 c70351b
Merge branch 'main' into knowledge
elie222 c20ab29
Merge branch 'main' into knowledge
elie222 c3fcce7
extract content
elie222 03db907
Merge branch 'main' into knowledge
elie222 9bdfc75
fix llm test file
elie222 838007d
extract knowledge test file
elie222 dec280d
rename
elie222 9138a38
logs
elie222 c2e3308
pass extractor tests
elie222 25fdd93
Merge branch 'main' into knowledge
elie222 439623d
package mdc
elie222 76df1fe
use tiptap for notes
elie222 803f88a
update package
elie222 fd56db2
Merge branch 'main' into knowledge
elie222 7536523
add extract history prompt
elie222 595467c
adjust extract
elie222 a0df6a7
generate reply with knowledge
elie222 10ce011
add economy model
elie222 5874894
update knowledge.mdc
elie222 9192d57
Merge branch 'main' into knowledge
elie222 4141bf9
Merge branch 'main' into knowledge
elie222 3339acf
adjust copy
elie222 bbd772a
knowledge migration
elie222 c8549b2
Merge branch 'main' into knowledge
elie222 c1c7c60
adjust tiptap styling
elie222 2af9ad0
fix knowledge not saving
elie222 3fc3497
Adjust needs reply check
elie222 c84dc8c
adjust prompt
elie222 f08719a
Refactor knowledge extraction logging and improve Gmail client access…
elie222 1624a6c
better logging in dev
elie222 13a3898
llm date helper
elie222 7acad75
add email date for llm
elie222 687531e
use economy model
elie222 d4c87a6
adjust copy
elie222 effa81c
show draft via knowledge base as default
elie222 fba31cd
Merge branch 'main' into knowledge
elie222 a6a4187
Adjust how we handle reply drafting and reply tracker labelling makin…
elie222 506b2cc
delete inline generate reply in favour of auto drafting
elie222 72e16de
migrate draftReplies to knowledge
elie222 79b2d85
refactor choose ai args into 2 files
elie222 ecd5ef3
massively restructure how we do reply tracking
elie222 0809351
migration
elie222 ac8db9e
fix onboarding
elie222 093f974
Merge branch 'main' into knowledge
elie222 a7a2a09
move feature rules to their own folder
elie222 13c8b75
add copy to kb page and show loading spinner for delete
elie222 9c7e57c
fix build
elie222 9eac680
Add reply tracker mdc
elie222 9c2a3e0
fix tests
elie222 27478db
remove old code
elie222 be79cde
adjust env var
elie222 e8121ac
adjust economy model handling
elie222 ecca97b
simplify to avoid recursion
elie222 ed8e5de
adjust open router usage
elie222 baacc47
delete old file
elie222 35f5c12
gracefully handle llm replying with string instead of boolean
elie222 17b1bbf
don't process reply tracker email if it's not the latest in the thread
elie222 fe886a6
fix build
elie222 ebe3ef3
label message instead of thread
elie222 821899e
adjust current time for llm
elie222 0f6b99b
adjust prompt
elie222 5007733
update getTodayForLLM function to accept a date parameter for improve…
elie222 b197f03
Add logging for missing API key in economy LLM provider selection
elie222 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
--- | ||
description: | ||
globs: | ||
alwaysApply: false | ||
--- | ||
# Knowledge Base | ||
|
||
This file explains the Knowledge Base feature and how it's implemented. | ||
|
||
The knowledge base helps users store and manage information that can be used to help draft responses to emails. It acts as a personal database of information that can be referenced when composing replies. | ||
|
||
## Overview | ||
|
||
Users can create, edit, and delete knowledge base entries. Each entry consists of: | ||
|
||
- A title for quick reference | ||
- Content that contains the actual information | ||
- Metadata like creation and update timestamps | ||
|
||
## Database Schema | ||
|
||
The `Knowledge` model in Prisma: | ||
|
||
```prisma | ||
model Knowledge { | ||
id String @id @default(cuid()) | ||
createdAt DateTime @default(now()) | ||
updatedAt DateTime @updatedAt | ||
title String | ||
content String | ||
|
||
userId String | ||
user User @relation(fields: [userId], references: [id], onDelete: Cascade) | ||
} | ||
``` | ||
|
||
Each knowledge entry belongs to a specific user and is automatically deleted if the user is deleted (cascade). | ||
|
||
## Main Files and Directories | ||
|
||
The knowledge base functionality is implemented in: | ||
|
||
- `apps/web/app/(app)/automation/knowledge/KnowledgeBase.tsx` - Main UI component | ||
- `apps/web/app/(app)/automation/knowledge/KnowledgeForm.tsx` - Form for creating/editing entries | ||
- `apps/web/utils/actions/knowledge.ts` - Server actions for CRUD operations | ||
- `apps/web/utils/actions/knowledge.validation.ts` - Zod validation schemas | ||
- `apps/web/app/api/knowledge/route.ts` - API route for fetching entries | ||
|
||
### AI Integration Files | ||
|
||
- `apps/web/utils/ai/knowledge/extract.ts` - Extract relevant knowledge from knowledge base entries | ||
- `apps/web/utils/ai/knowledge/extract-from-email-history.ts` - Extract context from previous emails | ||
- `apps/web/utils/ai/reply/draft-with-knowledge.ts` - Generate email drafts using extracted knowledge | ||
- `apps/web/utils/reply-tracker/generate-reply.ts` - Coordinates the extraction and drafting process | ||
- `apps/web/utils/llms/model-selector.ts` - Economy LLM selection for high-volume tasks | ||
|
||
## Features | ||
|
||
- **Create**: Users can add new knowledge entries with a title and content | ||
- **Read**: Entries are displayed in a table with title and last updated date | ||
- **Update**: Users can edit existing entries | ||
- **Delete**: Entries can be deleted with a confirmation dialog | ||
|
||
## Usage in Email Responses | ||
|
||
The knowledge base entries are used to help draft responses to emails. When composing a reply, the system can reference these entries to include relevant information, ensuring consistent and accurate responses. | ||
|
||
When drafting responses, we use two LLMs: | ||
|
||
1. A cheaper LLM that can process a lot of data (e.g. Google Gemini 2 Flash) | ||
2. A more expensive LLM to draft the response (e.g. Anthropic Sonnet 3.7) | ||
|
||
The cheaper LLM is an agent that extracts the key information needed for the drafter LLM. | ||
For example, the knowledge base may include 100 pages of content, and the LLM extracts half a page of knowledge to pass to the more expensive drafter LLM. | ||
|
||
## Dual LLM Architecture | ||
|
||
The dual LLM approach is implemented as follows: | ||
|
||
1. **Knowledge Extraction (Economy LLM)**: | ||
|
||
- Uses a more cost-efficient model like Gemini Flash for processing large volumes of knowledge base content | ||
- Analyzes all knowledge entries and extracts only relevant information based on the email content | ||
- Configured via environment variables (`ECONOMY_LLM_PROVIDER` and `ECONOMY_LLM_MODEL`) | ||
- If no specific economy model is configured, defaults to Gemini Flash when Google API key is available | ||
|
||
2. **Email Draft Generation (Core LLM)**: | ||
- Uses the default model (e.g., Anthropic Claude 3.7 Sonnet) for high-quality content generation | ||
- Receives the extracted relevant knowledge from the economy LLM | ||
- Generates the final email draft based on the provided context | ||
|
||
This architecture optimizes for both cost efficiency (using cheaper models for high-volume tasks) and quality (using premium models for user-facing content). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
--- | ||
description: | ||
globs: | ||
alwaysApply: false | ||
--- | ||
# Reply Tracker | ||
|
||
Reply Tracker (also known as Reply Zero) lets the user which emails need a reply for them and those they're awaiting a reply on. | ||
It updates the labels for the thread automatically in Gmail. | ||
|
||
The database models and fields that are used for this feature: | ||
|
||
- ThreadTracker | ||
- User.outboundReplyTracking | ||
- ActionType.TRACK_THREAD | ||
|
||
The system uses rules. The AI can choose which rule to use each time an email comes in. Rules are for incoming emails only. Not ongoing. | ||
When enabling the reply tracker, we create a rule for the user that has the following actions associated with it: | ||
- LABEL: "To Reply" | ||
- TRACK_THREAD | ||
- DRAFT_EMAIL (optional) | ||
|
||
We'll draft a reply for the user automatically if DRAFT_EMAIL is set. The draft will be generated using the email history for this sender as well as the Knowledge base. See `.cursor/rules/features/knowledge.mdc` for more on the Knowledge Base feature. | ||
|
||
Enabling `User.outboundReplyTracking` means that when a user sends an email, we'll run an LLM over the email and check if it needs a reply. If it does we'll mark it as Awaiting Reply. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
description: How to install packages | ||
globs: | ||
alwaysApply: false | ||
--- | ||
- Use `pnpm`. | ||
- Don't install in root. Install in `apps/web`: | ||
|
||
```sh | ||
cd apps/web | ||
pnpm add ... | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import { describe, expect, test, vi } from "vitest"; | ||
import { getActionItemsWithAiArgs } from "@/utils/ai/choose-rule/ai-choose-args"; | ||
import { type Action, ActionType, LogicalOperator } from "@prisma/client"; | ||
import type { RuleWithActions } from "@/utils/types"; | ||
import type { ParsedMessage, RuleWithActions } from "@/utils/types"; | ||
import { getActionItemsWithAiArgs } from "@/utils/ai/choose-rule/choose-args"; | ||
|
||
// pnpm test-ai ai-choose-args | ||
|
||
|
@@ -15,9 +15,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => { | |
const rule = getRule("Test rule", actions); | ||
|
||
const result = await getActionItemsWithAiArgs({ | ||
email: getEmail(), | ||
message: getParsedMessage({ | ||
subject: "Test subject", | ||
content: "Test content", | ||
}), | ||
user: getUser(), | ||
selectedRule: rule, | ||
gmail: {} as any, | ||
}); | ||
|
||
expect(result).toEqual(actions); | ||
|
@@ -33,12 +37,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => { | |
const rule = getRule("Choose this rule for meeting requests", actions); | ||
|
||
const result = await getActionItemsWithAiArgs({ | ||
email: getEmail({ | ||
message: getParsedMessage({ | ||
subject: "Quick question", | ||
content: "When is the meeting tomorrow?", | ||
}), | ||
user: getUser(), | ||
selectedRule: rule, | ||
gmail: {} as any, | ||
}); | ||
|
||
expect(result).toHaveLength(1); | ||
|
@@ -59,12 +64,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => { | |
); | ||
|
||
const result = await getActionItemsWithAiArgs({ | ||
email: getEmail({ | ||
message: getParsedMessage({ | ||
subject: "Quick question", | ||
content: "How much are pears?", | ||
}), | ||
user: getUser(), | ||
selectedRule: rule, | ||
gmail: {} as any, | ||
}); | ||
|
||
expect(result).toHaveLength(1); | ||
|
@@ -85,12 +91,13 @@ describe.skipIf(!isAiTest)("getActionItemsWithAiArgs", () => { | |
const rule = getRule("Test rule", actions); | ||
|
||
const result = await getActionItemsWithAiArgs({ | ||
email: getEmail({ | ||
message: getParsedMessage({ | ||
subject: "Project status", | ||
content: "Can you update me on the project status?", | ||
}), | ||
user: getUser(), | ||
selectedRule: rule, | ||
gmail: {} as any, | ||
}); | ||
|
||
expect(result).toHaveLength(2); | ||
|
@@ -120,13 +127,14 @@ Matt`, | |
); | ||
|
||
const result = await getActionItemsWithAiArgs({ | ||
email: getEmail({ | ||
message: getParsedMessage({ | ||
from: "[email protected]", | ||
subject: "fruits", | ||
content: "how much do apples cost?", | ||
}), | ||
user: getUser(), | ||
selectedRule: rule, | ||
gmail: {} as any, | ||
}); | ||
|
||
expect(result).toHaveLength(2); | ||
|
@@ -190,24 +198,40 @@ function getRule( | |
categoryFilterType: null, | ||
conditionalOperator: LogicalOperator.AND, | ||
type: null, | ||
trackReplies: null, | ||
}; | ||
} | ||
|
||
function getEmail({ | ||
function getParsedMessage({ | ||
from = "[email protected]", | ||
subject = "subject", | ||
content = "content", | ||
}: { from?: string; subject?: string; content?: string } = {}) { | ||
}): ParsedMessage { | ||
return { | ||
from, | ||
subject, | ||
content, | ||
id: "id", | ||
threadId: "thread-id", | ||
snippet: "", | ||
attachments: [], | ||
historyId: "history-id", | ||
sizeEstimate: 100, | ||
internalDate: new Date().toISOString(), | ||
inline: [], | ||
textPlain: content, | ||
// ...message, | ||
headers: { | ||
from, | ||
to: "[email protected]", | ||
subject, | ||
date: new Date().toISOString(), | ||
references: "", | ||
"message-id": "message-id", | ||
// ...message.headers, | ||
}, | ||
}; | ||
} | ||
|
||
function getUser() { | ||
return { | ||
id: "userId", | ||
aiModel: null, | ||
aiProvider: null, | ||
email: "[email protected]", | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.