-
-
Notifications
You must be signed in to change notification settings - Fork 8
feat: Implement Calendar Notepad with AI integration and collaborator tagging #652
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
base: main
Are you sure you want to change the base?
Changes from all commits
bfa31eb
f2d047f
823d967
f4890d8
aeb89b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,5 @@ import ReactMarkdown, { Options } from 'react-markdown' | |
| export const MemoizedReactMarkdown: FC<Options> = memo( | ||
| ReactMarkdown, | ||
| (prevProps, nextProps) => | ||
| prevProps.children === nextProps.children && | ||
| prevProps.className === nextProps.className | ||
| prevProps.children === nextProps.children | ||
|
Comment on lines
6
to
+7
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify where MemoizedReactMarkdown is used and whether non-children props vary.
rg -n -C3 '\bMemoizedReactMarkdown\b'
rg -n -C3 '<MemoizedReactMarkdown[^>]*className=|<MemoizedReactMarkdown[^>]*components=|<MemoizedReactMarkdown[^>]*remarkPlugins=|<MemoizedReactMarkdown[^>]*rehypePlugins='Repository: QueueLab/QCX Length of output: 1627 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== components/ui/markdown.tsx (lines 1-120) ==="
nl -ba components/ui/markdown.tsx | sed -n '1,120p'
echo "=== components/message.tsx (around MemoizedReactMarkdown usage) ==="
nl -ba components/message.tsx | sed -n '1,120p'Repository: QueueLab/QCX Length of output: 166 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== components/ui/markdown.tsx (first 160 lines) ==="
cat -n components/ui/markdown.tsx | sed -n '1,160p'
echo "=== components/message.tsx (first 220 lines) ==="
cat -n components/message.tsx | sed -n '1,220p'Repository: QueueLab/QCX Length of output: 2287 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n "MemoizedReactMarkdown" -SRepository: QueueLab/QCX Length of output: 332 Custom
🤖 Prompt for AI Agents |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,33 @@ export async function getNotes(date: Date, chatId: string | null): Promise<Calen | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves all notes for a specific chat session. | ||
| * @param chatId - The ID of the chat session. | ||
| * @returns A promise that resolves to an array of notes. | ||
| */ | ||
| export async function getChatNotes(chatId: string): Promise<CalendarNote[]> { | ||
| const userId = await getCurrentUserIdOnServer() | ||
| if (!userId) { | ||
| console.error('getChatNotes: User not authenticated') | ||
| return [] | ||
| } | ||
|
|
||
| try { | ||
| const notes = await db | ||
| .select() | ||
| .from(calendarNotes) | ||
| .where(and(eq(calendarNotes.userId, userId), eq(calendarNotes.chatId, chatId))) | ||
| .orderBy(desc(calendarNotes.date)) | ||
| .execute() | ||
|
|
||
| return notes | ||
| } catch (error) { | ||
| console.error('Error fetching chat notes:', error) | ||
| return [] | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Saves a new note or updates an existing one. | ||
| * @param noteData - The note data to save. | ||
|
|
@@ -84,9 +111,16 @@ export async function saveNote(noteData: NewCalendarNote | CalendarNote): Promis | |
| } else { | ||
| // Create new note | ||
| try { | ||
| // Parse @mentions from content | ||
| const mentions = noteData.content.match(/@(\w+)/g)?.map(m => m.substring(1)) || null; | ||
| const updatedNoteData = { | ||
| ...noteData, | ||
| userTags: mentions | ||
| }; | ||
|
Comment on lines
+114
to
+119
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deduplicate extracted The mention extraction correctly parses 🔧 Proposed fix // Parse `@mentions` from content
- const mentions = noteData.content.match(/@(\w+)/g)?.map(m => m.substring(1)) || null;
+ const matches = noteData.content.match(/@(\w+)/g);
+ const mentions = matches ? [...new Set(matches.map(m => m.substring(1)))] : null;
const updatedNoteData = {
...noteData,
userTags: mentions
};🤖 Prompt for AI AgentsSource: Learnings |
||
|
|
||
| const [newNote] = await db | ||
| .insert(calendarNotes) | ||
| .values({ ...noteData, userId }) | ||
| .values({ ...updatedNoteData, userId }) | ||
| .returning(); | ||
|
|
||
| if (newNote && newNote.chatId) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { tool } from 'ai' | ||
| import { calendarSchema } from '@/lib/schema/calendar' | ||
| import { saveNote, getChatNotes } from '@/lib/actions/calendar' | ||
| import { ToolProps } from '.' | ||
| import { Section } from '@/components/section' | ||
| import { BotMessage } from '@/components/message' | ||
| import { nanoid } from '@/lib/utils' | ||
|
|
||
| export const calendarTool = ({ uiStream }: ToolProps) => | ||
| tool({ | ||
| description: 'Create reminders, add notes to the calendar, or list existing notes for the current chat.', | ||
| parameters: calendarSchema, | ||
| execute: async ({ action, content, date, location }) => { | ||
| // In a real execution, we need the chatId. | ||
| // Since tools are called within the submit action context, | ||
| // we might need to find a way to pass chatId here if it's not readily available. | ||
| // For this implementation, we'll assume the AI state provides it or we handle it in app/actions. | ||
|
|
||
| if (action === 'create_note' && content) { | ||
| // We'll return a result that the caller (submit action) can use to actually save | ||
| return { | ||
| type: 'CALENDAR_ACTION', | ||
| action: 'create', | ||
| note: { | ||
| content, | ||
| date: date ? new Date(date) : new Date(), | ||
| locationTags: location ? { | ||
| type: 'Point', | ||
| coordinates: [location.longitude, location.latitude] | ||
| } : null | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (action === 'list_notes') { | ||
| return { | ||
| type: 'CALENDAR_ACTION', | ||
| action: 'list' | ||
| } | ||
| } | ||
|
|
||
| return { error: 'Invalid calendar action' } | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not emit
success: truewhen note persistence fails.Line 542 can return
nullfromsaveNote, but Line 549 always reports success. That creates false confirmations and inconsistent chat/calendar state.Suggested fix
if (result.action === 'create') { const saved = await saveNote({ ...result.note, chatId: aiState.get().chatId, userId: '', // set in saveNote userTags: null, mapFeatureId: null }) - output.result = { success: true, note: saved } + output.result = saved + ? { success: true, note: saved } + : { success: false, error: 'Failed to save calendar note' } } else if (result.action === 'list') {🤖 Prompt for AI Agents