Skip to content

Commit 1251632

Browse files
Allowing multiple events to be added and states for ChatWindow
1 parent 663cf8f commit 1251632

File tree

4 files changed

+172
-84
lines changed

4 files changed

+172
-84
lines changed

calendar.js

Lines changed: 75 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ const process = require('process');
44
const { authenticate } = require('@google-cloud/local-auth');
55
const { google } = require('googleapis');
66

7-
console.log("I am working.");
8-
97
// If modifying these scopes, delete token.json.
108
const SCOPES = ['https://www.googleapis.com/auth/calendar'];
119
// The file token.json stores the user's access and refresh tokens, and is
@@ -14,56 +12,76 @@ const SCOPES = ['https://www.googleapis.com/auth/calendar'];
1412
const TOKEN_PATH = path.join(process.cwd(), 'token.json');
1513
const CREDENTIALS_PATH = path.join(process.cwd(), 'credentials.json');
1614

17-
/**
18-
* Creates a new event on the user's primary calendar.
19-
* @param {google.auth.OAuth2} auth An authorized OAuth2 client.
20-
* @param {Object} eventBody The event details.
21-
*/
22-
async function createEvent(auth, eventBody) {
15+
async function createEvents(auth, eventBodiesArray) {
2316
const calendar = google.calendar({ version: 'v3', auth });
2417

25-
try {
26-
const event = await calendar.events.insert({
18+
const promises = eventBodiesArray.map(eventBody => {
19+
return calendar.events.insert({
2720
calendarId: 'primary',
2821
resource: eventBody,
22+
}).then(event => {
23+
console.log(`Event created: ${event.data.htmlLink}`);
24+
return event; // Return event for further processing if needed.
25+
}).catch(error => {
26+
console.error('Error creating event', error);
27+
return null; // Return null or appropriate error handling.
2928
});
30-
console.log(`Event created: ${event.data.htmlLink}`);
29+
});
30+
31+
try {
32+
const results = await Promise.all(promises);
33+
// Optional: Process results or perform further actions here.
3134
} catch (error) {
32-
console.error('Error creating event', error);
35+
console.error('An error occurred with Promise.all', error);
3336
}
3437
}
3538

36-
/**
37-
* Creates an object literal representing the event details.
38-
* @param {string} summary The summary of the event.
39-
* @param {string} location The location of the event.
40-
* @param {string} description The description of the event.
41-
* @param {Date} startDateTime The start date and time of the event.
42-
* @param {Date} endDateTime The end date and time of the event.
43-
* @returns {Object} The event object literal.
44-
*/
39+
function createEventObjects(optionsArray) {
40+
// Initialize an array to hold all event objects
41+
let events = [];
42+
43+
// Iterate over each set of options
44+
optionsArray.forEach(options => {
45+
let event = {
46+
'summary': options.summary,
47+
'location': options.location,
48+
'description': options.description,
49+
// Initialize start and end events without dateTime or date
50+
'start': {},
51+
'end': {}
52+
};
53+
54+
// Check if it's an all day event
55+
if (options.allDay) {
56+
event.start.date = options.start;
57+
event.end.date = options.end;
58+
event.start.timeZone = 'GMT+2';
59+
event.end.timeZone = 'GMT+2';
60+
} else {
61+
event.start.dateTime = options.start;
62+
event.end.dateTime = options.end;
63+
event.start.timeZone = options.startTimeZone || 'Etc/GMT+2';
64+
event.end.timeZone = options.endTimeZone || 'Etc/GMT+2';
65+
}
66+
67+
// Add recurrence if provided
68+
if (options.recurrence) {
69+
event.recurrence = options.recurrence;
70+
}
71+
72+
// Add reminders if provided
73+
if (options.reminders) {
74+
event.reminders = {
75+
'useDefault': false,
76+
'overrides': options.reminders
77+
};
78+
}
79+
80+
// Add the constructed event to the events array
81+
events.push(event);
82+
});
4583

46-
function createEventObject(summary, location, description, startDateTime, endDateTime) {
47-
return {
48-
'summary': summary,
49-
'location': location,
50-
'description': description,
51-
'start': {
52-
'dateTime': startDateTime.toISOString(),
53-
'timeZone': 'America/Los_Angeles', // Adjust to the desired timezone
54-
},
55-
'end': {
56-
'dateTime': endDateTime.toISOString(),
57-
'timeZone': 'America/Los_Angeles', // Adjust to the desired timezone
58-
},
59-
'reminders': {
60-
'useDefault': false,
61-
'overrides': [
62-
{ 'method': 'email', 'minutes': 24 * 60 },
63-
{ 'method': 'popup', 'minutes': 10 },
64-
],
65-
},
66-
};
84+
return events; // Returns an array of event objects
6785
}
6886

6987
/**
@@ -128,46 +146,23 @@ async function authorize() {
128146
return client;
129147
}
130148

131-
/**
132-
* Lists the next 10 events on the user's primary calendar.
133-
* @param {google.auth.OAuth2} auth An authorized OAuth2 client.
134-
*/
135-
async function listEvents(auth) {
136-
const calendar = google.calendar({ version: 'v3', auth });
137-
const res = await calendar.events.list({
138-
calendarId: 'primary',
139-
timeMin: new Date().toISOString(),
140-
maxResults: 5,
141-
singleEvents: true,
142-
orderBy: 'startTime',
143-
});
144-
const events = res.data.items;
145-
if (!events || events.length === 0) {
146-
console.log('No upcoming events found.');
147-
return;
148-
}
149-
console.log('Upcoming 10 events:');
150-
events.map((event, i) => {
151-
const start = event.start.dateTime || event.start.date;
152-
console.log(`${start} - ${event.summary}`);
153-
});
154-
}
155-
156-
authorize().then(listEvents).catch(console.error);
157-
158-
// Example usage
159149
authorize().then(auth => {
150+
// Preparing the date format for an all-day event
151+
const startDate = new Date('2024-05-15').toISOString().substring(0, 10); // Converting to 'YYYY-MM-DD' format
152+
const endDate = new Date('2024-05-16').toISOString().substring(0, 10); // All-day event end date should be the next day in 'YYYY-MM-DD' format
160153

161154
// Creating an event object
162-
const newEvent = createEventObject(
163-
'Team Meeting', // Summary
164-
'Online', // Location
165-
'Discuss project progress', // Description
166-
new Date('2024-05-15T09:00:00'), // Start date and time
167-
new Date('2024-05-15T10:00:00') // End date and time
168-
);
169-
170-
// Creating the event on Google Calendar
171-
createEvent(auth, newEvent).catch(console.error);
155+
const newEvent = createEventObjects([{
156+
summary: 'Team Meeting - All Day', // Summary
157+
location: 'Online', // Location
158+
description: 'Discuss project progress', // Description
159+
start: startDate, // Start date for all-day event
160+
end: endDate, // End date for all-day event
161+
allDay: true, // Marking this event as an all-day event
162+
// Time zone is irrelevant for all-day events but set for consistency if needed
163+
}]);
164+
165+
// Assuming createEvent is a function that creates an event on Google Calendar using the provided authentication and event object
166+
createEvents(auth, newEvent).catch(console.error);
172167

173168
}).catch(console.error);

interaction.js

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ const defaultSystemPrompt = "You are a helpful chatbot.";
1919
const GPT4 = "gpt-4-turbo-preview";
2020
const GPT3_5 = 'gpt-3.5-turbo-0125';
2121

22+
const CHAT_WINDOW_STATES = {
23+
CREATE: 'Create',
24+
SPECIFY: 'Specify',
25+
};
26+
2227
class ChatWindow {
2328
#messages = [];
2429
#USER = "user";
2530
#ASSISTANT = "assistant";
2631
#SYSTEM = "system";
32+
#STATE = CHAT_WINDOW_STATES.CREATE;
2733

2834
constructor(model = GPT3_5, systemPrompt = defaultSystemPrompt) {
2935
if (model != GPT4 && model != GPT3_5) {
@@ -34,6 +40,17 @@ class ChatWindow {
3440
this.#messagesAppend(systemMessage);
3541
}
3642

43+
getState() {
44+
return this.#STATE;
45+
}
46+
47+
setState(state) {
48+
if (state != CHAT_WINDOW_STATES.CREATE && state != CHAT_WINDOW_STATES.SPECIFY) {
49+
throw new Error(`State '${model}' does not exist.`)
50+
}
51+
this.#STATE = state;
52+
}
53+
3754
#makeMessage(role, message) {
3855
if (role != this.#USER && role != this.#ASSISTANT && role != this.#SYSTEM) {
3956
throw new Error(`Role '${role}' is not a valid role.`)
@@ -48,6 +65,7 @@ class ChatWindow {
4865
async #streamResponse(messages) {
4966
let stream = await openai.chat.completions.create({
5067
'model': this.model,
68+
//'response_format': { 'type': 'json_object' },
5169
'messages': messages,
5270
'stream': true
5371
});
@@ -70,16 +88,52 @@ class ChatWindow {
7088

7189
#generateMessageHTML(message) {
7290
let newParagraph = document.createElement('p');
73-
newParagraph.innerHTML = message.role + ":\n" + message.content;
91+
newParagraph.innerHTML = message.role + ":\n\n"
92+
newParagraph.innerHTML += message.content;
7493
chatHistory.appendChild(newParagraph);
7594
chatHistory.scrollTo({ top: chatHistory.scrollHeight });
7695
}
7796

97+
#parseJSONToObject(jsonString) {
98+
try {
99+
let obj = JSON.parse(jsonString);
100+
return obj;
101+
} catch (e) {
102+
// Log error or handle it as needed
103+
console.error('Error parsing JSON', e);
104+
return null; // or return an empty object {}, based on your needs
105+
}
106+
}
107+
108+
#generateMessageHTMLStreamed(message) {
109+
return new Promise((resolve) => {
110+
let newParagraph = document.createElement("p");
111+
newParagraph.innerHTML = `<strong>${message.role}</strong>:<br>`;
112+
chatHistory.appendChild(newParagraph);
113+
114+
let index = 0;
115+
function typeMessage() {
116+
if (index < message.content.length) {
117+
let chunkSize = Math.floor(Math.random() * 4) + 2; // 2-3 characters
118+
let part = message.content.slice(index, index + chunkSize);
119+
newParagraph.innerHTML += part;
120+
index += chunkSize;
121+
122+
chatHistory.scrollTo({ top: chatHistory.scrollHeight });
123+
setTimeout(typeMessage, Math.random() * 100 + 100);
124+
} else {
125+
resolve();
126+
}
127+
}
128+
typeMessage();
129+
});
130+
}
131+
78132
getMessages() {
79133
return this.#messages;
80134
}
81135

82-
sendMessage(prompt) {
136+
sendUserMessage(prompt) {
83137
let userMessage = this.#makeMessage(this.#USER, prompt);
84138
this.#messagesAppend(userMessage);
85139
this.#generateMessageHTML(userMessage);
@@ -88,14 +142,40 @@ class ChatWindow {
88142
this.#generateMessageHTML(emptyResponseMessage);
89143
this.#streamResponse(messages);
90144
}
145+
146+
async sendSystemMessage(prompt) {
147+
let systemMessage = this.#makeMessage(this.#SYSTEM, prompt);
148+
await this.#generateMessageHTMLStreamed(systemMessage);
149+
}
150+
}
151+
152+
function sleep(ms) {
153+
return new Promise(resolve => setTimeout(resolve, ms))
91154
}
92155

93-
const chatWindow = new ChatWindow(GPT3_5);
156+
const chatWindow = new ChatWindow(GPT4);
157+
(async () => {
158+
await chatWindow.sendSystemMessage("Welcome to CalendarMe!");
159+
await sleep(1000);
160+
await chatWindow.sendSystemMessage("Please write your plans here in as much detail as you like.")
161+
})();
94162

95163
textInputBox.addEventListener('keydown', function (e) {
96164
if (e.key === 'Enter') {
165+
let today = new Date().toISOString().split('T')[0];
166+
let time = new Date().toTimeString().split(' ')[0];
167+
let day = new Date().toLocaleString('en-us', { weekday: 'long' });
97168
let textFromInputBox = textInputBox.value;
169+
let planning_prompt = textFromInputBox;
170+
98171
textInputBox.value = "";
99-
chatWindow.sendMessage(textFromInputBox);
172+
173+
let constructedPrompt = planning_prompt//`Given the following current date and time: ${day}, ${today}T${time} and planning prompt: '${planning_prompt}', format the prompt's contents as JSON objects with the following keys: summary, location (Optional.), description, start, end, recurrence (Optional. array of RRULE strings), reminders (Optional. useDefault, overrides), timeZone (Etc/GMT+2), allDay (boolean), in an array that can be parsed to create calendar events. Please use 1-2 emojis per complex sentence in the title's lhs and description to make them more personal.`;
174+
175+
if (chatWindow.getState() == CHAT_WINDOW_STATES.CREATE) {
176+
constructedPrompt = `Given the following current date and time: ${day}, ${today}T${time} and planning prompt: '${planning_prompt}', format the prompt's contents as JSON objects with the following keys: summary, location (Optional.), description, start, end, recurrence (Optional. array of RRULE strings), reminders (Optional. useDefault, overrides), timeZone (Etc/GMT+2), allDay (Optional. boolean), in an array that can be parsed to create calendar events. Please use 1-2 emojis per complex sentence in the title's lhs and description to make them more personal.`;
177+
chatWindow.setState(CHAT_WINDOW_STATES.SPECIFY);
178+
}
179+
chatWindow.sendUserMessage(constructedPrompt);
100180
}
101181
});

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"dependencies": {
3030
"@google-cloud/local-auth": "^2.1.0",
3131
"googleapis": "^105.0.0",
32+
"json": "^11.0.0",
3233
"openai": "^4.40.2"
3334
}
3435
}

0 commit comments

Comments
 (0)