-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Update ai-text-to-calendar extension #17930
base: main
Are you sure you want to change the base?
Changes from all commits
fdd6df0
5d36dea
753a4cc
f302a0d
337f296
67e7092
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. now we can check outlook in roadmap 🥳 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. done! |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,12 +19,14 @@ Configure the extension via `Raycast Settings > Extensions > AI Text to Calendar | |
| `model` | Model Name | string | false | LLM model name, default is `gpt-4o-mini` | | ||
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. syntax: Model name 'gpt-4o-mini' appears to be a typo (should be 'gpt-4' or similar) |
||
| `language` | Language | string | false | Language of the output text, default is `English` | | ||
| `endpoint` | Endpoint | string | false | LLM service endpoint (e.g., <https://api.deepseek.com/v1>), default is `https://api.openai.com/v1` | | ||
| `calendar` | Calendar | string | false | Select your calendar service, default is `googleCalendar`. | | ||
|
||
|
||
## TODO | ||
|
||
- [ ] User default settings (e.g., default date, time) | ||
- [ ] With supplementary information (e.g., selected text + user input) | ||
- [ ] Support for other calendar services (e.g., use Apple Script or Shortcut for Apple Calendar) | ||
- [x] Support for other calendar services (e.g., use Apple Script or Shortcut for Apple Calendar) | ||
|
||
## License | ||
|
||
|
@@ -37,4 +39,4 @@ This project is licensed under the MIT License. | |
| `play tennis with Mike tommorrow at 5pm in the park` | ✓ | ✓ | | ||
| `Math class next Monday at 10am in lecture hall` | x | ✓ | | ||
|
||
Tips: try to use advanced LLM models for better results, especially for date reasoning. | ||
Tips: try to use advanced LLM models for better results, especially for date reasoning. |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,7 +1,8 @@ | ||||||||||||||
import { Clipboard, getPreferenceValues, getSelectedText, open, showHUD, showToast, Toast } from "@raycast/api"; | ||||||||||||||
import OpenAI from "openai"; | ||||||||||||||
import { toURL } from "./calendars"; | ||||||||||||||
|
||||||||||||||
interface CalendarEvent { | ||||||||||||||
export interface CalendarEvent { | ||||||||||||||
title: string; | ||||||||||||||
start_date: string; | ||||||||||||||
start_time: string; | ||||||||||||||
|
@@ -17,6 +18,7 @@ export default async function main() { | |||||||||||||
const endpoint = getPreferenceValues().endpoint || "https://api.openai.com/v1"; | ||||||||||||||
const language = getPreferenceValues().language || "English"; | ||||||||||||||
const model = getPreferenceValues().model || "gpt-4o-mini"; | ||||||||||||||
const calendar = getPreferenceValues().calendar || "googleCalendar"; | ||||||||||||||
|
||||||||||||||
showToast({ style: Toast.Style.Animated, title: "Extracting..." }); | ||||||||||||||
const selectedText = await getSelectedText(); | ||||||||||||||
Comment on lines
23
to
24
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. logic: getSelectedText() should be wrapped in a try-catch block with graceful error handling
Suggested change
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. It's better not to throw a new error in the 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. This syntax of JS is interesting. How about using try-catch and handling the exception/err? |
||||||||||||||
|
@@ -26,7 +28,7 @@ export default async function main() { | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
const calendarEvent = JSON.parse(json) as CalendarEvent; | ||||||||||||||
const url = toURL(calendarEvent); | ||||||||||||||
const url = toURL(calendarEvent, calendar); | ||||||||||||||
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. [Suggestion] Consider separating calendar-specific URL generation into dedicated methods Would it be possible to split the toURL function’s switch-case into separate methods? For example: let url: string;
switch (calendar) {
case "outlookOffice365":
url = toOutlookOfficeURL(calendarEvent);
break;
case "outlookPersonal":
url = toOutlookPersonalURL(calendarEvent);
break;
case "googleCalendar":
default:
url = toGoogleCalendarURL(calendarEvent);
} This could help improve testability and make the code more maintainable. 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. Thank you for your review! I tried separating the code that generate url so that compatible calendars can be easily added and implemented. 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. LGTM! 🚀 |
||||||||||||||
|
||||||||||||||
await showHUD("Extracted! Copied to clipboard and opened in browser."); | ||||||||||||||
await Clipboard.copy(`${url}`); | ||||||||||||||
|
@@ -89,20 +91,3 @@ Note: | |||||||||||||
|
||||||||||||||
return response.choices[0].message.content; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
function toURL(json: CalendarEvent) { | ||||||||||||||
// Clean up and format dates/times - remove any non-numeric characters | ||||||||||||||
const startDateTime = `${json.start_date.replace(/-/g, "")}T${json.start_time.replace(/:/g, "")}00`; | ||||||||||||||
const endDateTime = `${json.end_date.replace(/-/g, "")}T${json.end_time.replace(/:/g, "")}00`; | ||||||||||||||
|
||||||||||||||
// Encode parameters for URL safety | ||||||||||||||
const params = { | ||||||||||||||
text: encodeURIComponent(json.title), | ||||||||||||||
dates: `${startDateTime}/${endDateTime}`, | ||||||||||||||
details: encodeURIComponent(json.details), | ||||||||||||||
location: encodeURIComponent(json.location), | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${params.text}&dates=${params.dates}&details=${params.details}&location=${params.location}&trp=false`; | ||||||||||||||
return url; | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { CalendarEvent } from "../ai-text-to-calendar"; | ||
import { parseDateTimes } from "../utils"; | ||
|
||
export function toGoolgleCalenderURL(event: CalendarEvent) { | ||
const dateTimes = parseDateTimes(event); | ||
|
||
const startDateTime = `${dateTimes.startDate}T${dateTimes.startTime}00`; | ||
const endDateTime = `${dateTimes.endDate}T${dateTimes.endTime}00`; | ||
const params = { | ||
text: encodeURIComponent(event.title), | ||
dates: `${startDateTime}/${endDateTime}`, | ||
details: encodeURIComponent(event.details), | ||
location: encodeURIComponent(event.location), | ||
}; | ||
return `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${params.text}&dates=${params.dates}&details=${params.details}&location=${params.location}&trp=false`; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { CalendarEvent } from "../ai-text-to-calendar"; | ||
import { toGoolgleCalenderURL } from "./googleCalendar"; | ||
import { toOutlookOfficeURL, toOutlookPersonalURL } from "./outlookCalendar"; | ||
|
||
type CalendarType = "googleCalendar" | "outlookPersonal" | "outlookOffice"; | ||
|
||
export interface CalendarURLGenerator { | ||
(event: CalendarEvent): string; | ||
} | ||
const CALENDAR_URL_GENERATORS: Record<CalendarType, CalendarURLGenerator> = { | ||
googleCalendar: toGoolgleCalenderURL, | ||
outlookPersonal: toOutlookPersonalURL, | ||
outlookOffice: toOutlookOfficeURL, | ||
}; | ||
|
||
export function toURL(calendarEvent: CalendarEvent, calendarType: CalendarType) { | ||
const generator = CALENDAR_URL_GENERATORS[calendarType]; | ||
return generator(calendarEvent); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { CalendarEvent } from "../ai-text-to-calendar"; | ||
import { parseDateTimes } from "../utils"; | ||
|
||
export function toOutlookOfficeURL(event: CalendarEvent) { | ||
const baseUrl = "https://outlook.office.com/calendar/deeplink/compose"; | ||
return baseUrl + createQueryStringOfOutlook(event); | ||
} | ||
|
||
export function toOutlookPersonalURL(event: CalendarEvent) { | ||
const baseUrl = "https://outlook.live.com/calendar/deeplink/compose"; | ||
return baseUrl + createQueryStringOfOutlook(event); | ||
} | ||
|
||
function formatDateTimeForOutlook(dateStr: string, timeStr: string): string { | ||
if (dateStr.length !== 8) { | ||
throw new Error(`Invalid date format: ${dateStr}. Expected YYYYMMDD.`); | ||
} | ||
if (timeStr.length !== 6) { | ||
throw new Error(`Invalid time format: ${timeStr}. Expected hhmmss.`); | ||
} | ||
|
||
const year = dateStr.slice(0, 4); | ||
const month = dateStr.slice(4, 6); | ||
const day = dateStr.slice(6, 8); | ||
|
||
const hh = timeStr.slice(0, 2); | ||
const mm = timeStr.slice(2, 4); | ||
const ss = timeStr.slice(4, 6); | ||
|
||
return `${year}-${month}-${day}T${hh}:${mm}:${ss}00`; | ||
} | ||
|
||
function createQueryStringOfOutlook(event: CalendarEvent) { | ||
const dateTimes = parseDateTimes(event); | ||
|
||
const startDateTime = `${formatDateTimeForOutlook(dateTimes.startDate, dateTimes.startTime)}`; | ||
const endDateTime = `${formatDateTimeForOutlook(dateTimes.endDate, dateTimes.endTime)}`; | ||
|
||
const params = { | ||
text: encodeURIComponent(event.title), | ||
startdt: startDateTime, | ||
enddt: endDateTime, | ||
body: encodeURIComponent(event.details), | ||
location: encodeURIComponent(event.location), | ||
}; | ||
return `?subject=${params.text}&startdt=${params.startdt}&enddt=${params.enddt}&body=${params.body}&location=${params.location}`; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { CalendarEvent } from "../ai-text-to-calendar"; | ||
|
||
export function parseDateTimes(event: CalendarEvent) { | ||
const startDate = event.start_date.replace(/\D/g, ""); | ||
const startTime = event.start_time.replace(/\D/g, ""); | ||
const endDate = event.end_date.replace(/\D/g, ""); | ||
const endTime = event.end_time.replace(/\D/g, ""); | ||
|
||
return { startDate, startTime, endDate, endTime }; | ||
} |
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.
syntax: 'Customable' is not a word - should be 'Customizable'