Skip to content

Commit

Permalink
✨ add origin information to the AI time tracking feature
Browse files Browse the repository at this point in the history
🐛 try to fix favicon 404 not found errors randomly happening
➖ remove pickr dependency and use native html color picker everywhere
⬆️ updated package dependencies
  • Loading branch information
faburem committed Oct 25, 2023
1 parent 767dcfc commit 1a94358
Show file tree
Hide file tree
Showing 20 changed files with 1,307 additions and 671 deletions.
9 changes: 5 additions & 4 deletions imports/api/timecards/server/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -729,8 +729,8 @@ const getGoogleWorkspaceData = new ValidatedMethod({
}))
eventResponse = fetchedEvents
if (await getGlobalSettingAsync('openai_apikey') && fetchedEvents.length > 0) {
eventResponse = await Promise.all(fetchedEvents.map(async (eventData) => getOpenAIResponse(`Based on the following JSON representation of a calendar event, respond with a JSON object summarizing the event with as few words as possible. Add the date of the event and the duration in hours and try to identify the customer based on the majority of attendee e-mail addresses. Use the following schema for the return JSON:
\`Interface event {summary: string, duration:number, customer: string, date: date}\`
eventResponse = await Promise.all(fetchedEvents.map(async (eventData) => getOpenAIResponse(`Based on the following JSON representation of a calendar event, respond with a JSON object summarizing the event with as few words as possible. Add the date of the event and the duration in hours and try to identify the customer based on the majority of attendee e-mail addresses. Include the original un-altered summary in the origin field. Use the following schema for the return JSON:
\`Interface event {summary: string, duration:number, customer:string, date:date, origin:string}\`
${JSON.stringify(eventData)}`)))
}
const emails = await fetch(`https://www.googleapis.com/gmail/v1/users/me/messages?q=in:sent%20after:${Date.parse(startDate) / 1000}%20before:${Date.parse(endDate) / 1000}`, {
Expand Down Expand Up @@ -770,12 +770,13 @@ const getGoogleWorkspaceData = new ValidatedMethod({
}
}
if (await getGlobalSettingAsync('openai_apikey')) {
return getOpenAIResponse(`Based on the email representation in JSON format, respond with a JSON object with the following schema where you estimate the time it took to write the email in hours based on the snippet and sizeEstimate in bytes where 0.25 hours is the minimum and add it to the duration field, use date format "YYYY-MM-DD" for dates, summarize the content with as few words as possible, do not include the snippet in the summary, guess the customer company name based on the majority of recipient's mail address domain:
\`Interface message {summary:string,customer:string,date:date,duration:number}\`
return getOpenAIResponse(`Based on the email representation in JSON format, respond with a JSON object with the following schema where you estimate the time it took to write the email in hours based on the snippet and sizeEstimate in bytes where 0.25 hours is the minimum and add it to the duration field, use date format "YYYY-MM-DD" for dates, summarize the content with as few words as possible, do not include the snippet in the summary, guess the customer company name based on the majority of recipient's mail address domain. Include the original un-altered summary in the origin field. Use the following schema for the return JSON:
\`Interface message {summary:string,customer:string,date:date,duration:number,origin:string}\`
${JSON.stringify(jsonMessage)}`)
}
returnMessage.summary = jsonMessage.snippet.substring(0, 50)
returnMessage.duration = 0.25
returnMessage.origin = returnMessage.summary
return returnMessage
}
return returnMessage
Expand Down
9 changes: 9 additions & 0 deletions imports/ui/pages/details/components/detailtimetable.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,22 @@ Template.detailtimetable.onRendered(() => {
layout: 'ratio',
showTotalRow: true,
noDataMessage: t('tabular.sZeroRecords'),
inlineFilters: true,
events: {
onSortColumn(column) {
if (column) {
templateInstance.sort.set({ column: column.colIndex, order: column.sortOrder })
}
},
},
headerDropdown: [
{
label: 'Filter',
action(column) {
console.log(column)
},
},
],
}
if (getGlobalSetting('useState')) {
datatableConfig
Expand Down
25 changes: 13 additions & 12 deletions imports/ui/pages/overview/editproject/editproject.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,19 @@
<div class="mb-3">
<h5>{{#if newProject}}{{t "navigation.new_project"}}{{/if}}</h5>
</div>
<div class="mb-3 form-floating">
<input name="name" id="name" type="text" value="{{name}}" class="form-control" placeholder="{{t 'project.my_project_placeholder'}}" required/>
<label class="form-label" for="name">{{t "globals.prj_name"}}</label>
<div class="row">
<div class="col-8">
<div class="mb-3 form-floating">
<input name="name" id="name" type="text" value="{{name}}" class="form-control" placeholder="{{t 'project.my_project_placeholder'}}" required/>
<label class="form-label" for="name">{{t "globals.prj_name"}}</label>
</div>
</div>
<div class="col">
<div class="mb-3 form-floating">
<input name="color" id="color" type="color" value="{{color}}" class="form-control" />
<label class="form-label" for="color">{{t "globals.color"}}</label>
</div>
</div>
</div>
<div class="mb-3">
<div class="card">
Expand Down Expand Up @@ -111,15 +121,6 @@ <h5>{{#if newProject}}{{t "navigation.new_project"}}{{/if}}</h5>
</div>
</div>
{{/if}}
<div class="mb-3">
<div class="input-group" id="colpick">
<div class="form-floating">
<input name="color" id="color" type="text" value="{{color}}" data-color="{{color}}" class="form-control" />
<label class="form-label" for="color">{{t "globals.color"}}</label>
</div>
<button type="button" class="btn rounded-end" id="pickr"></button>
</div>
</div>
{{#if getGlobalSetting "enableWekan"}}
<div class="mb-3">
<div class="card">
Expand Down
43 changes: 2 additions & 41 deletions imports/ui/pages/overview/editproject/editproject.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Meteor } from 'meteor/meteor'
import { FlowRouter } from 'meteor/ostrio:flow-router-extra'
import 'jquery-serializejson'
import '@simonwep/pickr/dist/themes/monolith.min.css'
import Pickr from '@simonwep/pickr/dist/pickr.min'
import { t } from '../../../../utils/i18n.js'
import './editproject.html'
import Projects from '../../../../api/projects/projects.js'
Expand Down Expand Up @@ -44,38 +42,11 @@ Template.editproject.onCreated(function editprojectSetup() {
})
Template.editproject.onRendered(() => {
const templateInstance = Template.instance()
const pickrOptions = {
el: '#pickr',
theme: 'monolith',
lockOpacity: true,
comparison: false,
position: 'left-start',
components: {
preview: true,
opacity: false,
hue: true,
interaction: {
hex: false,
input: false,
clear: false,
save: false,
},
},
}
templateInstance.autorun(() => {
if (templateInstance.subscriptionsReady()) {
if (!FlowRouter.getParam('id')) {
templateInstance.color = `#${(`000000${Math.floor(0x1000000 * Math.random()).toString(16)}`).slice(-6)}`
$('#color').val(templateInstance.color)
pickrOptions.default = templateInstance.color
}
if (!templateInstance.pickr) {
window.setTimeout(() => {
templateInstance.pickr = Pickr.create(pickrOptions)
templateInstance.pickr.on('change', (color) => {
$('#color').val(color.toHEXA().toString())
}, 0)
})
}
if (!templateInstance.quill) {
import('cl-editor').then((Editor) => {
Expand Down Expand Up @@ -121,10 +92,10 @@ Template.editproject.onRendered(() => {
templateInstance.quill.setHtml(project.desc)
}
if (project?.color || templateInstance.color) {
templateInstance.pickr?.setColor(project?.color
templateInstance.$('#color').val(project?.color
? project.color : templateInstance.color)
} else {
templateInstance.pickr?.setColor('#009688')
templateInstance.$('#color').val('#009688')
}
if (project.desc instanceof Object && templateInstance.quill) {
import('quill-delta-to-html').then((deltaToHtml) => {
Expand Down Expand Up @@ -258,11 +229,6 @@ Template.editproject.events({
if (!templateInstance.$(event.currentTarget).val()) {
templateInstance.$(event.currentTarget).val('#009688')
}
if (!Template.instance().pickr?.setColor(templateInstance.$(event.currentTarget).val())) {
templateInstance.$('#color').addClass('is-invalid')
} else {
templateInstance.$('#color').removeClass('is-invalid')
}
},
'change #notbillable': (event, templateInstance) => {
event.preventDefault()
Expand Down Expand Up @@ -362,8 +328,3 @@ Template.editproject.helpers({
replaceSpecialChars: (string) => string.replace(/[^A-Z0-9]/ig, '_'),
gitlabquery: () => Template.instance()?.project?.get()?.gitlabquery,
})

Template.editproject.onDestroyed(function editprojectDestroyed() {
this.pickr?.destroy()
delete this.pickr
})
9 changes: 3 additions & 6 deletions imports/ui/pages/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@
<div class="col-lg-3">
{{#unless currentUser.profile.avatar}}
<div class="mb-3">
<div class="input-group" id="colpick">
<div class="form-floating">
<input aria-label='{{t "globals.color"}}' name="avatarColor" id="avatarColor" type="text" value="{{avatarColor}}" data-color="{{avatarColor}}" class="form-control" />
<label class="form-label" for="color">{{t "globals.color"}}</label>
</div>
<button type="button" class="btn" id="avatarColorPickr"></button>
<div class="form-floating">
<input aria-label='{{t "globals.color"}}' name="avatarColor" id="avatarColor" type="color" value="{{avatarColor}}" class="form-control" />
<label class="form-label" for="color">{{t "globals.color"}}</label>
</div>
</div>
<div class="mb-3">
Expand Down
40 changes: 0 additions & 40 deletions imports/ui/pages/profile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import namedavatar from 'namedavatar'
import '@simonwep/pickr/dist/themes/monolith.min.css'
import Pickr from '@simonwep/pickr/dist/pickr.min'
import { t } from '../../utils/i18n.js'
import '../shared components/backbutton.js'
import './profile.html'
Expand Down Expand Up @@ -54,7 +52,6 @@ Template.profile.events({
event.preventDefault()
templateInstance.$('#avatarImage').click()
},

'change #avatarImage': (event, templateInstance) => {
if (event.currentTarget.files && event.currentTarget.files[0]) {
const reader = new FileReader()
Expand Down Expand Up @@ -104,43 +101,6 @@ Template.profile.onRendered(function settingsRendered() {
if (!Meteor.loggingIn() && Meteor.user()
&& Meteor.user().profile && this.subscriptionsReady()) {
templateInstance.$('#avatarData').val(getUserSetting('avatar'))
if (templateInstance.pickr) {
templateInstance.pickr.destroy()
delete templateInstance.pickr
}
if (!getUserSetting('avatar') && templateInstance.$('#avatarColorPickr').length) {
const pickrOptions = {
el: '#avatarColorPickr',
theme: 'monolith',
lockOpacity: true,
comparison: false,
position: 'left-start',
default: (Meteor.user() && getUserSetting('avatarColor')
? getUserSetting('avatarColor') : Template.instance().selectedAvatarColor.get()),
components: {
preview: true,
opacity: false,
hue: true,
interaction: {
hex: false,
input: false,
clear: false,
save: false,
},
},
}
templateInstance.pickr = Pickr.create(pickrOptions)
templateInstance.pickr.on('change', (color) => {
templateInstance.selectedAvatarColor.set(color.toHEXA().toString())
templateInstance.$('#avatarColor').val(color.toHEXA().toString())
})
}
}
})
})
Template.profile.onDestroyed(function settingsDestroyed() {
if (this.pickr) {
this.pickr.destroy()
delete this.pickr
}
})
4 changes: 3 additions & 1 deletion imports/ui/pages/track/components/magicPopup.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template name="magicPopup">
<div class="modal fade" id="magicModal" tabindex="-1" aria-labelledby="magicModalLabel" aria-hidden="true">
<div class="modal fade" id="magicModal" tabindex="-1" aria-labelledby="magicModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
Expand All @@ -24,6 +24,7 @@ <h1 class="mx-auto text-center"><i class="fa-solid fa-wand-magic-sparkles fa-2xl
<thead>
<tr>
<th scope="col"><input type="checkbox" class="js-select-all form-check-input"/></th>
<th scope="col">{{t 'autocomplete.origin'}}</th>
<th scope="col">{{t 'globals.date'}}</th>
<th scope="col">{{t 'globals.project'}}</th>
<th scope="col">{{t 'globals.task'}}</th>
Expand All @@ -34,6 +35,7 @@ <h1 class="mx-auto text-center"><i class="fa-solid fa-wand-magic-sparkles fa-2xl
{{#each entry in magicData}}
<tr>
<td><input type="checkbox" class="js-magic-select form-check-input"/></td>
<td class="text-center"><i class="js-origin-icon text-primary fa-solid {{entry.icon}}" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-content="{{entry.origin}}" style="cursor:pointer"></i></td>
<td><input type="date" class="form-control js-magic-date" value="{{entry.date}}" required/></td>
<td>{{{ renderProjectSelect (entry.projectID) }}}</td>
<td><input type="text" class="form-control js-magic-task" value="{{entry.summary}}" required/></td>
Expand Down
7 changes: 7 additions & 0 deletions imports/ui/pages/track/components/magicPopup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra'
import dayjs from 'dayjs'
import bootstrap from 'bootstrap'
import './magicPopup.html'
import './projectsearch.js'
import { t } from '../../../../utils/i18n.js'
Expand Down Expand Up @@ -31,6 +32,8 @@ const getMagicData = (templateInstance) => {
googleAPI().then(() => {
Meteor.call('getGoogleWorkspaceData', { startDate, endDate }, (error, result) => {
if (!error) {
result.returnEvents = result.returnEvents.map((returnEvent) => ({ ...returnEvent, icon: 'fa-calendar' }))
result.returnMessages = result.returnMessages.map((returnMessage) => ({ ...returnMessage, icon: 'fa-envelope' }))
templateInstance.magicData.set(result.returnEvents.concat(result.returnMessages)
.sort((a, b) => (a.date > b.date ? 1 : -1)))
} else {
Expand Down Expand Up @@ -84,6 +87,10 @@ Template.magicPopup.events({
'click .js-select-all': (event, templateInstance) => {
templateInstance.$('.js-magic-select').prop('checked', event.currentTarget.checked)
},
'mouseover .js-origin-icon': (event) => {
const element = event.currentTarget
bootstrap.Popover.getOrCreateInstance(element)
},
'click .js-save': (event, templateInstance) => {
event.preventDefault()
let selectedEntries = []
Expand Down
4 changes: 0 additions & 4 deletions imports/ui/styles/general.scss
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,6 @@ $btn-link-color: $body-color;
.handle {
cursor: pointer;
}

.pickr .pcr-button {
height: 100% !important;
}
.dt-buttons {
padding-left: 15px;
}
Expand Down
5 changes: 3 additions & 2 deletions imports/ui/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,10 @@
},
"autocomplete": {
"modal_title": "KI unterstützte Zeiterfassung",
"processing": "Import und Verarbeitung der Daten ...",
"processing": "Import und Verarbeitung der Daten aus Google Workspace ...",
"authentication_missing": "Sie müssen den Zugriff auf Ihr Google Workspace-Konto autorisieren, um Daten zu verarbeiten.",
"authorize_now": "Jetzt autorisieren",
"save_selected": "Ausgewählte Zeiteinträge speichern"
"save_selected": "Ausgewählte Zeiteinträge speichern",
"origin": "Datenursprung"
}
}
5 changes: 3 additions & 2 deletions imports/ui/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,10 @@
},
"autocomplete": {
"modal_title": "AI powered autocomplete",
"processing": "Fetching data and processing using OpenAI ...",
"processing": "Fetching and processing Google Workspace data ...",
"authentication_missing": "You need to authorize access to your Google Workspace account to process data.",
"authorize_now": "Authorize now",
"save_selected": "Save selected time entries"
"save_selected": "Save selected time entries",
"origin": "Origin"
}
}
3 changes: 2 additions & 1 deletion imports/ui/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@
"processing": "Récupération des données et traitement à l'aide d'OpenAI...",
"authentication_missing": "Vous devez autoriser l'accès à votre compte Google Workspace pour traiter les données.",
"authorize_now": "Autoriser maintenant",
"save_selected": "Enregistrer les entrées de temps sélectionnées"
"save_selected": "Enregistrer les entrées de temps sélectionnées",
"origin": "Origine"
}
}
3 changes: 2 additions & 1 deletion imports/ui/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@
"processing": "Получение данных и их обработка с помощью OpenAI...",
"authentication_missing": "Вам необходимо авторизовать доступ к вашей учетной записи Google Workspace для обработки данных.",
"authorize_now": "Авторизоваться сейчас",
"save_selected": "Сохранить выбранные записи времени"
"save_selected": "Сохранить выбранные записи времени",
"origin": "Происхождение"
}
}
3 changes: 2 additions & 1 deletion imports/ui/translations/ukr.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@
"processing": "Отримання даних та обробка за допомогою OpenAI ...",
"authentication_missing": "Вам потрібно авторизувати доступ до свого облікового запису Google Workspace для обробки даних.",
"authorize_now": "Авторизувати зараз",
"save_selected": "Зберегти вибрані записи часу"
"save_selected": "Зберегти вибрані записи часу",
"origin": "Походження"
}
}
3 changes: 2 additions & 1 deletion imports/ui/translations/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@
"processing": "正在获取数据并使用 OpenAI 进行处理...",
"authentication_missing": "你需要授权访问你的 Google Workspace 帐户才能处理数据。",
"authorize_now": "现在授权",
"save_selected": "保存所选时间记录"
"save_selected": "保存所选时间记录",
"origin": "起源"
}
}

0 comments on commit 1a94358

Please sign in to comment.