fraim-framework 2.0.168 → 2.0.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/ai-hub/server.js +24 -3
- package/dist/src/ai-hub/word-sideload.js +95 -0
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/utils/job-aliases.js +47 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
- package/public/portfolio/ashley.html +1 -1
- package/public/portfolio/casey.html +1 -1
- package/public/portfolio/celia.html +1 -1
- package/public/portfolio/gautam.html +1 -1
- package/public/portfolio/hari.html +1 -1
- package/public/portfolio/maestro.html +1 -1
- package/public/portfolio/mandy.html +1 -1
- package/public/portfolio/pam.html +6 -6
- package/public/portfolio/qasm.html +1 -1
- package/public/portfolio/sade.html +1 -1
- package/public/portfolio/sam.html +1 -1
- package/public/portfolio/swen.html +6 -6
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>FRAIM Hub</title>
|
|
7
|
-
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
|
|
8
|
-
<script src="config.js" type="text/javascript"></script>
|
|
9
|
-
<style>
|
|
10
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
11
|
-
html, body { height: 100%; overflow: hidden; }
|
|
12
|
-
iframe { width: 100%; height: 100vh; border: none; display: block; }
|
|
13
|
-
</style>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<iframe id="hub" src="" allow="clipboard-read; clipboard-write"></iframe>
|
|
17
|
-
<script>
|
|
18
|
-
// PowerPoint task pane — mirrors extensions/office-word/taskpane.html so PPT
|
|
19
|
-
// gets the identical full Hub experience. It mounts the Hub /ai-hub/ surface in
|
|
20
|
-
// an iframe and bridges host context over postMessage using the same
|
|
21
|
-
// word-context / word-request / word-response contract the Hub already speaks
|
|
22
|
-
// (the names are the generic host-bridge protocol, not Word-specific).
|
|
23
|
-
//
|
|
24
|
-
// In Office Online the pane is served over HTTPS; loading an HTTP iframe from an
|
|
25
|
-
// HTTPS page is mixed-content-blocked, so use the pane's own origin there. On
|
|
26
|
-
// desktop (HTTP) use the direct Hub address.
|
|
27
|
-
var HUB_ORIGIN = window.location.protocol === 'https:'
|
|
28
|
-
? window.location.origin
|
|
29
|
-
: (window.FRAIM_HUB_ORIGIN || 'http://127.0.0.1:43091');
|
|
30
|
-
var hubFrame = document.getElementById('hub');
|
|
31
|
-
var pendingPush = null; // context queued before hub-ready fires
|
|
32
|
-
var hubReady = false;
|
|
33
|
-
var selectionHandlerAdded = false;
|
|
34
|
-
|
|
35
|
-
// ── postMessage bridge (inbound from Hub) ─────────────────────────────────
|
|
36
|
-
window.addEventListener('message', function(event) {
|
|
37
|
-
if (event.origin !== HUB_ORIGIN) return;
|
|
38
|
-
var msg = event.data || {};
|
|
39
|
-
if (msg.type === 'hub-ready') {
|
|
40
|
-
hubReady = true;
|
|
41
|
-
if (pendingPush) { pushToHub(pendingPush); pendingPush = null; }
|
|
42
|
-
} else if (msg.type === 'word-request') {
|
|
43
|
-
handleHostRequest(msg.action, msg.requestId, msg.payload || {});
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
function pushToHub(msg) {
|
|
48
|
-
if (hubFrame && hubFrame.contentWindow) {
|
|
49
|
-
hubFrame.contentWindow.postMessage(msg, HUB_ORIGIN);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── Reading PowerPoint context ──────────────────────────────────────────────
|
|
54
|
-
function docMeta() {
|
|
55
|
-
var meta = { docUrl: '', docTitle: '' };
|
|
56
|
-
try {
|
|
57
|
-
var doc = window.Office && Office.context && Office.context.document;
|
|
58
|
-
meta.docUrl = (doc && doc.url) ? doc.url : '';
|
|
59
|
-
if (meta.docUrl) {
|
|
60
|
-
var parts = meta.docUrl.replace(/\\/g, '/').split('/');
|
|
61
|
-
meta.docTitle = (parts[parts.length - 1] || '').replace(/\.[^.]+$/, '');
|
|
62
|
-
}
|
|
63
|
-
} catch(e) {}
|
|
64
|
-
return meta;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function readSelectionAsync(cb) {
|
|
68
|
-
try {
|
|
69
|
-
Office.context.document.getSelectedDataAsync(
|
|
70
|
-
Office.CoercionType.Text,
|
|
71
|
-
function(r) { cb(r.status === Office.AsyncResultStatus.Succeeded ? String(r.value || '').trim() : ''); }
|
|
72
|
-
);
|
|
73
|
-
} catch(e) { cb(''); }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Read the current slide's text + speaker notes via the PowerPoint API.
|
|
77
|
-
function readSlideAsync(cb) {
|
|
78
|
-
try {
|
|
79
|
-
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
80
|
-
PowerPoint.run(function(ctx) {
|
|
81
|
-
var slides = ctx.presentation.getSelectedSlides();
|
|
82
|
-
slides.load('items');
|
|
83
|
-
return ctx.sync().then(function() {
|
|
84
|
-
var slide = slides.items && slides.items[0];
|
|
85
|
-
if (!slide) { cb({ slideText: '', notes: '' }); return; }
|
|
86
|
-
var shapes = slide.shapes;
|
|
87
|
-
shapes.load('items/textFrame/textRange/text');
|
|
88
|
-
var ns = slide.notesSlide;
|
|
89
|
-
try { ns.load('notesTextFrame/textRange/text'); } catch(e) {}
|
|
90
|
-
return ctx.sync().then(function() {
|
|
91
|
-
var notes = '';
|
|
92
|
-
try { notes = ns.notesTextFrame.textRange.text || ''; } catch(e) {}
|
|
93
|
-
var slideText = (shapes.items || [])
|
|
94
|
-
.map(function(s){ try { return s.textFrame.textRange.text || ''; } catch(e){ return ''; } })
|
|
95
|
-
.filter(Boolean).join('\n');
|
|
96
|
-
cb({ slideText: slideText, notes: notes });
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
}).catch(function() { cb({ slideText: '', notes: '' }); });
|
|
100
|
-
} else { cb({ slideText: '', notes: '' }); }
|
|
101
|
-
} catch(e) { cb({ slideText: '', notes: '' }); }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function slideCountAsync(cb) {
|
|
105
|
-
try {
|
|
106
|
-
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
107
|
-
PowerPoint.run(function(ctx) {
|
|
108
|
-
var slides = ctx.presentation.slides;
|
|
109
|
-
slides.load('items');
|
|
110
|
-
return ctx.sync().then(function() { cb((slides.items || []).length); });
|
|
111
|
-
}).catch(function() { cb(0); });
|
|
112
|
-
} else { cb(0); }
|
|
113
|
-
} catch(e) { cb(0); }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function readFullContext(cb) {
|
|
117
|
-
var meta = docMeta();
|
|
118
|
-
readSelectionAsync(function(selection) {
|
|
119
|
-
readSlideAsync(function(slide) {
|
|
120
|
-
slideCountAsync(function(count) {
|
|
121
|
-
var body = [slide.slideText, slide.notes ? ('Notes: ' + slide.notes) : ''].filter(Boolean).join('\n');
|
|
122
|
-
cb({
|
|
123
|
-
docUrl: meta.docUrl,
|
|
124
|
-
docTitle: meta.docTitle,
|
|
125
|
-
selection: selection,
|
|
126
|
-
hasSelection: selection.length > 0,
|
|
127
|
-
bodyPreview: body.slice(0, 2000),
|
|
128
|
-
wordCount: body ? body.split(/\s+/).filter(Boolean).length : 0,
|
|
129
|
-
slideCount: count,
|
|
130
|
-
comments: [],
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ── Selection-change handler ───────────────────────────────────────────────
|
|
138
|
-
function addSelectionHandler() {
|
|
139
|
-
if (selectionHandlerAdded) return;
|
|
140
|
-
try {
|
|
141
|
-
Office.context.document.addHandlerAsync(
|
|
142
|
-
Office.EventType.DocumentSelectionChanged,
|
|
143
|
-
function() {
|
|
144
|
-
readSelectionAsync(function(sel) {
|
|
145
|
-
var m = docMeta();
|
|
146
|
-
pushToHub({ type: 'word-context-update', payload: {
|
|
147
|
-
docUrl: m.docUrl, docTitle: m.docTitle,
|
|
148
|
-
selection: sel, hasSelection: sel.length > 0,
|
|
149
|
-
}});
|
|
150
|
-
});
|
|
151
|
-
},
|
|
152
|
-
function(r) { if (r.status === Office.AsyncResultStatus.Succeeded) selectionHandlerAdded = true; }
|
|
153
|
-
);
|
|
154
|
-
} catch(e) {}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ── Handling requests from Hub ─────────────────────────────────────────────
|
|
158
|
-
function handleHostRequest(action, requestId, payload) {
|
|
159
|
-
function respond(result) {
|
|
160
|
-
pushToHub({ type: 'word-response', requestId: requestId, payload: result });
|
|
161
|
-
}
|
|
162
|
-
if (action === 'get-context') {
|
|
163
|
-
readFullContext(function(ctx) { respond(ctx); });
|
|
164
|
-
} else if (action === 'get-selection') {
|
|
165
|
-
readSelectionAsync(function(sel) { respond({ selection: sel, hasSelection: sel.length > 0 }); });
|
|
166
|
-
} else if (action === 'insert-text') {
|
|
167
|
-
// Insert a new text box on the current slide with the given text.
|
|
168
|
-
try {
|
|
169
|
-
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
170
|
-
PowerPoint.run(function(ctx) {
|
|
171
|
-
var slides = ctx.presentation.getSelectedSlides();
|
|
172
|
-
slides.load('items');
|
|
173
|
-
return ctx.sync().then(function() {
|
|
174
|
-
var slide = slides.items && slides.items[0];
|
|
175
|
-
if (!slide) throw new Error('No slide selected');
|
|
176
|
-
var box = slide.shapes.addTextBox(payload.text || '');
|
|
177
|
-
box.left = 50; box.top = 50; box.width = 600; box.height = 100;
|
|
178
|
-
return ctx.sync();
|
|
179
|
-
});
|
|
180
|
-
}).then(function() { respond({ ok: true }); }).catch(function(e) { respond({ ok: false, error: e.message }); });
|
|
181
|
-
} else { respond({ ok: false, error: 'PowerPoint API 1.1+ not available' }); }
|
|
182
|
-
} catch(e) { respond({ ok: false, error: e.message }); }
|
|
183
|
-
} else if (action === 'insert-after' || action === 'append-to-doc') {
|
|
184
|
-
// Append text into the current slide's speaker notes.
|
|
185
|
-
try {
|
|
186
|
-
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
187
|
-
PowerPoint.run(function(ctx) {
|
|
188
|
-
var slides = ctx.presentation.getSelectedSlides();
|
|
189
|
-
slides.load('items');
|
|
190
|
-
return ctx.sync().then(function() {
|
|
191
|
-
var slide = slides.items && slides.items[0];
|
|
192
|
-
if (!slide) throw new Error('No slide selected');
|
|
193
|
-
var ns = slide.notesSlide;
|
|
194
|
-
ns.load('notesTextFrame/textRange/text');
|
|
195
|
-
return ctx.sync().then(function() {
|
|
196
|
-
var existing = '';
|
|
197
|
-
try { existing = ns.notesTextFrame.textRange.text || ''; } catch(e) {}
|
|
198
|
-
ns.notesTextFrame.textRange.text = (existing ? existing + '\n' : '') + (payload.text || '');
|
|
199
|
-
return ctx.sync();
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
}).then(function() { respond({ ok: true }); }).catch(function(e) { respond({ ok: false, error: e.message }); });
|
|
203
|
-
} else { respond({ ok: false, error: 'PowerPoint API not available' }); }
|
|
204
|
-
} catch(e) { respond({ ok: false, error: e.message }); }
|
|
205
|
-
} else {
|
|
206
|
-
respond({ ok: false, error: 'Unknown action: ' + action });
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── Mount ─────────────────────────────────────────────────────────────────
|
|
211
|
-
function mountHub() {
|
|
212
|
-
var meta = docMeta();
|
|
213
|
-
var params = new URLSearchParams({ surface: 'task-pane' });
|
|
214
|
-
if (meta.docUrl) params.set('docUrl', meta.docUrl);
|
|
215
|
-
if (meta.docTitle) params.set('docTitle', meta.docTitle);
|
|
216
|
-
hubFrame.src = HUB_ORIGIN + '/ai-hub/?' + params.toString();
|
|
217
|
-
|
|
218
|
-
hubFrame.addEventListener('load', function() {
|
|
219
|
-
setTimeout(function() {
|
|
220
|
-
readFullContext(function(ctx) {
|
|
221
|
-
var msg = { type: 'word-context', payload: ctx };
|
|
222
|
-
if (hubReady) { pushToHub(msg); } else { pendingPush = msg; }
|
|
223
|
-
});
|
|
224
|
-
}, 300);
|
|
225
|
-
addSelectionHandler();
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (typeof Office !== 'undefined') {
|
|
230
|
-
Office.onReady(function() { mountHub(); });
|
|
231
|
-
} else {
|
|
232
|
-
window.addEventListener('DOMContentLoaded', mountHub);
|
|
233
|
-
}
|
|
234
|
-
</script>
|
|
235
|
-
</body>
|
|
236
|
-
</html>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>FRAIM Hub</title>
|
|
7
|
+
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
|
|
8
|
+
<script src="config.js" type="text/javascript"></script>
|
|
9
|
+
<style>
|
|
10
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
11
|
+
html, body { height: 100%; overflow: hidden; }
|
|
12
|
+
iframe { width: 100%; height: 100vh; border: none; display: block; }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<iframe id="hub" src="" allow="clipboard-read; clipboard-write"></iframe>
|
|
17
|
+
<script>
|
|
18
|
+
// PowerPoint task pane — mirrors extensions/office-word/taskpane.html so PPT
|
|
19
|
+
// gets the identical full Hub experience. It mounts the Hub /ai-hub/ surface in
|
|
20
|
+
// an iframe and bridges host context over postMessage using the same
|
|
21
|
+
// word-context / word-request / word-response contract the Hub already speaks
|
|
22
|
+
// (the names are the generic host-bridge protocol, not Word-specific).
|
|
23
|
+
//
|
|
24
|
+
// In Office Online the pane is served over HTTPS; loading an HTTP iframe from an
|
|
25
|
+
// HTTPS page is mixed-content-blocked, so use the pane's own origin there. On
|
|
26
|
+
// desktop (HTTP) use the direct Hub address.
|
|
27
|
+
var HUB_ORIGIN = window.location.protocol === 'https:'
|
|
28
|
+
? window.location.origin
|
|
29
|
+
: (window.FRAIM_HUB_ORIGIN || 'http://127.0.0.1:43091');
|
|
30
|
+
var hubFrame = document.getElementById('hub');
|
|
31
|
+
var pendingPush = null; // context queued before hub-ready fires
|
|
32
|
+
var hubReady = false;
|
|
33
|
+
var selectionHandlerAdded = false;
|
|
34
|
+
|
|
35
|
+
// ── postMessage bridge (inbound from Hub) ─────────────────────────────────
|
|
36
|
+
window.addEventListener('message', function(event) {
|
|
37
|
+
if (event.origin !== HUB_ORIGIN) return;
|
|
38
|
+
var msg = event.data || {};
|
|
39
|
+
if (msg.type === 'hub-ready') {
|
|
40
|
+
hubReady = true;
|
|
41
|
+
if (pendingPush) { pushToHub(pendingPush); pendingPush = null; }
|
|
42
|
+
} else if (msg.type === 'word-request') {
|
|
43
|
+
handleHostRequest(msg.action, msg.requestId, msg.payload || {});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
function pushToHub(msg) {
|
|
48
|
+
if (hubFrame && hubFrame.contentWindow) {
|
|
49
|
+
hubFrame.contentWindow.postMessage(msg, HUB_ORIGIN);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Reading PowerPoint context ──────────────────────────────────────────────
|
|
54
|
+
function docMeta() {
|
|
55
|
+
var meta = { docUrl: '', docTitle: '' };
|
|
56
|
+
try {
|
|
57
|
+
var doc = window.Office && Office.context && Office.context.document;
|
|
58
|
+
meta.docUrl = (doc && doc.url) ? doc.url : '';
|
|
59
|
+
if (meta.docUrl) {
|
|
60
|
+
var parts = meta.docUrl.replace(/\\/g, '/').split('/');
|
|
61
|
+
meta.docTitle = (parts[parts.length - 1] || '').replace(/\.[^.]+$/, '');
|
|
62
|
+
}
|
|
63
|
+
} catch(e) {}
|
|
64
|
+
return meta;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readSelectionAsync(cb) {
|
|
68
|
+
try {
|
|
69
|
+
Office.context.document.getSelectedDataAsync(
|
|
70
|
+
Office.CoercionType.Text,
|
|
71
|
+
function(r) { cb(r.status === Office.AsyncResultStatus.Succeeded ? String(r.value || '').trim() : ''); }
|
|
72
|
+
);
|
|
73
|
+
} catch(e) { cb(''); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Read the current slide's text + speaker notes via the PowerPoint API.
|
|
77
|
+
function readSlideAsync(cb) {
|
|
78
|
+
try {
|
|
79
|
+
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
80
|
+
PowerPoint.run(function(ctx) {
|
|
81
|
+
var slides = ctx.presentation.getSelectedSlides();
|
|
82
|
+
slides.load('items');
|
|
83
|
+
return ctx.sync().then(function() {
|
|
84
|
+
var slide = slides.items && slides.items[0];
|
|
85
|
+
if (!slide) { cb({ slideText: '', notes: '' }); return; }
|
|
86
|
+
var shapes = slide.shapes;
|
|
87
|
+
shapes.load('items/textFrame/textRange/text');
|
|
88
|
+
var ns = slide.notesSlide;
|
|
89
|
+
try { ns.load('notesTextFrame/textRange/text'); } catch(e) {}
|
|
90
|
+
return ctx.sync().then(function() {
|
|
91
|
+
var notes = '';
|
|
92
|
+
try { notes = ns.notesTextFrame.textRange.text || ''; } catch(e) {}
|
|
93
|
+
var slideText = (shapes.items || [])
|
|
94
|
+
.map(function(s){ try { return s.textFrame.textRange.text || ''; } catch(e){ return ''; } })
|
|
95
|
+
.filter(Boolean).join('\n');
|
|
96
|
+
cb({ slideText: slideText, notes: notes });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}).catch(function() { cb({ slideText: '', notes: '' }); });
|
|
100
|
+
} else { cb({ slideText: '', notes: '' }); }
|
|
101
|
+
} catch(e) { cb({ slideText: '', notes: '' }); }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function slideCountAsync(cb) {
|
|
105
|
+
try {
|
|
106
|
+
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
107
|
+
PowerPoint.run(function(ctx) {
|
|
108
|
+
var slides = ctx.presentation.slides;
|
|
109
|
+
slides.load('items');
|
|
110
|
+
return ctx.sync().then(function() { cb((slides.items || []).length); });
|
|
111
|
+
}).catch(function() { cb(0); });
|
|
112
|
+
} else { cb(0); }
|
|
113
|
+
} catch(e) { cb(0); }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readFullContext(cb) {
|
|
117
|
+
var meta = docMeta();
|
|
118
|
+
readSelectionAsync(function(selection) {
|
|
119
|
+
readSlideAsync(function(slide) {
|
|
120
|
+
slideCountAsync(function(count) {
|
|
121
|
+
var body = [slide.slideText, slide.notes ? ('Notes: ' + slide.notes) : ''].filter(Boolean).join('\n');
|
|
122
|
+
cb({
|
|
123
|
+
docUrl: meta.docUrl,
|
|
124
|
+
docTitle: meta.docTitle,
|
|
125
|
+
selection: selection,
|
|
126
|
+
hasSelection: selection.length > 0,
|
|
127
|
+
bodyPreview: body.slice(0, 2000),
|
|
128
|
+
wordCount: body ? body.split(/\s+/).filter(Boolean).length : 0,
|
|
129
|
+
slideCount: count,
|
|
130
|
+
comments: [],
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Selection-change handler ───────────────────────────────────────────────
|
|
138
|
+
function addSelectionHandler() {
|
|
139
|
+
if (selectionHandlerAdded) return;
|
|
140
|
+
try {
|
|
141
|
+
Office.context.document.addHandlerAsync(
|
|
142
|
+
Office.EventType.DocumentSelectionChanged,
|
|
143
|
+
function() {
|
|
144
|
+
readSelectionAsync(function(sel) {
|
|
145
|
+
var m = docMeta();
|
|
146
|
+
pushToHub({ type: 'word-context-update', payload: {
|
|
147
|
+
docUrl: m.docUrl, docTitle: m.docTitle,
|
|
148
|
+
selection: sel, hasSelection: sel.length > 0,
|
|
149
|
+
}});
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
function(r) { if (r.status === Office.AsyncResultStatus.Succeeded) selectionHandlerAdded = true; }
|
|
153
|
+
);
|
|
154
|
+
} catch(e) {}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Handling requests from Hub ─────────────────────────────────────────────
|
|
158
|
+
function handleHostRequest(action, requestId, payload) {
|
|
159
|
+
function respond(result) {
|
|
160
|
+
pushToHub({ type: 'word-response', requestId: requestId, payload: result });
|
|
161
|
+
}
|
|
162
|
+
if (action === 'get-context') {
|
|
163
|
+
readFullContext(function(ctx) { respond(ctx); });
|
|
164
|
+
} else if (action === 'get-selection') {
|
|
165
|
+
readSelectionAsync(function(sel) { respond({ selection: sel, hasSelection: sel.length > 0 }); });
|
|
166
|
+
} else if (action === 'insert-text') {
|
|
167
|
+
// Insert a new text box on the current slide with the given text.
|
|
168
|
+
try {
|
|
169
|
+
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
170
|
+
PowerPoint.run(function(ctx) {
|
|
171
|
+
var slides = ctx.presentation.getSelectedSlides();
|
|
172
|
+
slides.load('items');
|
|
173
|
+
return ctx.sync().then(function() {
|
|
174
|
+
var slide = slides.items && slides.items[0];
|
|
175
|
+
if (!slide) throw new Error('No slide selected');
|
|
176
|
+
var box = slide.shapes.addTextBox(payload.text || '');
|
|
177
|
+
box.left = 50; box.top = 50; box.width = 600; box.height = 100;
|
|
178
|
+
return ctx.sync();
|
|
179
|
+
});
|
|
180
|
+
}).then(function() { respond({ ok: true }); }).catch(function(e) { respond({ ok: false, error: e.message }); });
|
|
181
|
+
} else { respond({ ok: false, error: 'PowerPoint API 1.1+ not available' }); }
|
|
182
|
+
} catch(e) { respond({ ok: false, error: e.message }); }
|
|
183
|
+
} else if (action === 'insert-after' || action === 'append-to-doc') {
|
|
184
|
+
// Append text into the current slide's speaker notes.
|
|
185
|
+
try {
|
|
186
|
+
if (typeof PowerPoint !== 'undefined' && PowerPoint.run) {
|
|
187
|
+
PowerPoint.run(function(ctx) {
|
|
188
|
+
var slides = ctx.presentation.getSelectedSlides();
|
|
189
|
+
slides.load('items');
|
|
190
|
+
return ctx.sync().then(function() {
|
|
191
|
+
var slide = slides.items && slides.items[0];
|
|
192
|
+
if (!slide) throw new Error('No slide selected');
|
|
193
|
+
var ns = slide.notesSlide;
|
|
194
|
+
ns.load('notesTextFrame/textRange/text');
|
|
195
|
+
return ctx.sync().then(function() {
|
|
196
|
+
var existing = '';
|
|
197
|
+
try { existing = ns.notesTextFrame.textRange.text || ''; } catch(e) {}
|
|
198
|
+
ns.notesTextFrame.textRange.text = (existing ? existing + '\n' : '') + (payload.text || '');
|
|
199
|
+
return ctx.sync();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}).then(function() { respond({ ok: true }); }).catch(function(e) { respond({ ok: false, error: e.message }); });
|
|
203
|
+
} else { respond({ ok: false, error: 'PowerPoint API not available' }); }
|
|
204
|
+
} catch(e) { respond({ ok: false, error: e.message }); }
|
|
205
|
+
} else {
|
|
206
|
+
respond({ ok: false, error: 'Unknown action: ' + action });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Mount ─────────────────────────────────────────────────────────────────
|
|
211
|
+
function mountHub() {
|
|
212
|
+
var meta = docMeta();
|
|
213
|
+
var params = new URLSearchParams({ surface: 'task-pane' });
|
|
214
|
+
if (meta.docUrl) params.set('docUrl', meta.docUrl);
|
|
215
|
+
if (meta.docTitle) params.set('docTitle', meta.docTitle);
|
|
216
|
+
hubFrame.src = HUB_ORIGIN + '/ai-hub/?' + params.toString();
|
|
217
|
+
|
|
218
|
+
hubFrame.addEventListener('load', function() {
|
|
219
|
+
setTimeout(function() {
|
|
220
|
+
readFullContext(function(ctx) {
|
|
221
|
+
var msg = { type: 'word-context', payload: ctx };
|
|
222
|
+
if (hubReady) { pushToHub(msg); } else { pendingPush = msg; }
|
|
223
|
+
});
|
|
224
|
+
}, 300);
|
|
225
|
+
addSelectionHandler();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (typeof Office !== 'undefined') {
|
|
230
|
+
Office.onReady(function() { mountHub(); });
|
|
231
|
+
} else {
|
|
232
|
+
window.addEventListener('DOMContentLoaded', mountHub);
|
|
233
|
+
}
|
|
234
|
+
</script>
|
|
235
|
+
</body>
|
|
236
|
+
</html>
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
|
|
3
|
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
4
|
-
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
|
|
5
|
-
xsi:type="TaskPaneApp">
|
|
6
|
-
<Id>e7a3c812-91fd-4b2e-8c15-d4f6a903b71e</Id>
|
|
7
|
-
<Version>1.0.0.0</Version>
|
|
8
|
-
<ProviderName>FRAIM</ProviderName>
|
|
9
|
-
<DefaultLocale>en-US</DefaultLocale>
|
|
10
|
-
<DisplayName DefaultValue="FRAIM"/>
|
|
11
|
-
<Description DefaultValue="FRAIM AI Hub in PowerPoint - analyze, draft, and improve presentations."/>
|
|
12
|
-
<IconUrl DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/icon-64.png"/>
|
|
13
|
-
<HighResolutionIconUrl DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/icon-64.png"/>
|
|
14
|
-
<SupportUrl DefaultValue="http://127.0.0.1:43091"/>
|
|
15
|
-
<AppDomains>
|
|
16
|
-
<AppDomain>https://appsforoffice.microsoft.com</AppDomain>
|
|
17
|
-
</AppDomains>
|
|
18
|
-
<Hosts>
|
|
19
|
-
<Host Name="Presentation"/>
|
|
20
|
-
</Hosts>
|
|
21
|
-
<Requirements>
|
|
22
|
-
<Sets>
|
|
23
|
-
<Set Name="PowerPointApi" MinVersion="1.1"/>
|
|
24
|
-
</Sets>
|
|
25
|
-
</Requirements>
|
|
26
|
-
<DefaultSettings>
|
|
27
|
-
<SourceLocation DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/"/>
|
|
28
|
-
</DefaultSettings>
|
|
29
|
-
<Permissions>ReadWriteDocument</Permissions>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
|
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
4
|
+
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
|
|
5
|
+
xsi:type="TaskPaneApp">
|
|
6
|
+
<Id>e7a3c812-91fd-4b2e-8c15-d4f6a903b71e</Id>
|
|
7
|
+
<Version>1.0.0.0</Version>
|
|
8
|
+
<ProviderName>FRAIM</ProviderName>
|
|
9
|
+
<DefaultLocale>en-US</DefaultLocale>
|
|
10
|
+
<DisplayName DefaultValue="FRAIM"/>
|
|
11
|
+
<Description DefaultValue="FRAIM AI Hub in PowerPoint - analyze, draft, and improve presentations."/>
|
|
12
|
+
<IconUrl DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/icon-64.png"/>
|
|
13
|
+
<HighResolutionIconUrl DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/icon-64.png"/>
|
|
14
|
+
<SupportUrl DefaultValue="http://127.0.0.1:43091"/>
|
|
15
|
+
<AppDomains>
|
|
16
|
+
<AppDomain>https://appsforoffice.microsoft.com</AppDomain>
|
|
17
|
+
</AppDomains>
|
|
18
|
+
<Hosts>
|
|
19
|
+
<Host Name="Presentation"/>
|
|
20
|
+
</Hosts>
|
|
21
|
+
<Requirements>
|
|
22
|
+
<Sets>
|
|
23
|
+
<Set Name="PowerPointApi" MinVersion="1.1"/>
|
|
24
|
+
</Sets>
|
|
25
|
+
</Requirements>
|
|
26
|
+
<DefaultSettings>
|
|
27
|
+
<SourceLocation DefaultValue="http://127.0.0.1:43091/powerpoint-taskpane/"/>
|
|
28
|
+
</DefaultSettings>
|
|
29
|
+
<Permissions>ReadWriteDocument</Permissions>
|
|
30
30
|
</OfficeApp>
|
|
@@ -434,7 +434,7 @@ body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--tex
|
|
|
434
434
|
<div class="brand-logo">F</div>
|
|
435
435
|
<span class="brand-name">FRAIM</span>
|
|
436
436
|
</a>
|
|
437
|
-
<div class="header-actions">
|
|
437
|
+
<div class="header-actions">
|
|
438
438
|
<button class="theme-btn" onclick="toggleTheme()" title="Toggle dark mode">☾</button>
|
|
439
439
|
</div>
|
|
440
440
|
</header>
|
|
@@ -445,8 +445,8 @@ body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--tex
|
|
|
445
445
|
</div>
|
|
446
446
|
<div class="role-chip">AI Product Manager</div>
|
|
447
447
|
<h1>Specs that ship,<br>not <span>slide decks</span></h1>
|
|
448
|
-
<p>PaM turns vague ideas into traceable requirements, phased roadmaps, and acceptance criteria that engineers can execute on — without ambiguity, without meetings.</p>
|
|
449
|
-
</section>
|
|
448
|
+
<p>PaM turns vague ideas into traceable requirements, phased roadmaps, and acceptance criteria that engineers can execute on — without ambiguity, without meetings.</p>
|
|
449
|
+
</section>
|
|
450
450
|
|
|
451
451
|
<div class="section-label">
|
|
452
452
|
<h2>Selected Work</h2>
|
|
@@ -792,8 +792,8 @@ await ceq.track(<span class="snippet-string">'purchase'</span>, { memberId, amou
|
|
|
792
792
|
|
|
793
793
|
</div>
|
|
794
794
|
|
|
795
|
-
<footer class="portfolio-footer">
|
|
796
|
-
<div class="footer-sub">
|
|
795
|
+
<footer class="portfolio-footer">
|
|
796
|
+
<div class="footer-sub">
|
|
797
797
|
Part of the <a href="/">FRAIM</a> · 18 AI employees available ·
|
|
798
798
|
<a href="/">View all employees</a>
|
|
799
799
|
</div>
|
|
@@ -882,6 +882,6 @@ function updateWizard() {
|
|
|
882
882
|
// Init brand scale on load
|
|
883
883
|
document.addEventListener('DOMContentLoaded', () => setBrand('#6366f1','#6366f1'));
|
|
884
884
|
</script>
|
|
885
|
-
|
|
885
|
+
|
|
886
886
|
</body>
|
|
887
887
|
</html>
|