browser-ai-summarizer 1.0.0
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/README.md +142 -0
- package/dist/browser-ai-summarizer.cjs.cjs +408 -0
- package/dist/browser-ai-summarizer.esm.js +961 -0
- package/dist/browser-ai-summarizer.umd.js +408 -0
- package/package.json +27 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports):typeof define==`function`&&define.amd?define([`exports`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.AISummarizer={}))})(this,function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`ai-summarizer-styles`;function n(e){if(document.getElementById(t))return;let n=document.createElement(`style`);n.id=t,n.textContent=r(e),document.head.appendChild(n)}function r(e){let t={buttonBg:e.buttonBg??`#111111`,buttonColor:e.buttonColor??`#f7f3ed`,buttonRadius:e.buttonRadius??`3px`,buttonFont:e.buttonFont??`system-ui, -apple-system, sans-serif`,accentColor:e.accentColor??`#111111`,cardBg:e.cardBg??`#faf8f5`,cardHeaderBg:e.cardHeaderBg??`#f3ede5`,cardBorder:e.cardBorder??`#e4ddd4`,cardFont:e.cardFont??`system-ui, -apple-system, sans-serif`,zIndex:e.zIndex??1};return`
|
|
2
|
+
/* ── AI Summarizer Widget ────────────────────────────────── */
|
|
3
|
+
.ais-root {
|
|
4
|
+
--ais-btn-bg: ${t.buttonBg};
|
|
5
|
+
--ais-btn-color: ${t.buttonColor};
|
|
6
|
+
--ais-btn-radius: ${t.buttonRadius};
|
|
7
|
+
--ais-btn-font: ${t.buttonFont};
|
|
8
|
+
--ais-accent: ${t.accentColor};
|
|
9
|
+
--ais-card-bg: ${t.cardBg};
|
|
10
|
+
--ais-card-head-bg: ${t.cardHeaderBg};
|
|
11
|
+
--ais-card-border: ${t.cardBorder};
|
|
12
|
+
--ais-card-font: ${t.cardFont};
|
|
13
|
+
--ais-z: ${t.zIndex};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* ── Trigger ── */
|
|
17
|
+
.ais-trigger {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: 0.75rem;
|
|
21
|
+
margin: 1.75rem 0 0;
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
position: relative;
|
|
24
|
+
z-index: var(--ais-z);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ais-btn {
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 0.45rem;
|
|
31
|
+
padding: 0.55rem 1.15rem;
|
|
32
|
+
background: var(--ais-btn-bg);
|
|
33
|
+
color: var(--ais-btn-color);
|
|
34
|
+
font-family: var(--ais-btn-font);
|
|
35
|
+
font-size: 0.8rem;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
letter-spacing: 0.05em;
|
|
38
|
+
text-transform: uppercase;
|
|
39
|
+
border: 1.5px solid var(--ais-btn-bg);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
border-radius: var(--ais-btn-radius);
|
|
42
|
+
line-height: 1;
|
|
43
|
+
transition: filter 0.15s, opacity 0.2s, transform 0.1s;
|
|
44
|
+
user-select: none;
|
|
45
|
+
}
|
|
46
|
+
.ais-btn:hover:not(:disabled) { filter: brightness(1.15); }
|
|
47
|
+
.ais-btn:active:not(:disabled) { transform: scale(0.97); }
|
|
48
|
+
.ais-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
49
|
+
.ais-btn svg { width: 14px; height: 14px; display: block; }
|
|
50
|
+
|
|
51
|
+
.ais-btn--ghost {
|
|
52
|
+
background: transparent;
|
|
53
|
+
color: #666;
|
|
54
|
+
border-color: #d5cfc7;
|
|
55
|
+
}
|
|
56
|
+
.ais-btn--ghost:hover:not(:disabled) { filter: none; background: #f5f5f5; }
|
|
57
|
+
|
|
58
|
+
/* ── Spinner ── */
|
|
59
|
+
.ais-spinner {
|
|
60
|
+
display: inline-block;
|
|
61
|
+
width: 1em;
|
|
62
|
+
height: 1em;
|
|
63
|
+
border: 0.15em solid rgba(255,255,255,0.2);
|
|
64
|
+
border-top-color: currentColor;
|
|
65
|
+
border-radius: 50%;
|
|
66
|
+
animation: ais-spin 0.65s linear infinite;
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
vertical-align: -0.125em;
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
}
|
|
71
|
+
.ais-spinner--dark {
|
|
72
|
+
border-color: rgba(0,0,0,0.12);
|
|
73
|
+
border-top-color: #333;
|
|
74
|
+
}
|
|
75
|
+
@keyframes ais-spin { to { transform: rotate(360deg); } }
|
|
76
|
+
|
|
77
|
+
/* ── Type pills ── */
|
|
78
|
+
.ais-pills {
|
|
79
|
+
display: none;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: 0.35rem;
|
|
82
|
+
flex-wrap: wrap;
|
|
83
|
+
margin: 0.75rem 0 0;
|
|
84
|
+
}
|
|
85
|
+
.ais-pills--visible { display: flex; }
|
|
86
|
+
.ais-pills__label {
|
|
87
|
+
font-family: var(--ais-btn-font);
|
|
88
|
+
font-size: 0.7rem;
|
|
89
|
+
color: #999;
|
|
90
|
+
letter-spacing: 0.06em;
|
|
91
|
+
text-transform: uppercase;
|
|
92
|
+
margin-right: 0.15rem;
|
|
93
|
+
}
|
|
94
|
+
.ais-pill {
|
|
95
|
+
font-family: var(--ais-btn-font);
|
|
96
|
+
font-size: 0.7rem;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
letter-spacing: 0.05em;
|
|
99
|
+
text-transform: uppercase;
|
|
100
|
+
padding: 0.22rem 0.6rem;
|
|
101
|
+
border: 1px solid #ddd;
|
|
102
|
+
border-radius: 100px;
|
|
103
|
+
background: #fff;
|
|
104
|
+
color: #777;
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
transition: all 0.13s;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
}
|
|
109
|
+
.ais-pill:hover:not(:disabled):not(.ais-pill--active) { border-color: #999; color: #333; }
|
|
110
|
+
.ais-pill:disabled {
|
|
111
|
+
opacity: 0.5;
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
filter: grayscale(0.5);
|
|
114
|
+
}
|
|
115
|
+
.ais-pill--active {
|
|
116
|
+
background: var(--ais-accent);
|
|
117
|
+
border-color: var(--ais-accent);
|
|
118
|
+
color: #fff;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ── Consent dialog ── */
|
|
122
|
+
.ais-consent {
|
|
123
|
+
display: none;
|
|
124
|
+
margin: 1.1rem 0 0;
|
|
125
|
+
padding: 1.1rem 1.4rem;
|
|
126
|
+
background: #fffdf9;
|
|
127
|
+
border: 1px solid #e8e3db;
|
|
128
|
+
border-left: 3px solid #c8a96a;
|
|
129
|
+
border-radius: 0 4px 4px 0;
|
|
130
|
+
font-family: var(--ais-card-font);
|
|
131
|
+
animation: ais-fade-up 0.25s ease;
|
|
132
|
+
}
|
|
133
|
+
.ais-consent--visible { display: block; }
|
|
134
|
+
.ais-consent__title {
|
|
135
|
+
font-size: 0.82rem;
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
color: #111;
|
|
138
|
+
margin: 0 0 0.4rem;
|
|
139
|
+
}
|
|
140
|
+
.ais-consent__body {
|
|
141
|
+
font-size: 0.82rem;
|
|
142
|
+
color: #555;
|
|
143
|
+
line-height: 1.65;
|
|
144
|
+
margin: 0 0 0.9rem;
|
|
145
|
+
}
|
|
146
|
+
.ais-consent__body strong { color: #333; font-weight: 600; }
|
|
147
|
+
.ais-consent__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
148
|
+
|
|
149
|
+
/* ── Download progress ── */
|
|
150
|
+
.ais-progress {
|
|
151
|
+
display: none;
|
|
152
|
+
margin: 1.1rem 0 0;
|
|
153
|
+
padding: 0.9rem 1.1rem;
|
|
154
|
+
background: var(--ais-card-bg);
|
|
155
|
+
border: 1px solid var(--ais-card-border);
|
|
156
|
+
border-radius: 4px;
|
|
157
|
+
font-family: var(--ais-card-font);
|
|
158
|
+
}
|
|
159
|
+
.ais-progress--visible { display: block; }
|
|
160
|
+
.ais-progress__top {
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
justify-content: space-between;
|
|
164
|
+
margin-bottom: 0.5rem;
|
|
165
|
+
gap: 0.5rem;
|
|
166
|
+
}
|
|
167
|
+
.ais-progress__label {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 0.45rem;
|
|
171
|
+
font-size: 0.8rem;
|
|
172
|
+
color: #555;
|
|
173
|
+
}
|
|
174
|
+
.ais-progress__pct {
|
|
175
|
+
font-size: 0.78rem;
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
color: #111;
|
|
178
|
+
font-variant-numeric: tabular-nums;
|
|
179
|
+
}
|
|
180
|
+
.ais-progress__bar-bg {
|
|
181
|
+
height: 3px;
|
|
182
|
+
background: #e0dbd3;
|
|
183
|
+
border-radius: 2px;
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
}
|
|
186
|
+
.ais-progress__bar {
|
|
187
|
+
height: 100%;
|
|
188
|
+
background: var(--ais-accent);
|
|
189
|
+
border-radius: 2px;
|
|
190
|
+
width: 0%;
|
|
191
|
+
transition: width 0.25s ease;
|
|
192
|
+
}
|
|
193
|
+
.ais-progress__size {
|
|
194
|
+
margin-top: 0.45rem;
|
|
195
|
+
font-size: 0.72rem;
|
|
196
|
+
color: #666;
|
|
197
|
+
font-variant-numeric: tabular-nums;
|
|
198
|
+
}
|
|
199
|
+
.ais-progress__note { margin-top: 0.35rem; font-size: 0.72rem; color: #aaa; }
|
|
200
|
+
|
|
201
|
+
/* ── Summary card ── */
|
|
202
|
+
.ais-card {
|
|
203
|
+
margin: 1.5rem 0 0;
|
|
204
|
+
border: 1px solid var(--ais-card-border);
|
|
205
|
+
border-left: 3px solid var(--ais-accent);
|
|
206
|
+
background: var(--ais-card-bg);
|
|
207
|
+
border-radius: 0 4px 4px 0;
|
|
208
|
+
overflow: hidden;
|
|
209
|
+
font-family: var(--ais-card-font);
|
|
210
|
+
animation: ais-fade-up 0.35s ease forwards;
|
|
211
|
+
}
|
|
212
|
+
@keyframes ais-fade-up {
|
|
213
|
+
from { opacity: 0; transform: translateY(5px); }
|
|
214
|
+
to { opacity: 1; transform: translateY(0); }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.ais-card__head {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
justify-content: space-between;
|
|
221
|
+
padding: 0.8rem 1.2rem;
|
|
222
|
+
border-bottom: 1px solid var(--ais-card-border);
|
|
223
|
+
background: var(--ais-card-head-bg);
|
|
224
|
+
gap: 0.5rem;
|
|
225
|
+
flex-wrap: wrap;
|
|
226
|
+
}
|
|
227
|
+
.ais-card__title-row { display: flex; align-items: baseline; gap: 0.55rem; }
|
|
228
|
+
.ais-card__title {
|
|
229
|
+
font-size: 0.9rem;
|
|
230
|
+
font-weight: 600;
|
|
231
|
+
color: #111;
|
|
232
|
+
margin: 0;
|
|
233
|
+
font-family: var(--ais-card-font);
|
|
234
|
+
}
|
|
235
|
+
.ais-card__badge {
|
|
236
|
+
font-size: 0.63rem;
|
|
237
|
+
font-weight: 500;
|
|
238
|
+
letter-spacing: 0.08em;
|
|
239
|
+
text-transform: uppercase;
|
|
240
|
+
color: #888;
|
|
241
|
+
border: 1px solid #d5cfc7;
|
|
242
|
+
padding: 0.1rem 0.38rem;
|
|
243
|
+
border-radius: 2px;
|
|
244
|
+
background: #fff;
|
|
245
|
+
white-space: nowrap;
|
|
246
|
+
}
|
|
247
|
+
.ais-card__actions { display: flex; gap: 0.25rem; align-items: center; }
|
|
248
|
+
.ais-icon-btn {
|
|
249
|
+
background: none;
|
|
250
|
+
border: none;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
padding: 0.28rem;
|
|
253
|
+
color: #aaa;
|
|
254
|
+
border-radius: 3px;
|
|
255
|
+
line-height: 0;
|
|
256
|
+
transition: color 0.13s, background 0.13s;
|
|
257
|
+
}
|
|
258
|
+
.ais-icon-btn:hover { color: #333; background: rgba(0,0,0,0.06); }
|
|
259
|
+
.ais-icon-btn svg { width: 14px; height: 14px; display: block; }
|
|
260
|
+
|
|
261
|
+
.ais-card__body { padding: 1.1rem 1.4rem 0.9rem; }
|
|
262
|
+
.ais-card__content {
|
|
263
|
+
font-size: 0.89rem;
|
|
264
|
+
color: #2a2a2a;
|
|
265
|
+
line-height: 1.78;
|
|
266
|
+
min-height: 1.5rem;
|
|
267
|
+
}
|
|
268
|
+
.ais-card__content ul {
|
|
269
|
+
list-style: none;
|
|
270
|
+
padding: 0; margin: 0;
|
|
271
|
+
display: flex;
|
|
272
|
+
flex-direction: column;
|
|
273
|
+
gap: 0.4rem;
|
|
274
|
+
}
|
|
275
|
+
.ais-card__content li {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: flex-start;
|
|
278
|
+
gap: 0.6rem;
|
|
279
|
+
padding: 0.5rem 0.75rem;
|
|
280
|
+
background: #fff;
|
|
281
|
+
border: 1px solid #ece7df;
|
|
282
|
+
border-radius: 3px;
|
|
283
|
+
font-size: 0.875rem;
|
|
284
|
+
line-height: 1.65;
|
|
285
|
+
color: #333;
|
|
286
|
+
}
|
|
287
|
+
.ais-card__content li::before {
|
|
288
|
+
content: '›';
|
|
289
|
+
color: #999;
|
|
290
|
+
font-size: 1.05rem;
|
|
291
|
+
line-height: 1.45;
|
|
292
|
+
flex-shrink: 0;
|
|
293
|
+
}
|
|
294
|
+
.ais-card__content li span {
|
|
295
|
+
flex: 1;
|
|
296
|
+
}
|
|
297
|
+
.ais-card__content p { margin: 0 0 0.6rem; }
|
|
298
|
+
.ais-card__content p:last-child { margin: 0; }
|
|
299
|
+
.ais-card__content strong { font-weight: 600; color: #111; }
|
|
300
|
+
|
|
301
|
+
/* Streaming cursor */
|
|
302
|
+
.ais-cursor {
|
|
303
|
+
display: inline-block;
|
|
304
|
+
width: 2px; height: 0.85em;
|
|
305
|
+
background: #666;
|
|
306
|
+
vertical-align: text-bottom;
|
|
307
|
+
margin-left: 2px;
|
|
308
|
+
animation: ais-blink 0.85s step-end infinite;
|
|
309
|
+
}
|
|
310
|
+
@keyframes ais-blink { 50% { opacity: 0; } }
|
|
311
|
+
|
|
312
|
+
.ais-card__foot {
|
|
313
|
+
display: flex;
|
|
314
|
+
align-items: center;
|
|
315
|
+
justify-content: space-between;
|
|
316
|
+
padding: 0.55rem 1.4rem;
|
|
317
|
+
border-top: 1px solid #ece7df;
|
|
318
|
+
gap: 0.5rem;
|
|
319
|
+
flex-wrap: wrap;
|
|
320
|
+
}
|
|
321
|
+
.ais-card__note {
|
|
322
|
+
font-size: 0.68rem;
|
|
323
|
+
color: #bbb;
|
|
324
|
+
letter-spacing: 0.03em;
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 0.35rem;
|
|
328
|
+
}
|
|
329
|
+
.ais-card__note-dot {
|
|
330
|
+
width: 3px; height: 3px;
|
|
331
|
+
background: #ccc; border-radius: 50%; flex-shrink: 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* ── Error ── */
|
|
335
|
+
.ais-error {
|
|
336
|
+
margin: 1.1rem 0 0;
|
|
337
|
+
padding: 0.9rem 1.1rem;
|
|
338
|
+
background: #fff8f7;
|
|
339
|
+
border: 1px solid #f0cac7;
|
|
340
|
+
border-left: 3px solid #b94040;
|
|
341
|
+
border-radius: 0 4px 4px 0;
|
|
342
|
+
font-family: var(--ais-card-font);
|
|
343
|
+
font-size: 0.82rem;
|
|
344
|
+
color: #7a2a2a;
|
|
345
|
+
display: flex;
|
|
346
|
+
align-items: flex-start;
|
|
347
|
+
justify-content: space-between;
|
|
348
|
+
gap: 0.75rem;
|
|
349
|
+
animation: ais-fade-up 0.25s ease;
|
|
350
|
+
}
|
|
351
|
+
.ais-error a { color: inherit; }
|
|
352
|
+
.ais-error__retry {
|
|
353
|
+
font-family: var(--ais-btn-font);
|
|
354
|
+
font-size: 0.76rem;
|
|
355
|
+
font-weight: 500;
|
|
356
|
+
color: #7a2a2a;
|
|
357
|
+
background: none;
|
|
358
|
+
border: 1px solid #f0cac7;
|
|
359
|
+
border-radius: 3px;
|
|
360
|
+
padding: 0.28rem 0.65rem;
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
white-space: nowrap;
|
|
363
|
+
flex-shrink: 0;
|
|
364
|
+
transition: background 0.13s;
|
|
365
|
+
}
|
|
366
|
+
.ais-error__retry:hover { background: rgba(0,0,0,0.04); }
|
|
367
|
+
`}var i=`preferences`,a=`userPrefs`;function o(e){return new Promise((t,n)=>{let r=indexedDB.open(e,1);r.onupgradeneeded=e=>{e.target.result.createObjectStore(i)},r.onsuccess=e=>t(e.target.result),r.onerror=()=>n(r.error)})}async function s(e){try{let t=(await o(e)).transaction(i,`readonly`).objectStore(i).get(a);return await new Promise(e=>{t.onsuccess=()=>e(t.result??null),t.onerror=()=>e(null)})}catch{return null}}async function c(e,t){try{(await o(e)).transaction(i,`readwrite`).objectStore(i).put(t,a)}catch{}}var l=3.5,u=8e3;function d(){let e=globalThis.Summarizer;if(!e)throw Error(`Summarizer API is not available in this browser.`);return e}function f(){return`Summarizer`in globalThis}async function p(e={}){return d().availability({...e,expectedInputLanguages:x(e.expectedInputLanguages),expectedContextLanguages:x(e.expectedContextLanguages)})}var m=null,h=null;function g(e){return JSON.stringify({type:e.type,length:e.length,format:e.format,preference:e.preference,sharedContext:e.sharedContext,outputLanguage:e.outputLanguage,expectedInputLanguages:e.expectedInputLanguages,expectedContextLanguages:e.expectedContextLanguages})}async function _(e){let t=g(e);return m&&h===t?m:(m&&=((await m).destroy(),null),h=t,m=v(e),m)}async function v(e){return await p(e)!==`available`&&S(),d().create({type:e.type,length:e.length,format:e.format??`markdown`,preference:e.preference,sharedContext:e.sharedContext,outputLanguage:e.outputLanguage,expectedInputLanguages:x(e.expectedInputLanguages),expectedContextLanguages:x(e.expectedContextLanguages),signal:e.signal,monitor:e.onDownloadProgress?t=>{t.addEventListener(`downloadprogress`,t=>{let n=t,r=typeof n.loaded==`number`?n.loaded:0,i=typeof n.total==`number`?n.total:0,a=!!n.lengthComputable,o=a&&i>0?Math.round(r/i*100):r<=1?Math.round(r*100):0;e.onDownloadProgress?.({pct:o,loaded:r,total:i,lengthComputable:a})})}:void 0})}function y(e,t){let n=t?Math.floor(t*l):u;if(e.length<=n)return e;let r=e.lastIndexOf(`
|
|
368
|
+
|
|
369
|
+
`,n),i=r>n*.6?r:n;return e.slice(0,i)+`
|
|
370
|
+
|
|
371
|
+
[… content truncated for summary]`}async function b(e){let{summarizer:t,context:n,signal:r,onChunk:i}=e,a=new TextDecoder,o=e=>{if(typeof e==`string`)return e;if(e instanceof Uint8Array)return a.decode(e);if(e instanceof ArrayBuffer)return a.decode(new Uint8Array(e));if(e&&typeof e==`object`){let t=e.text;if(typeof t==`string`)return t;let n=e.output;if(typeof n==`string`)return n;let r=e.outputText;if(typeof r==`string`)return r}return null},s=await C(e.text,t,n,r),c=async e=>{let a=t.summarizeStreaming(e,{context:n,signal:r}),s=``,c=!1;for await(let e of w(a)){if(r.aborted)break;let t=o(e);t&&(t.startsWith(s)?s=t:s+=t,c=!0,i(s))}if(!c&&t.summarize){let a=await t.summarize(e,{context:n,signal:r});return a&&i(a),a}return s};try{return await c(s)}catch(e){if(e.name===`QuotaExceededError`){let e=t.inputQuota?Math.floor(t.inputQuota*.7):void 0;return s=y(s,e),await c(s)}throw e}}function x(e){return e&&e.length>0?e:void 0}function S(){let e=globalThis.navigator;if(e?.userActivation&&!e.userActivation.isActive)throw Error(`Summarizer.create() must be called from a user gesture.`)}async function C(e,t,n,r){let i=t.inputQuota;if(!i)return y(e,void 0);if(t.measureInputUsage)try{if(await t.measureInputUsage(e,{context:n,signal:r})<=i)return e}catch{}return y(e,i)}async function*w(e){if(e&&typeof e[Symbol.asyncIterator]==`function`){yield*e;return}if(e&&typeof e.getReader==`function`){let t=e.getReader();try{for(;;){let{value:e,done:n}=await t.read();if(n)break;yield e}}finally{t.releaseLock()}return}throw Error(`Unsupported summary stream type.`)}function T(e){return e.replace(/[&<>"']/g,e=>{switch(e){case`&`:return`&`;case`<`:return`<`;case`>`:return`>`;case`"`:return`"`;case`'`:return`'`;default:return e}})}function E(e){let t=e.trim().split(`
|
|
372
|
+
`),n=``,r=!1;for(let e of t){let t=e.trim();if(!t){r&&=(n+=`</ul>`,!1);continue}let i=/^[-*•]\s+/.test(t),a=/^\d+\.\s+/.test(t);if(i||a){r||=(n+=`<ul>`,!0);let e=t.replace(/^([-*•]|\d+\.)\s+/,``);n+=`<li><span>${D(T(e))}</span></li>`}else r&&=(n+=`</ul>`,!1),n+=`<p>${D(T(t))}</p>`}return r&&(n+=`</ul>`),n}function D(e){return e.replace(/\*\*(.*?)\*\*/g,`<strong>$1</strong>`).replace(/__(.*?)__/g,`<strong>$1</strong>`).replace(/\*(.*?)\*/g,`<em>$1</em>`).replace(/_(.*?)_/g,`<em>$1</em>`)}function O(e,t,n){let r=document.createElement(e);return r.className=t,n!==void 0&&(r.innerHTML=n),r}function k(e){for(let t of e.split(`,`).map(e=>e.trim())){let e=document.querySelector(t);if(e)return e}return null}function A(e){let t=(k(e)??document.querySelector(`article`)??document.querySelector(`main`)??document.body).cloneNode(!0);return t.querySelectorAll(`script, style, noscript, iframe, .ads, .advertisement, nav, footer, header, aside:not(.article-content), .social-share, .comments, .related-posts`).forEach(e=>e.remove()),(t.innerText??``).replace(/[\t ]+/g,` `).replace(/\n\s*\n\s*\n+/g,`
|
|
373
|
+
|
|
374
|
+
`).trim()}function j(e,t){return t===`plain-text`?T(e).replace(/\n/g,`<br>`):E(e)}var M=`<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="width: 1.2em; height: 1.2em; vertical-align: middle;">
|
|
375
|
+
<path d="M7 2c.4 3.2 2 5.2 5.6 5.6-3.6.4-5.2 2-5.6 5.6-.4-3.6-2.4-5.6-6-6 4.4-.4 6-2 6.4-5.2z" fill="currentColor"/>
|
|
376
|
+
<path d="M13.5 1.5c.2 1.2.8 1.8 2 2-1.2.2-1.8.8-2 2-.2-1.2-.8-1.8-2-2 1.2-.2 1.8-.8 2-2z" fill="currentColor"/>
|
|
377
|
+
</svg>`,N=`<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
378
|
+
<rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" stroke-width="1.3"/>
|
|
379
|
+
<path d="M3 11H2.5A1.5 1.5 0 0 1 1 9.5v-7A1.5 1.5 0 0 1 2.5 1h7A1.5 1.5 0 0 1 11 2.5V3"
|
|
380
|
+
stroke="currentColor" stroke-width="1.3"/>
|
|
381
|
+
</svg>`,P=`<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
382
|
+
<path d="M3 3l10 10M13 3L3 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
383
|
+
</svg>`,F=`<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
384
|
+
<rect x="3" y="3" width="10" height="10" rx="1.5" fill="currentColor"/>
|
|
385
|
+
</svg>`,I=`<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
386
|
+
<path d="M2.5 9.5v2a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-2M8.5 2v7M5.5 6l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
387
|
+
</svg>`,L={"key-points":`Key Points`,tldr:`TL;DR`,teaser:`Teaser`,headline:`Headline`};function R(){let e=document.createElement(`div`);return e.className=`ais-root`,e}function z(e){let t=O(`div`,`ais-trigger`),n=O(`button`,`ais-btn`);return n.setAttribute(`aria-label`,`Summarize this article with on-device AI`),n.innerHTML=`${M} ${e}`,t.appendChild(n),{wrap:t,btn:n}}function B(e,t){e.disabled=!0,e.innerHTML=`<span class="ais-spinner" aria-hidden="true"></span> ${t}`}function V(e,t=`Regenerate`){e.disabled=!1,e.innerHTML=`${M} ${t}`}function H(e,t){let n=O(`div`,`ais-pills`);n.setAttribute(`role`,`group`),n.setAttribute(`aria-label`,`Summary style`);let r=O(`span`,`ais-pills__label`);r.textContent=`Style`,n.appendChild(r);let i=[];return Object.entries(L).forEach(([r,a])=>{let o=O(`button`,`ais-pill${r===e?` ais-pill--active`:``}`);o.textContent=a,o.dataset.type=r,o.addEventListener(`click`,()=>{o.classList.contains(`ais-pill--active`)||o.disabled||(n.querySelectorAll(`.ais-pill`).forEach(e=>e.classList.remove(`ais-pill--active`)),o.classList.add(`ais-pill--active`),t(r))}),n.appendChild(o),i.push(o)}),{wrap:n,setDisabled:e=>{i.forEach(t=>{t.disabled=e,t.setAttribute(`aria-disabled`,String(e))})}}}function U(e){e.classList.add(`ais-pills--visible`)}function W(e,t){let n=O(`div`,`ais-consent`);n.setAttribute(`role`,`dialog`),n.setAttribute(`aria-modal`,`false`),n.setAttribute(`aria-labelledby`,`ais-consent-title`),n.innerHTML=`
|
|
388
|
+
<div class="ais-consent__title" id="ais-consent-title">⬇ Download AI model to continue</div>
|
|
389
|
+
<div class="ais-consent__body">
|
|
390
|
+
Summaries run <strong>entirely on your device</strong> using Gemini Nano — no content
|
|
391
|
+
leaves your browser. Browser needs to download this model once
|
|
392
|
+
(<strong>~1–2 GB</strong>), then it's cached for all future use.
|
|
393
|
+
</div>
|
|
394
|
+
<div class="ais-consent__actions"></div>
|
|
395
|
+
`;let r=n.querySelector(`.ais-consent__actions`),i=O(`button`,`ais-btn`);i.innerHTML=`${I} Download & summarize`,i.addEventListener(`click`,e);let a=O(`button`,`ais-btn ais-btn--ghost`);return a.textContent=`Not now`,a.addEventListener(`click`,t),r.appendChild(i),r.appendChild(a),n}function G(e){e.classList.add(`ais-consent--visible`)}function K(e){e.classList.remove(`ais-consent--visible`)}function q(){let e=O(`div`,`ais-progress`);e.setAttribute(`role`,`status`),e.setAttribute(`aria-live`,`polite`),e.innerHTML=`
|
|
396
|
+
<div class="ais-progress__top">
|
|
397
|
+
<div class="ais-progress__label">
|
|
398
|
+
<span class="ais-spinner ais-spinner--dark" aria-hidden="true"></span>
|
|
399
|
+
Downloading Gemini Nano…
|
|
400
|
+
</div>
|
|
401
|
+
<span class="ais-progress__pct">0%</span>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="ais-progress__bar-bg">
|
|
404
|
+
<div class="ais-progress__bar"></div>
|
|
405
|
+
</div>
|
|
406
|
+
<div class="ais-progress__size">Exact size hidden by the browser</div>
|
|
407
|
+
<div class="ais-progress__note">One-time download · cached in Browser for future use</div>
|
|
408
|
+
`;let t=e.querySelector(`.ais-progress__bar`),n=e.querySelector(`.ais-progress__pct`),r=e.querySelector(`.ais-progress__size`),i=e=>{if(!Number.isFinite(e)||e<=0)return`0 B`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;let i=n>=100?0:n>=10?1:2;return`${n.toFixed(i)} ${t[r]}`};return{wrap:e,setProgress:a=>{let o=Math.max(0,Math.min(100,Math.round(a.pct)));t.style.width=`${o}%`,n.textContent=`${o}%`,a.lengthComputable&&a.total>1?r.textContent=`${i(Math.min(a.loaded,a.total))} / ${i(a.total)}`:a.loaded>1?r.textContent=`${i(a.loaded)} downloaded`:r.textContent=`Exact size hidden by the browser`,e.classList.add(`ais-progress--visible`)}}}function J(e){e.classList.remove(`ais-progress--visible`)}function Y(e,t,n){let r=O(`div`,`ais-card`);r.id=`ais-card`,r.setAttribute(`role`,`region`),r.setAttribute(`aria-label`,`AI-generated article summary`);let i=O(`div`,`ais-card__head`),a=O(`div`,`ais-card__title-row`),o=O(`h2`,`ais-card__title`);o.textContent=`AI Summary`;let s=O(`span`,`ais-card__badge`);s.textContent=`${L[e]} · ${t}`,a.appendChild(o),a.appendChild(s);let c=O(`div`,`ais-card__actions`),l=O(`button`,`ais-icon-btn`);l.setAttribute(`title`,`Copy summary`),l.setAttribute(`aria-label`,`Copy summary to clipboard`),l.innerHTML=N;let u=O(`button`,`ais-icon-btn ais-stop-btn`);u.setAttribute(`title`,`Stop generating`),u.setAttribute(`aria-label`,`Stop generating summary`),u.innerHTML=F;let d=O(`button`,`ais-icon-btn`);d.setAttribute(`title`,`Dismiss`),d.setAttribute(`aria-label`,`Dismiss summary`),d.innerHTML=P,c.appendChild(u),c.appendChild(l),c.appendChild(d),i.appendChild(a),i.appendChild(c);let f=O(`div`,`ais-card__body`),p=O(`div`,`ais-card__content`);p.innerHTML=`<span class="ais-cursor" aria-hidden="true"></span>`,f.appendChild(p);let m=O(`div`,`ais-card__foot`),h=O(`span`,`ais-card__note`);h.innerHTML=`<span class="ais-card__note-dot" aria-hidden="true"></span>On-device · Gemini Nano · Browser Summarizer API`,m.appendChild(h),r.appendChild(i),r.appendChild(f),r.appendChild(m);let g=``;l.addEventListener(`click`,async()=>{if(g)try{await navigator.clipboard.writeText(g),l.innerHTML=`✓`,l.style.color=`#4a9a5c`,setTimeout(()=>{l.innerHTML=N,l.style.color=``},2e3)}catch{}}),d.addEventListener(`click`,()=>r.remove());let _=e=>{let t=p.querySelector(`.ais-cursor`);e&&!t&&p.insertAdjacentHTML(`beforeend`,`<span class="ais-cursor" aria-hidden="true"></span>`),e||t?.remove()};return{card:r,contentEl:p,stopBtn:u,setCursor:_,setContent:e=>{g=e,p.innerHTML=j(e,n),_(!1)},streamChunk:e=>{g=e,p.innerHTML=j(e,n),_(!1)},finalize:()=>{_(!1),u.hidden=!0}}}function X(e,t){let n=O(`div`,`ais-error`);n.id=`ais-error`,n.setAttribute(`role`,`alert`);let r=O(`span`,``);if(r.innerHTML=`⚠ ${e}`,n.appendChild(r),t){let e=O(`button`,`ais-error__retry`);e.textContent=`Retry`,e.addEventListener(`click`,()=>{n.remove(),t()}),n.appendChild(e)}return n}var Z=`.gh-content, .post-content, .article-body, article .content, .entry-content`,ee=`.gh-article-header, .post-header, .article-header, header.article`,te=`This is a news article or blog post. Avoid jargon, use correct grammar, focus on clarity, and ensure the reader can grasp the core ideas quickly.`,ne=new Set([`en`,`es`,`ja`]);function re(e){let t=e?.trim().toLowerCase();if(!t)return`en`;let n=t.split(`-`)[0];return ne.has(n)?n:`en`}var Q=class{opts;abortController=null;root=null;mounted=!1;isSummarizing=!1;constructor(e={}){this.opts={contentSelector:e.contentSelector??Z,anchorSelector:e.anchorSelector??ee,defaultType:e.defaultType??`key-points`,defaultLength:e.defaultLength??`medium`,format:e.format??`markdown`,preference:e.preference??`auto`,sharedContext:e.sharedContext??te,outputLanguage:re(e.outputLanguage),expectedInputLanguages:e.expectedInputLanguages??[],expectedContextLanguages:e.expectedContextLanguages??[],buttonLabel:e.buttonLabel??`Summarize with AI`,showTypeSwitcher:e.showTypeSwitcher??!0,requireDownloadConsent:e.requireDownloadConsent??!0,dbName:e.dbName??`ai-summarizer`,theme:e.theme??{},onSummary:e.onSummary,onError:e.onError,onUnsupported:e.onUnsupported}}async mount(){if(this.mounted)return;if(this.mounted=!0,!f()){this.opts.onUnsupported?.();return}n(this.opts.theme);let e={type:(await s(this.opts.dbName))?.type??this.opts.defaultType},t=k(this.opts.anchorSelector),r=k(this.opts.contentSelector)??document.querySelector(`article`)??document.querySelector(`main`);if(!r)return;this.root=R();let i=this.buildUI(e);this.root.appendChild(i.triggerWrap),i.pillsEl&&this.root.appendChild(i.pillsEl),this.root.appendChild(i.consentEl),this.root.appendChild(i.progressEl),i.triggerBtn.addEventListener(`click`,async()=>{this.clearSummary(),K(i.consentEl),await this.preflightAndRun(e,i,!1)}),t?t.insertAdjacentElement(`afterend`,this.root):r.insertAdjacentElement(`beforebegin`,this.root),this.warmup(e).catch(()=>{})}async warmup(e){if(!f())return;let{expectedInputLanguages:t,expectedContextLanguages:n}=this.getLanguageHints();try{await _({type:e?.type??this.opts.defaultType,length:this.opts.defaultLength,format:this.opts.format,preference:this.opts.preference,sharedContext:this.opts.sharedContext,outputLanguage:this.opts.outputLanguage,expectedInputLanguages:t,expectedContextLanguages:n})}catch{}}buildUI(e){let{wrap:t,btn:n}=z(this.opts.buttonLabel),{wrap:r,setProgress:i}=q(),a={},o=null,s=()=>{};if(this.opts.showTypeSwitcher){let{wrap:t,setDisabled:n}=H(e.type,async t=>{this.isSummarizing||(e.type=t,await c(this.opts.dbName,e),this.clearSummary(),await this.preflightAndRun(e,a,!1))});o=t,s=n}let l=W(async()=>{K(l),await this.preflightAndRun(e,a,!0)},()=>K(l));return a.triggerWrap=t,a.triggerBtn=n,a.pillsEl=o,a.setPillsDisabled=s,a.progressEl=r,a.progressSetProgress=i,a.consentEl=l,a}setSummarizingState(e,t){this.isSummarizing=t,e.setPillsDisabled(t)}async summarize(){this.root||await this.mount(),this.root?.querySelector(`.ais-btn`)?.click()}destroy(){this.abortController?.abort(),this.root?.remove(),this.root=null,this.mounted=!1}clearSummary(){this.root?.querySelector(`#ais-card`)?.remove(),this.root?.querySelector(`#ais-error`)?.remove()}showError(e,t){this.clearSummary();let n=X(e,t);this.root?.appendChild(n)}getLanguageHints(){return{expectedInputLanguages:this.opts.expectedInputLanguages?.length?this.opts.expectedInputLanguages:void 0,expectedContextLanguages:this.opts.expectedContextLanguages?.length?this.opts.expectedContextLanguages:void 0}}async runSummarizer(e,t,n,r){let{triggerBtn:i,pillsEl:a,progressEl:o,progressSetProgress:s,consentEl:l}=t,{expectedInputLanguages:u,expectedContextLanguages:d}=this.getLanguageHints();this.abortController?.abort(),this.abortController=new AbortController;let f=this.abortController.signal;this.setSummarizingState(t,!0),B(i,`Preparing…`),this.clearSummary(),K(l),J(o);let p=n===`downloadable`||n===`downloading`,m=null,h=null;try{p||(h=setTimeout(()=>{f.aborted||B(i,`Loading model…`)},1200)),m=await _({type:e.type,length:this.opts.defaultLength,format:this.opts.format,preference:this.opts.preference,sharedContext:this.opts.sharedContext,outputLanguage:this.opts.outputLanguage,expectedInputLanguages:u,expectedContextLanguages:d,signal:f,onDownloadProgress:p?e=>{s(e),B(i,`Downloading… ${e.pct}%`)}:void 0}),h!==null&&(clearTimeout(h),h=null),J(o),B(i,`Summarizing…`);let n=A(this.opts.contentSelector);if(!n||n.length<80)throw Error(`Not enough article text found to summarize.`);let r=Y(e.type,this.opts.defaultLength,this.opts.format);this.root?.appendChild(r.card),r.card.scrollIntoView({behavior:`smooth`,block:`nearest`}),r.stopBtn.addEventListener(`click`,()=>{this.abortController?.abort(),r.finalize(),this.setSummarizingState(t,!1),V(i,this.opts.buttonLabel)});let l=document.title??``,g=await b({summarizer:m,text:n,context:`Article title: "${l}". Provide a clear, accurate summary.`,signal:f,onChunk:e=>r.streamChunk(e)});r.finalize(),a&&U(a),V(i,`Regenerate`),g&&this.opts.onSummary?.(g,e.type),await c(this.opts.dbName,e)}catch(n){if(n.name===`AbortError`){V(i,this.opts.buttonLabel);return}J(o);let a=n instanceof Error?n:Error(String(n));this.opts.onError?.(a),this.showError(`Summary failed: ${a.message||`Unknown error.`}`,()=>this.preflightAndRun(e,t,r)),V(i,this.opts.buttonLabel)}finally{h!==null&&clearTimeout(h),this.setSummarizingState(t,!1)}}async preflightAndRun(e,t,n){let{expectedInputLanguages:r,expectedContextLanguages:i}=this.getLanguageHints(),a=null;try{a=await p({type:e.type,length:this.opts.defaultLength,format:this.opts.format,preference:this.opts.preference,sharedContext:this.opts.sharedContext,outputLanguage:this.opts.outputLanguage,expectedInputLanguages:r,expectedContextLanguages:i})}catch(e){let t=e instanceof Error?e:Error(String(e));this.showError(`Failed to check model availability: ${t.message}`);return}if(!a||a===`unavailable`){this.showError(`Gemini Nano is not available on this device. See <a href="https://developer.chrome.com/docs/ai/summarizer-api#hardware_requirements" target="_blank" rel="noopener">hardware requirements ↗</a>.`);return}if((a===`downloadable`||a===`downloading`)&&this.opts.requireDownloadConsent&&!n){G(t.consentEl);return}await this.runSummarizer(e,t,a,n)}};function $(){let e=document.querySelector(`[data-ai-summarizer]`);if(!e)return;let t={},n=e.getAttribute(`data-ai-summarizer`);if(n&&n!==``&&n!==`true`)try{t=JSON.parse(n)}catch{}new Q(t).mount()}typeof document<`u`&&(document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,$):$()),e.AISummarizer=Q});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "browser-ai-summarizer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "tsc -p tsconfig.build.json && vite build",
|
|
8
|
+
"preview": "vite preview"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/browser-ai-summarizer.cjs.cjs",
|
|
11
|
+
"module": "dist/browser-ai-summarizer.esm.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/browser-ai-summarizer.esm.js",
|
|
16
|
+
"require": "./dist/browser-ai-summarizer.cjs.cjs",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "~5.9.3",
|
|
25
|
+
"vite": "^8.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|