הבהרה: המלצות כלליות בלבד, לא ייעוץ מקצועי. האחריות לבדוק לפני שימוש היא שלכם. לפני שמקליטים פגישה, אמרו למשתתפים שאתם מקליטים, והשתמשו רק בכלי AI שהארגון שלכם מאשר למידע הזה. הנוסח המלא בסוף.
למי זה מתאים
לכל מי שיושב בהרבה פגישות ויוצא מהן עם "אז מי עושה מה?": מנכ"לים וסמנכ"לי כספים, ועדות השקעה, הנהלות של עמותות, צוותי ליווי בקרנות, ישיבות צוות שבועיות. אם משימות הולכות לאיבוד בין הפגישה למייל הסיכום, או שהמייל הזה פשוט לא נכתב, הערכה הזאת בשבילכם.
לא צריך לדעת לתכנת. מעתיקים קוד אחד לגיליון, פעם אחת, ומשם הכל עובד מתפריט.
מה יהיה לכם בסוף השעה
- גיליון Google Sheets עם לשונית "אנשים" (שם, תפקיד, אימייל, כינויים) ולשונית "משימות".
- פרומפט ראשי שהופך תמליל לסיכום, החלטות, משימות ושאלות פתוחות, בפורמט שהגיליון יודע לקרוא.
- תפריט "פגישות" בגיליון: מדביקים את התשובה של ה-AI, והמשימות נרשמות לבד, כל אחת עם אחראי.
- טיוטות ב-Gmail: מייל אישי לכל אחראי עם המשימות שלו, ומייל סיכום לכל המשתתפים. שום דבר לא נשלח לפני שאתם קוראים ולוחצים "שליחה".
- תזכורת יומית בשמונה בבוקר על משימות פתוחות שהיעד שלהן הגיע.
מה צריך
- חשבון Google (Sheets ו-Gmail). חשבון של הארגון עדיף על חשבון פרטי.
- תמליל של פגישה. אם אין לכם, יש דרכים להשיג אחד בצעד 1.
- כלי AI שהארגון מאשר: ChatGPT, Claude, Gemini או הכלי הפנימי שלכם. הקוד לא שולח שום דבר לשום AI. אתם בוחרים איפה התמליל עובר.
כלל אחד שלא מוותרים עליו: ה-AI לא ממציא אחראים ולא ממציא תאריכים. משימה בלי אחראי ברור נרשמת "לא שויך", ויעד שלא נאמר בפגישה נשאר ריק. עדיף חור גלוי מאשר מייל לאדם הלא נכון.
הערכה, צעד אחרי צעד (60 דקות)
צעד 1 (10 דקות): מקליטים ומתמללים, ברשות
מה עושים: משיגים תמליל של פגישה אחת אמיתית, כדי לבנות הכל על משהו אמיתי.
איך: שלוש דרכים, מהקלה לפרטית:
- התמלול של כלי הפגישות. ב-Zoom, ב-Google Meet וב-Teams יש תמלול מובנה בחלק מהחבילות. מפעילים אותו בתחילת הפגישה ומורידים את הקובץ בסוף.
- מקליטים בטלפון ומעלים לכלי AI. מהיר, אבל ההקלטה יוצאת לשרתים של החברה. לא מתאים לפגישות רגישות.
- מתמללים על המחשב שלכם. ההקלטה לא יוצאת מהמחשב. ההוראות המלאות בערכה תמלול עברית בחינם, על המחשב שלכם.
בתחילת הפגישה אומרים משפט אחד: "אני מקליט כדי שהסיכום והמשימות ייצאו מדויקים. למישהו מפריע?". אם למישהו מפריע, לא מקליטים, ורושמים ידנית.
טעות נפוצה: להתחיל להקליט באמצע. הדקות הראשונות של פגישה הן לרוב איפה שנאמר "המטרה של היום היא...", וזה בדיוק מה שה-AI צריך כדי לכתוב סיכום טוב.
צעד 2 (10 דקות): גיליון ולשונית אנשים
מה עושים: פותחים גיליון חדש בשם "פגישות ומשימות". את הלשוניות עצמן הקוד ייצור בצעד 3, ועכשיו מכינים את רשימת האנשים.
איך: כותבים רשימה של כל מי שעשוי לקבל משימה: שם מלא, תפקיד, אימייל, וכינויים. כינוי הוא כל דרך שבה קוראים לאדם בפגישה: שם פרטי, שם משפחה, "המנכ"ל", "רו"ח". מפרידים כינויים בפסיק.
שם תפקיד אימייל כינויים
[שם מלא] [תפקיד] [אימייל] [שם פרטי, כינוי]
[שם מלא] [תפקיד] [אימייל] [שם משפחה]
למה ככה: הקוד מחפש אחראי לפי הסדר הזה: שם מלא, כינוי, תפקיד, ואז שם פרטי. אם שני אנשים באותו שם פרטי, הוא לא מנחש, והמשימה נשארת "לא שויך". לכן כדאי לתת לכל אחד כינוי ייחודי, כמו שם משפחה.
צעד 3 (10 דקות): מדביקים את הקוד ומריצים setup
מה עושים: מוסיפים לגיליון את הקוד מהנספח, ונותנים לו הרשאות פעם אחת.
איך:
- בגיליון: תוספים > Apps Script (Extensions > Apps Script). נפתח קובץ
Code.gs. - מוחקים את מה שיש בו, מדביקים את כל הקוד מהנספח שבסוף הערכה, ושומרים.
- למעלה בוחרים את הפונקציה
setupולוחצים הפעלה (Run). Google תבקש הרשאות לגיליון ול-Gmail. מאשרים. אם מופיע "Google לא אימתה את האפליקציה הזאת", לוחצים מתקדם ואז מעבר אל הפרויקט. זה הקוד שלכם, בחשבון שלכם. - חוזרים לגיליון ומרעננים. מופיעות שתי לשוניות, "אנשים" ו"משימות", ותפריט חדש בשם פגישות.
- מדביקים את רשימת האנשים מצעד 2 לתא A2 בלשונית "אנשים".
למה ככה: הקוד רק יוצר טיוטות ב-Gmail. הוא לא שולח מייל לאף אחד חוץ מהתזכורת היומית, שיוצאת אליכם (ולאחראים רק אם תבחרו בזה בצעד 6).
צעד 4 (10 דקות): מהתמליל לתשובה שהגיליון קורא
מה עושים: פותחים שיחה חדשה בכלי ה-AI, מדביקים את הפרומפט הראשי, ובמקום הסוגריים את רשימת האנשים ואת התמליל.
אתה מזכיר פגישות מדויק. קרא את התמליל ואת רשימת האנשים, והחזר JSON אחד בלבד, בשורה אחת, בלי שום טקסט לפניו או אחריו.
רשימת האנשים (שם | תפקיד | כינויים):
[הדביקו את לשונית "אנשים"]
כותרת הפגישה: [למשל: ועדת השקעות חודשית]
תאריך הפגישה: [yyyy-mm-dd]
התמליל:
[הדביקו את התמליל]
כללים מחייבים:
1. משימה היא רק דבר שמישהו התחייב או התבקש לעשות. רעיון, דעה או "כדאי ש..." בלי מבצע הם לא משימה.
2. owner: השם המלא מהרשימה, בדיוק כפי שהוא כתוב שם. אם לא ברור מי אחראי, או שהאדם לא ברשימה, כתוב את מה שנאמר בתמליל ("מישהו מהכספים") ואל תנחש.
3. due: רק תאריך שנאמר או שאפשר לחשב בוודאות מהתאריך של הפגישה ("עד יום חמישי"). בפורמט yyyy-mm-dd. אם לא נאמר מועד, השאר "".
4. quote: משפט קצר מהתמליל שבו סוכמה המשימה, מילה במילה.
5. אל תכלול סכומים, מספרי חשבון, תעודות זהות או פרטים אישיים שלא נחוצים כדי לבצע את המשימה.
6. participants: רק שמות מלאים מהרשימה של מי שדיבר או הוזכר כנוכח.
המבנה:
{"meeting":{"title":"","date":"","participants":[]},"summary":["עד 5 נקודות"],"decisions":[],"tasks":[{"task":"פועל בציווי + מה בדיוק","owner":"","due":"","quote":""}],"open_questions":[]}
טעות נפוצה: לבקש מה-AI "תסכם ותוציא משימות" בלי רשימת האנשים. אז הוא כותב "דני" כשיש שלושה דני בארגון, וממציא שמות משפחה. הרשימה היא מה שהופך את התשובה למשהו שאפשר לשלוח.
צעד 5 (10 דקות): מייבאים, בודקים ושולחים
מה עושים: מעבירים את התשובה לגיליון, עוברים על מה שנרשם, ושולחים את הטיוטות.
איך:
- מעתיקים את ה-JSON שה-AI החזיר.
- בגיליון: פגישות > ייבוא משימות מפגישה, מדביקים בחלון ולוחצים OK.
- בלשונית "משימות" נוספות שורות, אחת לכל משימה. הודעה קטנה אומרת כמה משימות נוספו, כמה טיוטות נוצרו, וכמה משימות לא שויכו.
- משימה שכתוב בה "לא שויך": משייכים ידנית (כותבים את השם, התפקיד והאימייל), ומוסיפים אותה לטיוטה של אותו אדם.
- פותחים ב-Gmail את טיוטות. עוברים על כל טיוטה, מתקנים מה שצריך, ושולחים.
למה ככה: בכל משימה יש עמודת "ציטוט מהפגישה". כשמישהו עונה "לא אמרתי שאני עושה את זה", יש תשובה עניינית, בלי ויכוח.
צעד 6 (5 דקות): תזכורת יומית
מה עושים: מפעילים תזכורת שיוצאת כל בוקר על משימות פתוחות שהיעד שלהן הגיע או עבר.
איך:
- בגיליון: פגישות > הפעלת תזכורת יומית ב-8:00. מאשרים הרשאות אם מתבקש.
- מעכשיו, כשמשימה בוצעה, כותבים בעמודת הסטטוס בוצע. כל דבר אחר נחשב פתוח.
- כברירת מחדל התזכורת מגיעה רק אליכם, עם כל הרשימה. אם רוצים שגם כל אחראי יקבל תזכורת על המשימות שלו: ב-Apps Script, הגדרות הפרויקט > מאפייני סקריפט, מוסיפים
REMIND_OWNERSעם הערךtrue.
כדאי להתחיל כמה שבועות כשהתזכורת מגיעה רק אליכם, ורק אחר כך לפתוח אותה לאחראים.
צעד 7 (5 דקות): הפגישה הבאה מתחילה מהמשימות הפתוחות
מה עושים: לפני הפגישה הבאה, מכינים מהגיליון סדר יום קצר: מה נסגר, מה תקוע, ומה צריך החלטה.
איך: מסננים בלשונית "משימות" את מה שלא סומן "בוצע", מעתיקים את השורות ומריצים את הפרומפט הזה.
הנה המשימות הפתוחות מהגיליון (משימה | אחראי | יעד | ציטוט): [הדביקו].
היום [תאריך]. כתוב סדר יום לפגישת מעקב של 15 דקות:
א. משימות שהיעד שלהן עבר, לפי אחראי. ליד כל אחת: שאלה אחת לאחראי.
ב. משימות שתלויות זו בזו, אם יש.
ג. החלטות שצריך לקבל כדי שמשימה תתקדם.
אל תמציא סטטוס. מה שלא כתוב בטבלה, כתוב "לברר".
אבטחה ופרטיות, בקצרה
- התמליל הוא המידע הרגיש, לא הגיליון. בפגישות של כספים, השקעות או מוטבים, השתמשו רק בכלי AI שהארגון אישר, או מחקו מהתמליל שמות של לקוחות, סכומים ופרטים מזהים לפני ההדבקה.
- הקוד לא שולח את התמליל לשום מקום. הוא מקבל רק את התשובה המעובדת, ושומר אותה בגיליון שלכם.
- שתפו את הגיליון רק עם מי שצריך. כל מי שיש לו גישת עריכה לגיליון יכול לראות את כל המשימות.
- מחיקה. קבעו כלל, למשל: הקלטות נמחקות אחרי שהסיכום נשלח, ותמלילים אחרי חודש.
כשמשהו לא עובד
| מה רואים | מה עושים |
|---|---|
| "ה-JSON לא תקין" | ה-AI הוסיף טקסט או ירד שורה באמצע. בקשו ממנו: "החזר רק את ה-JSON, בשורה אחת", והדביקו שוב. |
| כל המשימות "לא שויך" | השמות בתשובה לא תואמים לרשימה. בדקו שהדבקתם את רשימת האנשים בפרומפט, והוסיפו כינויים. |
| לא נוצרו טיוטות | לאנשים אין אימייל בלשונית "אנשים", או שלא אישרתם הרשאת Gmail. הריצו שוב את setup. |
| אין תפריט "פגישות" | רעננו את הגיליון. אם עדיין אין, הריצו את onOpen מתוך Apps Script. |
| התזכורת לא הגיעה | בדקו ב-Apps Script, בלשונית טריגרים (השעון), שיש טריגר אחד ל-remindOverdue. |
דוגמה מלאה: ועדת השקעות בקרן "צפון אחד" (עסק בדוי)
כל מה שבדוגמה בדוי: הקרן, האנשים, החברות והסכומים.
לשונית אנשים:
| שם | תפקיד | אימייל | כינויים |
|---|---|---|---|
| מיכל ברק | שותפה מנהלת | michal@example.com | מיכל |
| עמית סגל | סמנכ"ל כספים | amit@example.com | סגל, CFO |
| נועם דהן | אנליסט | noam@example.com | דהן |
| נועם פרץ | יועץ משפטי | noamp@example.com | פרץ, היועץ המשפטי |
מיכל: אז לגבי חברת הלוגיסטיקה, אנחנו לא מחליטים היום. דהן, תכין עד יום ראשון השוואה של המכפילים מול שלוש עסקאות דומות.
עמית: אני אשלח לשותפים את תזכיר ההון של הרבעון עד יום חמישי.
מיכל: ונועם, תבדוק את סעיף ההגנה מדילול לפני שאנחנו חוזרים אליהם.
פרץ: אני על זה.
{"meeting":{"title":"ועדת השקעות שבועית","date":"2026-10-20","participants":["מיכל ברק","עמית סגל","נועם דהן","נועם פרץ"]},"summary":["החלטה על חברת הלוגיסטיקה נדחתה לפגישה הבאה"],"decisions":["לא מחליטים היום על חברת הלוגיסטיקה"],"tasks":[{"task":"להכין השוואת מכפילים מול שלוש עסקאות דומות","owner":"נועם דהן","due":"2026-10-25","quote":"דהן, תכין עד יום ראשון השוואה של המכפילים"},{"task":"לשלוח לשותפים את תזכיר ההון של הרבעון","owner":"עמית סגל","due":"2026-10-22","quote":"אני אשלח לשותפים את תזכיר ההון של הרבעון עד יום חמישי"},{"task":"לבדוק את סעיף ההגנה מדילול","owner":"נועם","due":"","quote":"ונועם, תבדוק את סעיף ההגנה מדילול"}],"open_questions":[]}
מה קרה בגיליון: נוספו שלוש משימות. שתי הראשונות שויכו לדהן ולסגל. בשלישית ה-AI כתב רק "נועם", ויש שני נועם, אז היא נרשמה "לא שויך: נועם". מהתמליל ברור שפרץ ענה "אני על זה", אז משייכים ידנית לנועם פרץ. נוצרו שתי טיוטות אישיות וטיוטת סיכום אחת לארבעת המשתתפים.
שימו לב לשני דברים: למשימה השלישית לא הומצא יעד, כי לא נאמר אחד. והקוד לא ניחש איזה נועם, גם כשזה נראה ברור. בדיוק בגלל זה בודקים לפני ששולחים.
צ'קליסט לפני כל שליחה
- המשתתפים ידעו שהפגישה מוקלטת
- התמליל עבר רק דרך כלי AI שהארגון מאשר
- אין משימה שנשארה "לא שויך"
- כל יעד בגיליון נאמר בפגישה, או סומן כריק
- עברתם על כל טיוטה ב-Gmail לפני השליחה
- אין בטיוטות סכומים או פרטים שלא כל הנמענים אמורים לראות
- ההקלטה נמחקה, או שנקבע מתי היא תימחק
נספח: הקוד המלא (Code.gs)
מעתיקים הכל, מהשורה הראשונה עד האחרונה, לקובץ Code.gs. אין צורך לשנות שום שורה. כדי לקבל תפריט ומיילים באנגלית, מוסיפים מאפיין סקריפט LANG עם הערך en.
/**
* Meeting -> tasks -> email drafts (free kit, gindiinnovation.com)
*
* Bound to a Google Sheet with two tabs (created by setup):
* People: name | role | email | aliases (comma separated)
* Tasks: id | meeting date | meeting | task | owner | role | email | due | status | quote | created
*
* Script properties (Project Settings > Script properties), all optional:
* LANG he or en, default he (menu, tab names and email texts)
* REMIND_OWNERS true = the daily reminder emails each owner; otherwise it emails only you
*
* No AI key here: you run the prompt in the AI tool your organization allows,
* and paste its JSON. The script only checks it, files the tasks and writes drafts.
*/
var TEXT = {
he: {
people: 'אנשים',
tasks: 'משימות',
peopleHeaders: ['שם', 'תפקיד', 'אימייל', 'כינויים'],
taskHeaders: ['מזהה', 'תאריך הפגישה', 'פגישה', 'משימה', 'אחראי', 'תפקיד', 'אימייל', 'יעד', 'סטטוס', 'ציטוט מהפגישה', 'נוצר'],
menu: 'פגישות',
menuImport: 'ייבוא משימות מפגישה',
menuRemind: 'תזכורת עכשיו: משימות שהגיע זמנן',
menuTrigger: 'הפעלת תזכורת יומית ב-8:00',
paste: 'הדביקו כאן את ה-JSON שהחזיר כלי ה-AI (שורה אחת)',
badJson: 'ה-JSON לא תקין: ',
open: 'פתוח',
done: 'בוצע',
unassigned: 'לא שויך',
noDue: 'לא נקבע',
subjectOwner: 'המשימות שלך מהפגישה: ',
subjectAll: 'סיכום פגישה: ',
subjectRemind: 'תזכורת: משימות פתוחות שהגיע זמנן',
hello: 'שלום ',
yourTasks: 'סוכמו בפגישה המשימות האלה באחריותך:',
due: 'יעד: ',
summary: 'סיכום',
decisions: 'החלטות',
allTasks: 'משימות',
questions: 'שאלות פתוחות',
reply: 'אם משהו לא מדויק, ענו למייל הזה ונתקן.',
remindIntro: 'המשימות האלה פתוחות והיעד שלהן הגיע או עבר:',
result: function (t, d, u) { return 'נוספו ' + t + ' משימות ונוצרו ' + d + ' טיוטות ב-Gmail. ' + (u ? u + ' משימות לא שויכו לאף אחד: שייכו אותן בגיליון.' : 'כל המשימות שויכו.'); },
triggerOk: 'מעכשיו תצא תזכורת כל בוקר בסביבות 8:00.',
setupOk: 'הלשוניות מוכנות. מלאו את לשונית האנשים והתחילו.',
nothing: 'אין משימות פתוחות שהגיע זמנן.',
},
en: {
people: 'People',
tasks: 'Tasks',
peopleHeaders: ['Name', 'Role', 'Email', 'Aliases'],
taskHeaders: ['ID', 'Meeting date', 'Meeting', 'Task', 'Owner', 'Role', 'Email', 'Due', 'Status', 'Quote from the meeting', 'Created'],
menu: 'Meetings',
menuImport: 'Import tasks from a meeting',
menuRemind: 'Remind now: tasks that are due',
menuTrigger: 'Turn on a daily reminder at 8:00',
paste: 'Paste the JSON your AI tool returned (one line)',
badJson: 'The JSON is not valid: ',
open: 'Open',
done: 'Done',
unassigned: 'Unassigned',
noDue: 'Not set',
subjectOwner: 'Your tasks from the meeting: ',
subjectAll: 'Meeting summary: ',
subjectRemind: 'Reminder: open tasks that are due',
hello: 'Hi ',
yourTasks: 'These tasks were agreed in the meeting and are yours:',
due: 'Due: ',
summary: 'Summary',
decisions: 'Decisions',
allTasks: 'Tasks',
questions: 'Open questions',
reply: 'If anything is off, reply to this email and we will fix it.',
remindIntro: 'These tasks are open and due today or earlier:',
result: function (t, d, u) { return t + ' tasks added and ' + d + ' Gmail drafts created. ' + (u ? u + ' tasks have no owner: assign them in the sheet.' : 'Every task has an owner.'); },
triggerOk: 'A reminder will now go out every morning around 8:00.',
setupOk: 'The tabs are ready. Fill in the People tab and start.',
nothing: 'No open tasks are due.',
},
};
function lang_() {
var v = String(PropertiesService.getScriptProperties().getProperty('LANG') || 'he').toLowerCase();
return v === 'en' ? 'en' : 'he';
}
function t_() { return TEXT[lang_()]; }
function onOpen() {
var t = t_();
SpreadsheetApp.getUi().createMenu(t.menu)
.addItem(t.menuImport, 'importMeeting')
.addItem(t.menuRemind, 'remindOverdue')
.addItem(t.menuTrigger, 'setupDailyReminder')
.addToUi();
}
/** Creates the two tabs with their headers, if they are missing. Run once. */
function setup() {
var t = t_();
var ss = SpreadsheetApp.getActive();
[[t.people, t.peopleHeaders], [t.tasks, t.taskHeaders]].forEach(function (s) {
var sh = ss.getSheetByName(s[0]) || ss.insertSheet(s[0]);
if (sh.getLastRow() === 0) {
sh.getRange(1, 1, 1, s[1].length).setValues([s[1]]).setFontWeight('bold');
sh.setFrozenRows(1);
}
});
onOpen();
SpreadsheetApp.getUi().alert(t.setupOk);
}
// ---------- Pure helpers (no Google services, testable anywhere) ----------
/** Lowercase, no quotes or geresh, single spaces */
function norm(s) {
return String(s || '').toLowerCase().replace(/["'`׳״]/g, '').replace(/\s+/g, ' ').trim();
}
/** Accepts the model's answer even with a ```json fence or text around it */
function parseMeetingJson(text) {
var s = String(text || '').trim();
var start = s.indexOf('{');
var end = s.lastIndexOf('}');
if (start < 0 || end <= start) throw new Error('no { ... }');
var data = JSON.parse(s.slice(start, end + 1));
if (!data || !Array.isArray(data.tasks)) throw new Error('missing "tasks"');
data.meeting = data.meeting || {};
data.summary = Array.isArray(data.summary) ? data.summary : [];
data.decisions = Array.isArray(data.decisions) ? data.decisions : [];
data.open_questions = Array.isArray(data.open_questions) ? data.open_questions : [];
data.tasks = data.tasks
.filter(function (x) { return x && String(x.task || '').trim(); })
.map(function (x) {
return { task: String(x.task).trim(), owner: String(x.owner || '').trim(), due: normalizeDue(x.due), quote: String(x.quote || '').trim() };
});
return data;
}
/** "2026-10-03" stays; anything that is not a real yyyy-mm-dd date becomes "" (no invented dates) */
function normalizeDue(v) {
var m = String(v || '').trim().match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!m) return '';
var d = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3]));
return d.getUTCMonth() === +m[2] - 1 && d.getUTCDate() === +m[3] ? m[0] : '';
}
/**
* Finds the person a task belongs to: full name, then an alias, then the role,
* then a first name that only one person has. No match = null (the task stays unassigned).
*/
function matchPerson(owner, people) {
var o = norm(owner);
if (!o) return null;
var by = function (fn) { var hits = people.filter(fn); return hits.length === 1 ? hits[0] : null; };
return (
by(function (p) { return norm(p.name) === o; }) ||
by(function (p) { return p.aliases.some(function (a) { return norm(a) === o; }); }) ||
by(function (p) { return norm(p.role) === o; }) ||
by(function (p) { return norm(p.name).split(' ')[0] === o; })
);
}
/** Groups the matched tasks by email: { email: { person, tasks[] } } */
function groupByOwner(tasks, people) {
var out = {};
tasks.forEach(function (task) {
var p = matchPerson(task.owner, people);
if (!p || !p.email) return;
(out[p.email] = out[p.email] || { person: p, tasks: [] }).tasks.push(task);
});
return out;
}
function ownerEmailBody(t, person, tasks, meetingTitle) {
var lines = [t.hello + person.name + ',', '', t.yourTasks, ''];
tasks.forEach(function (x, i) { lines.push((i + 1) + '. ' + x.task + ' (' + t.due + (x.due || t.noDue) + ')'); });
lines.push('', meetingTitle, '', t.reply);
return lines.join('\n');
}
function summaryEmailBody(t, data, people) {
var section = function (title, items) { return items.length ? ['', title + ':'].concat(items.map(function (s) { return '- ' + s; })) : []; };
var taskLines = data.tasks.map(function (x) {
var p = matchPerson(x.owner, people);
return x.task + ' | ' + (p ? p.name : t.unassigned) + ' | ' + t.due + (x.due || t.noDue);
});
return [data.meeting.title || '']
.concat(section(t.summary, data.summary), section(t.decisions, data.decisions), section(t.allTasks, taskLines), section(t.questions, data.open_questions), ['', t.reply])
.join('\n');
}
// ---------- Google Sheets and Gmail ----------
function readPeople_() {
var sh = SpreadsheetApp.getActive().getSheetByName(t_().people);
if (!sh || sh.getLastRow() < 2) return [];
return sh.getRange(2, 1, sh.getLastRow() - 1, 4).getValues()
.filter(function (r) { return String(r[0]).trim(); })
.map(function (r) {
return { name: String(r[0]).trim(), role: String(r[1]).trim(), email: String(r[2]).trim(), aliases: String(r[3]).split(',').map(function (a) { return a.trim(); }).filter(String) };
});
}
/** Menu: paste the JSON, file the tasks, write Gmail drafts (nothing is sent) */
function importMeeting() {
var t = t_();
var ui = SpreadsheetApp.getUi();
var res = ui.prompt(t.menuImport, t.paste, ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() !== ui.Button.OK) return;
var data;
try { data = parseMeetingJson(res.getResponseText()); } catch (e) { ui.alert(t.badJson + e.message); return; }
var ss = SpreadsheetApp.getActive();
var tz = ss.getSpreadsheetTimeZone();
var sheet = ss.getSheetByName(t.tasks);
var people = readPeople_();
var now = new Date();
var date = normalizeDue(data.meeting.date) || Utilities.formatDate(now, tz, 'yyyy-MM-dd');
var title = String(data.meeting.title || '').trim() || date;
var start = Math.max(sheet.getLastRow(), 1);
var unassigned = 0;
var rows = data.tasks.map(function (x, i) {
var p = matchPerson(x.owner, people);
if (!p) unassigned += 1;
var id = date.replace(/-/g, '').slice(2) + '-' + (start + i);
var owner = p ? p.name : x.owner ? t.unassigned + ': ' + x.owner : t.unassigned;
return [id, date, title, x.task, owner, p ? p.role : '', p ? p.email : '', x.due, t.open, x.quote, now];
});
if (rows.length) sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
var drafts = 0;
var groups = groupByOwner(data.tasks, people);
Object.keys(groups).forEach(function (email) {
GmailApp.createDraft(email, t.subjectOwner + title, ownerEmailBody(t, groups[email].person, groups[email].tasks, title));
drafts += 1;
});
var participants = (data.meeting.participants || [])
.map(function (n) { var p = matchPerson(n, people); return p && p.email; })
.filter(function (e, i, all) { return e && all.indexOf(e) === i; });
if (participants.length) {
GmailApp.createDraft(participants.join(','), t.subjectAll + title, summaryEmailBody(t, data, people));
drafts += 1;
}
ui.alert(t.result(rows.length, drafts, unassigned));
}
/** Open tasks due today or earlier: one email to you, or one per owner if REMIND_OWNERS is true */
function remindOverdue() {
var t = t_();
var ss = SpreadsheetApp.getActive();
var tz = ss.getSpreadsheetTimeZone();
var today = Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd');
var sh = ss.getSheetByName(t.tasks);
var rows = sh.getLastRow() < 2 ? [] : sh.getRange(2, 1, sh.getLastRow() - 1, 11).getValues();
var due = rows.filter(function (r) {
var d = r[7] instanceof Date ? Utilities.formatDate(r[7], tz, 'yyyy-MM-dd') : normalizeDue(r[7]);
return d && d <= today && String(r[8]).trim() !== t.done;
});
var interactive = false;
try { SpreadsheetApp.getUi(); interactive = true; } catch (e) { interactive = false; }
if (!due.length) { if (interactive) SpreadsheetApp.getUi().alert(t.nothing); return; }
var line = function (r) { return '- ' + r[3] + ' | ' + r[4] + ' | ' + t.due + (r[7] instanceof Date ? Utilities.formatDate(r[7], tz, 'yyyy-MM-dd') : r[7]) + ' | ' + r[2]; };
var toOwners = String(PropertiesService.getScriptProperties().getProperty('REMIND_OWNERS')).toLowerCase() === 'true';
if (toOwners) {
var byEmail = {};
due.forEach(function (r) { if (r[6]) (byEmail[r[6]] = byEmail[r[6]] || []).push(r); });
Object.keys(byEmail).forEach(function (email) {
MailApp.sendEmail(email, t.subjectRemind, [t.remindIntro, ''].concat(byEmail[email].map(line)).join('\n'));
});
}
// You always get the full list, including tasks with no owner
MailApp.sendEmail(Session.getEffectiveUser().getEmail(), t.subjectRemind, [t.remindIntro, ''].concat(due.map(line)).join('\n'));
}
/** Menu: one daily trigger for remindOverdue, around 8:00 in the sheet's time zone */
function setupDailyReminder() {
ScriptApp.getProjectTriggers()
.filter(function (tr) { return tr.getHandlerFunction() === 'remindOverdue'; })
.forEach(function (tr) { ScriptApp.deleteTrigger(tr); });
ScriptApp.newTrigger('remindOverdue').timeBased().everyDays(1).atHour(8).create();
SpreadsheetApp.getUi().alert(t_().triggerOk);
}
// For local tests only (Node). Apps Script has no `module`, so this line does nothing there.
if (typeof module !== 'undefined') {
module.exports = { TEXT: TEXT, norm: norm, parseMeetingJson: parseMeetingJson, normalizeDue: normalizeDue, matchPerson: matchPerson, groupByOwner: groupByOwner, ownerEmailBody: ownerEmailBody, summaryEmailBody: summaryEmailBody };
}
הבהרה
התוכן כאן הוא המלצות כלליות בלבד, ולא ייעוץ מקצועי, משפטי, חשבונאי או מיסויי. כל עסק וארגון שונה. האחריות לבדוק שהתוכן מתאים למצב שלכם, ולבדוק את הנתונים, הפרומפטים והקוד לפני שימוש, היא שלכם. כלים, מחירים ותנאי שימוש של שירותים חיצוניים משתנים, ולכן כדאי לבדוק אותם במקור. לפני שמקליטים פגישה או שיחה, הודיעו למשתתפים, ובדקו עם עורך דין מה חל עליכם, במיוחד כשמדובר במידע על לקוחות, עובדים או משקיעים.
התוכן, הפרומפטים, התבניות והקוד ניתנים כמות שהם (AS IS), בלי התחייבות שהם מתאימים למטרה מסוימת או נקיים מטעויות. Gindi Innovation ואביעד גינדי לא יישאו באחריות לכל נזק, ישיר או עקיף, שייגרם משימוש בהם או מהסתמכות עליהם, ובכלל זה מתוצרים של כלי AI. השימוש כפוף לתקנון האתר.
ומה הלאה
הערכה הזאת עובדת עם הדבקה אחת: אתם מעבירים את התמליל ל-AI ואת התשובה לגיליון. אפשר לחבר את אותו תהליך כך שהתמליל ייכנס לבד מכלי הפגישות, המשימות יירשמו במערכת שאתם כבר עובדים איתה, והטיוטות יחכו לאישור שלכם בטלפון. ההחלטה מה יוצא נשארת שלכם.
רוצים שזה ירוץ אצלכם אוטומטית? aviad@gindiinnovation.com · gindiinnovation.com