-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCode.js
More file actions
239 lines (220 loc) · 9.68 KB
/
Code.js
File metadata and controls
239 lines (220 loc) · 9.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/**
* Learning Library Dynamic Content Engine — Master Orchestration
* Spreadsheet-bound. All top-level GAS entry points live here.
*/
// ─── Menu ────────────────────────────────────────────────────────────────────
function onOpen() {
SessionService.ensureMasterSheet();
SpreadsheetApp.getUi()
.createMenu('Learning Library Engine')
.addItem('🚀 Launch New Session', 'showWizard')
.addSeparator()
.addItem('🔬 Synthesize Research', 'menuTriggerSynthesis')
.addItem('💎 Generate Gems', 'menuTriggerGems')
.addItem('📧 Draft & Send Pre-Session Email', 'menuTriggerEmail')
.addItem('🌐 Generate Site Code', 'menuTriggerSiteCode')
.addSeparator()
.addItem('📅 Authorize Calendar Access', 'triggerCalendarAuth')
.addItem('🌐 Open Dashboard', 'openDashboard')
.addItem('⚙️ Initial Setup', 'showSetupWizard')
.addToUi();
}
// ─── Wizard / Session Launch ─────────────────────────────────────────────────
function showWizard() {
const html = HtmlService.createHtmlOutputFromFile('Wizard')
.setWidth(500)
.setHeight(640);
SpreadsheetApp.getUi().showModalDialog(html, '🚀 Launch New Session');
}
/**
* Called from Wizard.html via google.script.run.
* @param {Object} params - { name, theme, date, format, audience, brand }
* @returns {Object} - { sessionId, folderUrl, dbUrl, brief }
*/
function processWizardSubmit(params) {
const result = SessionService.setupSession(params);
CacheService.getScriptCache().remove('dashboard_v2');
return result;
}
// ─── Menu Triggers (row-selected operations) ─────────────────────────────────
function menuTriggerSynthesis() {
const { sessionId, session } = _getSelectedSession();
if (!sessionId) return;
const ui = SpreadsheetApp.getUi();
try {
ui.alert(`Starting Synthesis for: ${session.name}\nThis may take 30–60 seconds...`);
const count = SynthesisService.synthesizeAll(sessionId);
ui.alert(`✅ Synthesis complete! ${count} resource(s) analyzed.`);
} catch (e) {
ui.alert('❌ Synthesis failed: ' + e.message);
}
}
function menuTriggerGems() {
const { sessionId, session } = _getSelectedSession();
if (!sessionId) return;
const ui = SpreadsheetApp.getUi();
try {
ui.alert(`Generating Gems for: ${session.name}\nThis may take 20–40 seconds...`);
GemsService.generateGems(sessionId);
ui.alert('✅ Gems generated! View them in the Dashboard or Learning Library.');
} catch (e) {
ui.alert('❌ Gems generation failed: ' + e.message);
}
}
function menuTriggerEmail() {
const { sessionId, session } = _getSelectedSession();
if (!sessionId) return;
const ui = SpreadsheetApp.getUi();
const recipientsInput = ui.prompt(
'Pre-Session Email',
`Enter recipient emails for "${session.name}" (comma-separated):`,
ui.ButtonSet.OK_CANCEL
);
if (recipientsInput.getSelectedButton() !== ui.Button.OK) return;
const recipients = recipientsInput.getResponseText()
.split(',').map(e => e.trim()).filter(Boolean);
if (!recipients.length) {
ui.alert('No recipients entered.');
return;
}
try {
ui.alert('Drafting email with AI... (~15 seconds)');
const result = EmailService.draftAndSend(sessionId, recipients);
ui.alert(`✅ Email sent to ${recipients.length} recipient(s)!\nSubject: ${result.subject}`);
} catch (e) {
ui.alert('❌ Email failed: ' + e.message);
}
}
function menuTriggerSiteCode() {
const { sessionId, session } = _getSelectedSession();
if (!sessionId) return;
const ui = SpreadsheetApp.getUi();
try {
ui.alert(`Generating site code for: ${session.name}...\nThis takes ~10 seconds.`);
const result = SitesService.generateSiteCode(sessionId);
ui.alert(`✅ Site code created!\n\nOpen the doc to copy each section:\n${result.docUrl}`);
} catch (e) {
ui.alert('❌ Site code failed: ' + e.message);
}
}
function triggerCalendarAuth() {
var authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
if (authInfo.getAuthorizationStatus() === ScriptApp.AuthorizationStatus.REQUIRED) {
var authUrl = authInfo.getAuthorizationUrl();
var html = HtmlService.createHtmlOutput(
'<!DOCTYPE html><html><body style="font-family:\'Google Sans\',Arial,sans-serif;padding:20px;">' +
'<p style="margin:0 0 14px;color:#444;font-size:13px;">Calendar access requires re-authorization.<br>Click the button below to grant access.</p>' +
'<a href="' + authUrl + '" target="_blank" ' +
'style="display:inline-block;background:#0B2B46;color:#fff;padding:10px 22px;border-radius:6px;' +
'text-decoration:none;font-size:13px;font-weight:700;">Authorize Calendar Access →</a>' +
'<p style="margin-top:12px;font-size:0.75rem;color:#888;">After authorizing, reload the web app. The dashboard will load without delays.</p>' +
'</body></html>'
).setWidth(380).setHeight(180);
SpreadsheetApp.getUi().showModalDialog(html, 'Calendar Authorization Required');
} else {
// Already authorized — confirm it works
try {
CalendarApp.getDefaultCalendar();
SpreadsheetApp.getUi().alert('✅ Calendar access is already authorized. Reload the web app — the dashboard should load normally.');
} catch (e) {
SpreadsheetApp.getUi().alert('Calendar check failed: ' + e.message);
}
}
}
function openDashboard() {
// Read the URL cached when doGet() first ran — that is always the live /exec URL.
// Calling ScriptApp.getService().getUrl() from a menu trigger returns the /dev URL,
// which only works for script editors and appears "not available" to everyone else.
var url = '';
try { url = PropertiesService.getScriptProperties().getProperty('WEB_APP_URL') || ''; } catch(e) {}
if (!url) {
// Fallback if the web app hasn't been opened yet since last deploy
try { url = ScriptApp.getService().getUrl() || ''; } catch(e) {}
}
if (!url) {
SpreadsheetApp.getUi().alert(
'⚠️ Dashboard URL not found.\n\n' +
'Open the web app once in your browser (Deploy → Manage deployments → copy the /exec URL), ' +
'then return here and the menu item will work automatically.'
);
return;
}
const safeUrl = url.replace(/'/g, "\\'").replace(/"/g, '"');
const html = HtmlService.createHtmlOutput(
`<!DOCTYPE html><html><body style="font-family:'Google Sans',Arial,sans-serif;margin:0;padding:20px 24px;background:#fff;">
<div style="border:1px solid #DDE4ED;border-radius:8px;overflow:hidden;">
<div style="background:#0B2B46;padding:10px 16px;">
<span style="color:#5DCDF5;font-size:10px;letter-spacing:2px;text-transform:uppercase;font-weight:700;">Learning Library</span>
<span style="color:rgba(255,255,255,0.6);font-size:10px;margin-left:8px;">Content Engine</span>
</div>
<div style="padding:16px;text-align:center;">
<p style="margin:0 0 16px;color:#444;font-size:13px;">Dashboard is opening in a new tab.</p>
<a href="${safeUrl}" target="_blank"
style="display:inline-block;background:#0B2B46;color:#fff;padding:10px 28px;border-radius:6px;text-decoration:none;font-size:13px;font-weight:700;letter-spacing:.3px;">
Open Dashboard →
</a>
</div>
</div>
<script>window.open('${safeUrl}', '_blank');<\/script>
</body></html>`
).setWidth(340).setHeight(170);
SpreadsheetApp.getUi().showModalDialog(html, 'Open Dashboard');
}
function showSetupWizard() {
const html = HtmlService.createHtmlOutputFromFile('Setup')
.setWidth(500)
.setHeight(420);
SpreadsheetApp.getUi().showModalDialog(html, '⚙️ Initial Setup Wizard');
}
function getSetupConfig() {
const props = PropertiesService.getScriptProperties();
return {
geminiKey: props.getProperty('GEMINI_API_KEY') || '',
rootFolderId: props.getProperty('ROOT_FOLDER_ID') || ''
};
}
function saveSetupConfig(config) {
const props = PropertiesService.getScriptProperties();
if (config.geminiKey) props.setProperty('GEMINI_API_KEY', config.geminiKey);
if (config.rootFolderId) props.setProperty('ROOT_FOLDER_ID', config.rootFolderId);
return true;
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
/**
* Gets the session corresponding to the currently selected row in the Sessions sheet.
*/
function _getSelectedSession() {
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getActiveSheet();
if (sheet.getName() !== 'Sessions') {
ui.alert('Please navigate to the Sessions tab and select a session row.');
return {};
}
const row = sheet.getActiveCell().getRow();
if (row <= 1) {
ui.alert('Please select a session row (not the header).');
return {};
}
const sessionId = sheet.getRange(row, 1).getValue();
const session = SessionService.getSession(sessionId);
if (!session) {
ui.alert('Could not find session data for row ' + row);
return {};
}
return { sessionId, session };
}
/**
* Returns the Gemini API key from Script Properties.
* Used by GeminiService.
*/
function getGeminiApiKey() {
return PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
}
/**
* Returns the current user's OAuth token for the Google Drive Picker.
* Called from Wizard.html and Index.html via google.script.run.
*/
function getOAuthToken() {
return ScriptApp.getOAuthToken();
}