dewee-webchat 0.4.2 → 0.5.0-beta.1
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 +64 -7
- package/astro/DeweeChat.astro +2 -2
- package/dist/{chunk-UGHJZAKL.js → chunk-M6J3IXEI.js} +351 -50
- package/dist/{chunk-SAUDVPFH.js → chunk-WHHQ6FKK.js} +1 -1
- package/dist/dewee-webchat.js +34 -27
- package/dist/{handler-DunrbnSD.d.ts → handler-B-c6tDr7.d.ts} +50 -5
- package/dist/index.d.ts +33 -5
- package/dist/index.js +2 -2
- package/dist/page-context-H3ZROFAV.js +137 -0
- package/dist/{protocol-CLbGE9Ns.d.ts → protocol-BbjGSfZa.d.ts} +21 -2
- package/dist/react.d.ts +3 -1
- package/dist/react.js +9 -7
- package/dist/server-identity.d.ts +2 -2
- package/dist/server.d.ts +7 -4
- package/dist/server.js +631 -61
- package/dist/vue.d.ts +60 -13
- package/dist/vue.js +10 -5
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
isLinkErrorCode,
|
|
10
10
|
parseJobAccessLaunch,
|
|
11
11
|
stripPageBlock
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WHHQ6FKK.js";
|
|
13
13
|
|
|
14
14
|
// src/actions/directives.ts
|
|
15
15
|
var DIRECTIVE_LANG = "dewee-action";
|
|
@@ -79,14 +79,44 @@ function stripDirectives(content) {
|
|
|
79
79
|
return removeBlocks(lines, findBlocks(lines, DIRECTIVE_OPEN_RE));
|
|
80
80
|
}
|
|
81
81
|
function isActionResultMessage(content) {
|
|
82
|
-
return
|
|
82
|
+
return /^\s*```dewee-action-result(?:\s|$)/.test(content);
|
|
83
|
+
}
|
|
84
|
+
function compactResultBodies(content) {
|
|
85
|
+
const open = /```dewee-action-result\s+/g;
|
|
86
|
+
const bodies = [];
|
|
87
|
+
let match;
|
|
88
|
+
while (match = open.exec(content)) {
|
|
89
|
+
const start = open.lastIndex;
|
|
90
|
+
if (content[start] !== "{") continue;
|
|
91
|
+
let depth = 0, quoted = false, escaped = false;
|
|
92
|
+
for (let i = start; i < content.length; i++) {
|
|
93
|
+
const c = content[i];
|
|
94
|
+
if (quoted) {
|
|
95
|
+
if (escaped) escaped = false;
|
|
96
|
+
else if (c === "\\") escaped = true;
|
|
97
|
+
else if (c === '"') quoted = false;
|
|
98
|
+
} else if (c === '"') quoted = true;
|
|
99
|
+
else if (c === "{") depth++;
|
|
100
|
+
else if (c === "}" && --depth === 0) {
|
|
101
|
+
const close = /^\s*```/.exec(content.slice(i + 1));
|
|
102
|
+
if (close) {
|
|
103
|
+
bodies.push(content.slice(start, i + 1));
|
|
104
|
+
open.lastIndex = i + 1 + close[0].length;
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return bodies;
|
|
83
111
|
}
|
|
84
112
|
function parseActionResults(content) {
|
|
85
113
|
const lines = content.replace(/\r\n?/g, "\n").split("\n");
|
|
86
114
|
const out = [];
|
|
87
|
-
|
|
115
|
+
const blocks = findBlocks(lines, RESULT_OPEN_RE);
|
|
116
|
+
const bodies = blocks.length ? blocks.map((b) => b.body.join("\n")) : compactResultBodies(content);
|
|
117
|
+
for (const body of bodies) {
|
|
88
118
|
try {
|
|
89
|
-
const r = JSON.parse(
|
|
119
|
+
const r = JSON.parse(body);
|
|
90
120
|
if (r && typeof r.id === "string" && ["ok", "denied", "error"].includes(r.status)) out.push(r);
|
|
91
121
|
} catch {
|
|
92
122
|
}
|
|
@@ -584,6 +614,8 @@ var ApiClient = class {
|
|
|
584
614
|
session = null;
|
|
585
615
|
config = null;
|
|
586
616
|
sessionPromise = null;
|
|
617
|
+
supersededSessions = /* @__PURE__ */ new Map();
|
|
618
|
+
sessionGeneration = 0;
|
|
587
619
|
url(action, qs) {
|
|
588
620
|
const base = this.o.endpoint.replace(/\/+$/, "");
|
|
589
621
|
const q = qs ? "?" + new URLSearchParams(qs).toString() : "";
|
|
@@ -618,7 +650,7 @@ var ApiClient = class {
|
|
|
618
650
|
h[HEADER_GATEWAY_TOKEN] = this.o.gatewayToken;
|
|
619
651
|
}
|
|
620
652
|
if (auth) {
|
|
621
|
-
const s =
|
|
653
|
+
const s = await this.ensureSession();
|
|
622
654
|
h.authorization = `Bearer ${s.token}`;
|
|
623
655
|
}
|
|
624
656
|
return h;
|
|
@@ -632,16 +664,24 @@ var ApiClient = class {
|
|
|
632
664
|
}
|
|
633
665
|
return body;
|
|
634
666
|
}
|
|
635
|
-
|
|
667
|
+
/** Obtain a short-lived, URL-bound image capability without exposing the visitor bearer in src. */
|
|
668
|
+
async imageUrl(source, signal) {
|
|
669
|
+
const res = await this.request(this.url("image-ticket"), { method: "POST", headers: await this.headers(true), body: JSON.stringify({ url: source }), redirect: "error", signal });
|
|
670
|
+
const data = await this.parse(res);
|
|
671
|
+
if (typeof data.ticket !== "string" || !/^[A-Za-z0-9_-]{1,8192}$/.test(data.ticket)) throw new Error("Invalid image ticket");
|
|
672
|
+
return this.url("image", { ticket: data.ticket });
|
|
673
|
+
}
|
|
674
|
+
async getConfig(force = false, probePageContext = false) {
|
|
636
675
|
if (this.config && !force) return this.config;
|
|
637
|
-
this.config = await this.parse(await this.request(this.url("config"), { headers: await this.headers() }));
|
|
676
|
+
this.config = await this.parse(await this.request(this.url("config", probePageContext ? { pageContext: "1" } : void 0), { headers: await this.headers() }));
|
|
638
677
|
return this.config;
|
|
639
678
|
}
|
|
640
679
|
/** When each signed-in session should be refreshed, on this browser's clock. */
|
|
641
680
|
refreshAt = /* @__PURE__ */ new WeakMap();
|
|
642
681
|
/**
|
|
643
682
|
* Session reuse. A signed-in session follows its schedule (see `scheduleRefresh`). An anonymous
|
|
644
|
-
* token is refreshed once less than 10% of its lifetime (at most 24 h, at least 30 s
|
|
683
|
+
* token is refreshed once less than 10% of its lifetime (at most 24 h, at least 30 s, and no more
|
|
684
|
+
* than half the lifetime) remains; a
|
|
645
685
|
* stored token from before `obtainedAt` existed uses the 24 h margin. When the backend reports the
|
|
646
686
|
* lifetime (`expiresIn`), it is counted from receipt, so a skewed browser clock does not matter.
|
|
647
687
|
*/
|
|
@@ -650,7 +690,7 @@ var ApiClient = class {
|
|
|
650
690
|
const reported = typeof s.expiresIn === "number" && s.expiresIn >= 0 && s.obtainedAt;
|
|
651
691
|
const end = reported ? s.obtainedAt + s.expiresIn : s.expiresAt;
|
|
652
692
|
const lifetime = reported ? s.expiresIn : s.obtainedAt ? s.expiresAt - s.obtainedAt : Infinity;
|
|
653
|
-
const margin = Math.min(864e5, Math.max(3e4, lifetime * 0.1));
|
|
693
|
+
const margin = Math.min(864e5, lifetime / 2, Math.max(3e4, lifetime * 0.1));
|
|
654
694
|
return end - Date.now() > margin;
|
|
655
695
|
}
|
|
656
696
|
/**
|
|
@@ -668,7 +708,11 @@ var ApiClient = class {
|
|
|
668
708
|
if (!saved) return null;
|
|
669
709
|
try {
|
|
670
710
|
const s = JSON.parse(saved);
|
|
671
|
-
|
|
711
|
+
if (s.verified) {
|
|
712
|
+
store.del(this.storageKey);
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
return s;
|
|
672
716
|
} catch {
|
|
673
717
|
return null;
|
|
674
718
|
}
|
|
@@ -679,21 +723,35 @@ var ApiClient = class {
|
|
|
679
723
|
}
|
|
680
724
|
/** Returns a valid visitor session, creating or refreshing it when needed. */
|
|
681
725
|
ensureSession() {
|
|
726
|
+
if (this.sessionPromise) return this.sessionPromise;
|
|
682
727
|
if (this.session && this.fresh(this.session)) return Promise.resolve(this.session);
|
|
683
|
-
|
|
728
|
+
const generation = this.sessionGeneration;
|
|
729
|
+
const promise = this.loadSession(generation).catch((error) => {
|
|
730
|
+
if (generation !== this.sessionGeneration) return this.supersededSessions.get(generation);
|
|
731
|
+
throw error;
|
|
732
|
+
}).finally(() => {
|
|
733
|
+
if (this.sessionPromise === promise) this.sessionPromise = null;
|
|
734
|
+
this.supersededSessions.delete(generation);
|
|
735
|
+
});
|
|
736
|
+
return this.sessionPromise = promise;
|
|
684
737
|
}
|
|
685
|
-
async loadSession() {
|
|
738
|
+
async loadSession(generation) {
|
|
686
739
|
if (!this.session) {
|
|
687
740
|
const s = this.stored();
|
|
688
741
|
if (s) {
|
|
689
|
-
if (this.fresh(s)
|
|
742
|
+
if (this.fresh(s)) {
|
|
743
|
+
const cfg = await this.getConfig();
|
|
744
|
+
if (generation !== this.sessionGeneration) return this.supersededSessions.get(generation);
|
|
745
|
+
if (!cfg.identity) return this.session = s;
|
|
746
|
+
}
|
|
690
747
|
this.session = s;
|
|
691
748
|
}
|
|
692
749
|
}
|
|
693
|
-
return this.createSession();
|
|
750
|
+
return this.createSession(generation);
|
|
694
751
|
}
|
|
695
|
-
async createSession() {
|
|
752
|
+
async createSession(generation) {
|
|
696
753
|
const cfg = await this.getConfig();
|
|
754
|
+
if (generation !== this.sessionGeneration) return this.supersededSessions.get(generation);
|
|
697
755
|
const body = {};
|
|
698
756
|
const previous = this.session && !this.session.verified ? this.session : this.stored();
|
|
699
757
|
if (previous?.token) body.token = previous.token;
|
|
@@ -717,9 +775,12 @@ var ApiClient = class {
|
|
|
717
775
|
s = await post();
|
|
718
776
|
}
|
|
719
777
|
} catch (e) {
|
|
778
|
+
if (generation !== this.sessionGeneration) return this.supersededSessions.get(generation);
|
|
779
|
+
this.session = null;
|
|
720
780
|
if (e instanceof ApiError && e.status === 401) tabStore.del(this.reuseKey);
|
|
721
781
|
throw e;
|
|
722
782
|
}
|
|
783
|
+
if (generation !== this.sessionGeneration) return this.supersededSessions.get(generation);
|
|
723
784
|
const now = Date.now();
|
|
724
785
|
const held = { ...s, obtainedAt: now };
|
|
725
786
|
this.session = held;
|
|
@@ -749,8 +810,14 @@ var ApiClient = class {
|
|
|
749
810
|
* stored anonymous visitor id is kept, so an anonymous visitor's history survives.
|
|
750
811
|
*/
|
|
751
812
|
async renewSession() {
|
|
813
|
+
const previousGeneration = this.sessionGeneration;
|
|
814
|
+
const previous = this.sessionPromise;
|
|
815
|
+
this.sessionGeneration++;
|
|
752
816
|
this.session = null;
|
|
753
|
-
|
|
817
|
+
this.sessionPromise = null;
|
|
818
|
+
const next = this.ensureSession();
|
|
819
|
+
if (previous) this.supersededSessions.set(previousGeneration, next);
|
|
820
|
+
return next;
|
|
754
821
|
}
|
|
755
822
|
/** Requests a single-use launch ticket. Does not refresh or swap the signed-in session after refusal. */
|
|
756
823
|
async jobAccess(signal) {
|
|
@@ -1078,7 +1145,10 @@ var en = {
|
|
|
1078
1145
|
jobAccessOpened: "Job access opened in a new window. Review and confirm any permissions there.",
|
|
1079
1146
|
jobAccessUnavailable: "Job access could not be opened. Sign in again or retry.",
|
|
1080
1147
|
jobAccessPopupBlocked: "Allow popups for this site, then choose Job access again.",
|
|
1081
|
-
jobAccessPopupClosed: "The window was closed. Choose Job access again to retry."
|
|
1148
|
+
jobAccessPopupClosed: "The window was closed. Choose Job access again to retry.",
|
|
1149
|
+
pageShare: "Share this page with the assistant for this message",
|
|
1150
|
+
pageChanged: "The page changed. Review it and choose to share again before sending.",
|
|
1151
|
+
pageUnavailable: "Page details are unavailable. You can send without them."
|
|
1082
1152
|
};
|
|
1083
1153
|
var vi = {
|
|
1084
1154
|
title: "Tr\u1EE3 l\xFD",
|
|
@@ -1138,7 +1208,10 @@ var vi = {
|
|
|
1138
1208
|
jobAccessOpened: "\u0110\xE3 m\u1EDF c\u1EEDa s\u1ED5 quy\u1EC1n c\xF4ng vi\u1EC7c. H\xE3y xem l\u1EA1i v\xE0 x\xE1c nh\u1EADn quy\u1EC1n t\u1EA1i \u0111\xF3.",
|
|
1139
1209
|
jobAccessUnavailable: "Ch\u01B0a m\u1EDF \u0111\u01B0\u1EE3c quy\u1EC1n c\xF4ng vi\u1EC7c. H\xE3y \u0111\u0103ng nh\u1EADp l\u1EA1i ho\u1EB7c th\u1EED l\u1EA1i.",
|
|
1140
1210
|
jobAccessPopupBlocked: "Cho ph\xE9p c\u1EEDa s\u1ED5 b\u1EADt l\xEAn cho trang n\xE0y r\u1ED3i ch\u1ECDn Quy\u1EC1n c\xF4ng vi\u1EC7c l\u1EA7n n\u1EEFa.",
|
|
1141
|
-
jobAccessPopupClosed: "C\u1EEDa s\u1ED5 \u0111\xE3 \u0111\xF3ng. Ch\u1ECDn Quy\u1EC1n c\xF4ng vi\u1EC7c l\u1EA7n n\u1EEFa \u0111\u1EC3 th\u1EED l\u1EA1i."
|
|
1211
|
+
jobAccessPopupClosed: "C\u1EEDa s\u1ED5 \u0111\xE3 \u0111\xF3ng. Ch\u1ECDn Quy\u1EC1n c\xF4ng vi\u1EC7c l\u1EA7n n\u1EEFa \u0111\u1EC3 th\u1EED l\u1EA1i.",
|
|
1212
|
+
pageShare: "Chia s\u1EBB trang n\xE0y v\u1EDBi tr\u1EE3 l\xFD trong tin nh\u1EAFn n\xE0y",
|
|
1213
|
+
pageChanged: "Trang \u0111\xE3 thay \u0111\u1ED5i. H\xE3y xem l\u1EA1i v\xE0 ch\u1ECDn chia s\u1EBB tr\u01B0\u1EDBc khi g\u1EEDi.",
|
|
1214
|
+
pageUnavailable: "Kh\xF4ng th\u1EC3 \u0111\u1ECDc n\u1ED9i dung trang. B\u1EA1n v\u1EABn c\xF3 th\u1EC3 g\u1EEDi tin nh\u1EAFn."
|
|
1142
1215
|
};
|
|
1143
1216
|
function pickStrings(locale) {
|
|
1144
1217
|
const l = (locale || (typeof document !== "undefined" ? document.documentElement.lang : "") || (typeof navigator !== "undefined" ? navigator.language : "") || "en").toLowerCase();
|
|
@@ -1158,6 +1231,40 @@ var icons = {
|
|
|
1158
1231
|
};
|
|
1159
1232
|
|
|
1160
1233
|
// src/widget/markdown.ts
|
|
1234
|
+
var imageObservers = /* @__PURE__ */ new WeakMap();
|
|
1235
|
+
var imageLoads = /* @__PURE__ */ new WeakMap();
|
|
1236
|
+
function disposeMarkdownImages(root) {
|
|
1237
|
+
for (const image of root.querySelectorAll("img.md-image")) {
|
|
1238
|
+
imageObservers.get(image)?.disconnect();
|
|
1239
|
+
imageObservers.delete(image);
|
|
1240
|
+
imageLoads.get(image)?.abort();
|
|
1241
|
+
imageLoads.delete(image);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function safeImageSrc(doc, raw, allowed) {
|
|
1245
|
+
try {
|
|
1246
|
+
if (!raw || /[\u0000-\u0020\u007f\\]/.test(raw) || raw.startsWith("//")) return null;
|
|
1247
|
+
const decoded = decodeURIComponent(raw);
|
|
1248
|
+
if (/[\u0000-\u0020\u007f\\]/.test(decoded) || decoded.startsWith("//")) return null;
|
|
1249
|
+
const page = new URL(doc.defaultView?.location.href ?? doc.baseURI);
|
|
1250
|
+
const url = new URL(raw, doc.baseURI);
|
|
1251
|
+
if (url.username || url.password || !["http:", "https:"].includes(url.protocol)) return null;
|
|
1252
|
+
if (/\.svg$/i.test(decodeURIComponent(url.pathname))) return null;
|
|
1253
|
+
if (url.origin === page.origin) return url.href;
|
|
1254
|
+
if (url.protocol !== "https:") return null;
|
|
1255
|
+
const trusted = allowed.some((item) => {
|
|
1256
|
+
try {
|
|
1257
|
+
const origin = new URL(item);
|
|
1258
|
+
return origin.protocol === "https:" && !origin.username && !origin.password && origin.href === origin.origin + "/" && origin.origin === url.origin;
|
|
1259
|
+
} catch {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
return trusted ? url.href : null;
|
|
1264
|
+
} catch {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1161
1268
|
function el(doc, tag, cls) {
|
|
1162
1269
|
const e = doc.createElement(tag);
|
|
1163
1270
|
if (cls) e.className = cls;
|
|
@@ -1169,45 +1276,87 @@ function safeHref(url) {
|
|
|
1169
1276
|
if (/^[/#?]/.test(u) && !u.startsWith("//")) return u;
|
|
1170
1277
|
return null;
|
|
1171
1278
|
}
|
|
1172
|
-
var INLINE_SRC =
|
|
1173
|
-
function renderInline(doc, text, parent) {
|
|
1279
|
+
var INLINE_SRC = /\\!\[[^\]]*\]\([^)\s]+\)|(`+)([\s\S]*?[^`])\1(?!`)|\*\*([^*]+?)\*\*|__([^_]+?)__|~~([^~]+?)~~|\*([^*\s][^*]*?)\*|(?<![\w])_([^_\s][^_]*?)_(?![\w])|(?<!\\)!\[([^\]]*)\]\(([^)\s]+)\)|\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)|(https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/;
|
|
1280
|
+
function renderInline(doc, text, parent, options = {}) {
|
|
1174
1281
|
const re = new RegExp(INLINE_SRC.source, "g");
|
|
1175
1282
|
let last = 0;
|
|
1176
1283
|
for (let m; m = re.exec(text); ) {
|
|
1177
1284
|
if (m.index > last) parent.appendChild(doc.createTextNode(text.slice(last, m.index)));
|
|
1178
1285
|
last = m.index + m[0].length;
|
|
1179
|
-
if (m[
|
|
1286
|
+
if (m[0].startsWith("\\!")) {
|
|
1287
|
+
parent.appendChild(doc.createTextNode(m[0].slice(1)));
|
|
1288
|
+
} else if (m[2] !== void 0) {
|
|
1180
1289
|
const c = el(doc, "code");
|
|
1181
1290
|
c.textContent = m[2];
|
|
1182
1291
|
parent.appendChild(c);
|
|
1183
1292
|
} else if (m[3] !== void 0 || m[4] !== void 0) {
|
|
1184
1293
|
const s = el(doc, "strong");
|
|
1185
|
-
renderInline(doc, m[3] ?? m[4], s);
|
|
1294
|
+
renderInline(doc, m[3] ?? m[4], s, options);
|
|
1186
1295
|
parent.appendChild(s);
|
|
1187
1296
|
} else if (m[5] !== void 0) {
|
|
1188
1297
|
const s = el(doc, "del");
|
|
1189
|
-
renderInline(doc, m[5], s);
|
|
1298
|
+
renderInline(doc, m[5], s, options);
|
|
1190
1299
|
parent.appendChild(s);
|
|
1191
1300
|
} else if (m[6] !== void 0 || m[7] !== void 0) {
|
|
1192
1301
|
const s = el(doc, "em");
|
|
1193
|
-
renderInline(doc, m[6] ?? m[7], s);
|
|
1302
|
+
renderInline(doc, m[6] ?? m[7], s, options);
|
|
1194
1303
|
parent.appendChild(s);
|
|
1195
1304
|
} else if (m[8] !== void 0) {
|
|
1196
|
-
const
|
|
1305
|
+
const original = options.deferImages ? null : safeImageSrc(doc, m[9], options.imageOrigins ?? []);
|
|
1306
|
+
if (!original || !options.imageProxy) parent.appendChild(doc.createTextNode(m[8]));
|
|
1307
|
+
else {
|
|
1308
|
+
const img = el(doc, "img", "md-image");
|
|
1309
|
+
img.alt = m[8];
|
|
1310
|
+
img.setAttribute("loading", "lazy");
|
|
1311
|
+
img.setAttribute("decoding", "async");
|
|
1312
|
+
img.referrerPolicy = "no-referrer";
|
|
1313
|
+
let retried = false;
|
|
1314
|
+
const load = () => {
|
|
1315
|
+
const controller = new AbortController();
|
|
1316
|
+
imageLoads.set(img, controller);
|
|
1317
|
+
return options.imageProxy(original, controller.signal).then((src) => {
|
|
1318
|
+
if (!controller.signal.aborted && img.isConnected) img.src = src;
|
|
1319
|
+
}).catch(() => {
|
|
1320
|
+
if (!controller.signal.aborted && img.isConnected) img.replaceWith(doc.createTextNode(img.alt));
|
|
1321
|
+
}).finally(() => {
|
|
1322
|
+
if (imageLoads.get(img) === controller) imageLoads.delete(img);
|
|
1323
|
+
});
|
|
1324
|
+
};
|
|
1325
|
+
img.addEventListener("error", () => {
|
|
1326
|
+
if (!retried) {
|
|
1327
|
+
retried = true;
|
|
1328
|
+
void load();
|
|
1329
|
+
} else img.replaceWith(doc.createTextNode(img.alt));
|
|
1330
|
+
});
|
|
1331
|
+
parent.appendChild(img);
|
|
1332
|
+
const Observer = doc.defaultView?.IntersectionObserver;
|
|
1333
|
+
if (Observer) {
|
|
1334
|
+
const observer = new Observer((entries) => {
|
|
1335
|
+
if (!entries.some((entry) => entry.isIntersecting)) return;
|
|
1336
|
+
observer.disconnect();
|
|
1337
|
+
imageObservers.delete(img);
|
|
1338
|
+
if (img.parentNode) void load();
|
|
1339
|
+
}, { rootMargin: "200px" });
|
|
1340
|
+
imageObservers.set(img, observer);
|
|
1341
|
+
observer.observe(img);
|
|
1342
|
+
} else void load();
|
|
1343
|
+
}
|
|
1344
|
+
} else if (m[10] !== void 0) {
|
|
1345
|
+
const href = safeHref(m[11]);
|
|
1197
1346
|
if (href) {
|
|
1198
1347
|
const a = el(doc, "a");
|
|
1199
1348
|
a.href = href;
|
|
1200
1349
|
a.target = "_blank";
|
|
1201
1350
|
a.rel = "noopener noreferrer nofollow";
|
|
1202
|
-
renderInline(doc, m[
|
|
1351
|
+
renderInline(doc, m[10], a, options);
|
|
1203
1352
|
parent.appendChild(a);
|
|
1204
|
-
} else parent.appendChild(doc.createTextNode(m[
|
|
1205
|
-
} else if (m[
|
|
1353
|
+
} else parent.appendChild(doc.createTextNode(m[10]));
|
|
1354
|
+
} else if (m[12] !== void 0) {
|
|
1206
1355
|
const a = el(doc, "a");
|
|
1207
|
-
a.href = m[
|
|
1356
|
+
a.href = m[12];
|
|
1208
1357
|
a.target = "_blank";
|
|
1209
1358
|
a.rel = "noopener noreferrer nofollow";
|
|
1210
|
-
a.textContent = m[
|
|
1359
|
+
a.textContent = m[12];
|
|
1211
1360
|
parent.appendChild(a);
|
|
1212
1361
|
}
|
|
1213
1362
|
}
|
|
@@ -1215,7 +1364,7 @@ function renderInline(doc, text, parent) {
|
|
|
1215
1364
|
}
|
|
1216
1365
|
var isTableSep = (l) => /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(l);
|
|
1217
1366
|
var splitRow = (l) => l.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
|
|
1218
|
-
function renderMarkdown(doc, src) {
|
|
1367
|
+
function renderMarkdown(doc, src, options = {}) {
|
|
1219
1368
|
const frag = doc.createDocumentFragment();
|
|
1220
1369
|
const lines = src.replace(/\r\n?/g, "\n").split("\n");
|
|
1221
1370
|
let i = 0;
|
|
@@ -1243,7 +1392,7 @@ function renderMarkdown(doc, src) {
|
|
|
1243
1392
|
if (h) {
|
|
1244
1393
|
const tag = `h${Math.min(6, h[1].length + 2)}`;
|
|
1245
1394
|
const e = el(doc, tag);
|
|
1246
|
-
renderInline(doc, h[2], e);
|
|
1395
|
+
renderInline(doc, h[2], e, options);
|
|
1247
1396
|
frag.appendChild(e);
|
|
1248
1397
|
i++;
|
|
1249
1398
|
continue;
|
|
@@ -1260,7 +1409,7 @@ function renderMarkdown(doc, src) {
|
|
|
1260
1409
|
const tr = el(doc, "tr");
|
|
1261
1410
|
for (const c of splitRow(line)) {
|
|
1262
1411
|
const th = el(doc, "th");
|
|
1263
|
-
renderInline(doc, c, th);
|
|
1412
|
+
renderInline(doc, c, th, options);
|
|
1264
1413
|
tr.appendChild(th);
|
|
1265
1414
|
}
|
|
1266
1415
|
thead.appendChild(tr);
|
|
@@ -1271,7 +1420,7 @@ function renderMarkdown(doc, src) {
|
|
|
1271
1420
|
const r = el(doc, "tr");
|
|
1272
1421
|
for (const c of splitRow(lines[i])) {
|
|
1273
1422
|
const td = el(doc, "td");
|
|
1274
|
-
renderInline(doc, c, td);
|
|
1423
|
+
renderInline(doc, c, td, options);
|
|
1275
1424
|
r.appendChild(td);
|
|
1276
1425
|
}
|
|
1277
1426
|
tbody.appendChild(r);
|
|
@@ -1286,7 +1435,7 @@ function renderMarkdown(doc, src) {
|
|
|
1286
1435
|
const body = [];
|
|
1287
1436
|
while (i < lines.length && /^\s*>/.test(lines[i])) body.push(lines[i++].replace(/^\s*>\s?/, ""));
|
|
1288
1437
|
const q = el(doc, "blockquote");
|
|
1289
|
-
q.appendChild(renderMarkdown(doc, body.join("\n")));
|
|
1438
|
+
q.appendChild(renderMarkdown(doc, body.join("\n"), options));
|
|
1290
1439
|
frag.appendChild(q);
|
|
1291
1440
|
continue;
|
|
1292
1441
|
}
|
|
@@ -1302,11 +1451,11 @@ function renderMarkdown(doc, src) {
|
|
|
1302
1451
|
if (m[1].length > baseIndent) {
|
|
1303
1452
|
const sub = [];
|
|
1304
1453
|
while (i < lines.length && (/^\s+/.test(lines[i]) && lines[i].match(/^(\s*)/)[1].length > baseIndent)) sub.push(lines[i++].slice(baseIndent + 2));
|
|
1305
|
-
list.lastElementChild?.appendChild(renderMarkdown(doc, sub.join("\n")));
|
|
1454
|
+
list.lastElementChild?.appendChild(renderMarkdown(doc, sub.join("\n"), options));
|
|
1306
1455
|
continue;
|
|
1307
1456
|
}
|
|
1308
1457
|
const item = el(doc, "li");
|
|
1309
|
-
renderInline(doc, m[3], item);
|
|
1458
|
+
renderInline(doc, m[3], item, options);
|
|
1310
1459
|
list.appendChild(item);
|
|
1311
1460
|
i++;
|
|
1312
1461
|
}
|
|
@@ -1319,7 +1468,7 @@ function renderMarkdown(doc, src) {
|
|
|
1319
1468
|
const p = el(doc, "p");
|
|
1320
1469
|
para.forEach((l, idx) => {
|
|
1321
1470
|
if (idx) p.appendChild(el(doc, "br"));
|
|
1322
|
-
renderInline(doc, l, p);
|
|
1471
|
+
renderInline(doc, l, p, options);
|
|
1323
1472
|
});
|
|
1324
1473
|
frag.appendChild(p);
|
|
1325
1474
|
}
|
|
@@ -1517,6 +1666,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
1517
1666
|
.md li { margin: 3px 0; }
|
|
1518
1667
|
.md h3, .md h4, .md h5, .md h6 { margin: 14px 0 6px; font-size: 15px; line-height: 1.35; }
|
|
1519
1668
|
.md a { color: var(--dw-accent); text-decoration: underline; text-underline-offset: 2px; }
|
|
1669
|
+
.md .md-image { display: block; max-width: 100%; max-height: 320px; min-height: 48px; width: auto; height: auto; object-fit: contain; border-radius: 8px; }
|
|
1520
1670
|
.md code { font-family: var(--dw-mono); font-size: .86em; background: var(--dw-surface); padding: 1.5px 5px; border-radius: 5px; }
|
|
1521
1671
|
.md pre { background: #12132a; color: #e6e6f5; padding: 12px 14px; border-radius: 12px; overflow-x: auto; font-size: 12.5px; line-height: 1.55; }
|
|
1522
1672
|
.md pre code { background: none; padding: 0; color: inherit; font-size: inherit; }
|
|
@@ -1611,6 +1761,9 @@ details > summary svg.ic { width: 14px; height: 14px; flex: none; }
|
|
|
1611
1761
|
|
|
1612
1762
|
/* ---------- composer ---------- */
|
|
1613
1763
|
.composer { padding: 10px 12px 12px; border-top: 1px solid var(--dw-border); background: var(--dw-bg); }
|
|
1764
|
+
.page-context { font-size: 12px; margin-bottom: 8px; }
|
|
1765
|
+
.page-context label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
1766
|
+
.page-preview { max-height: 96px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; padding: 6px; margin: 6px 0; border: 1px solid var(--dw-border); border-radius: 6px; font: inherit; }
|
|
1614
1767
|
.box {
|
|
1615
1768
|
display: flex; align-items: flex-end; gap: 8px; border: 1px solid var(--dw-border); border-radius: 14px;
|
|
1616
1769
|
padding: 6px 6px 6px 12px; background: var(--dw-bg); transition: border-color .15s, box-shadow .15s;
|
|
@@ -1665,6 +1818,10 @@ var ATTRS = [
|
|
|
1665
1818
|
"height",
|
|
1666
1819
|
"z-index",
|
|
1667
1820
|
"dom-actions-root",
|
|
1821
|
+
"image-origins",
|
|
1822
|
+
"page-context",
|
|
1823
|
+
"context-selector",
|
|
1824
|
+
"exclude-selectors",
|
|
1668
1825
|
...BOOL_ATTRS
|
|
1669
1826
|
];
|
|
1670
1827
|
var camel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -1712,12 +1869,17 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
1712
1869
|
api = null;
|
|
1713
1870
|
apiKey = "";
|
|
1714
1871
|
server = null;
|
|
1872
|
+
sharedPage;
|
|
1873
|
+
preparingPage = false;
|
|
1874
|
+
pagePending = false;
|
|
1875
|
+
pageGeneration = 0;
|
|
1715
1876
|
agentId = "";
|
|
1716
1877
|
conversationId = "";
|
|
1717
1878
|
messages = [];
|
|
1718
1879
|
running = null;
|
|
1719
1880
|
jobAccessLaunch = null;
|
|
1720
1881
|
activated = false;
|
|
1882
|
+
probedPage = false;
|
|
1721
1883
|
strings = pickStrings();
|
|
1722
1884
|
mql = null;
|
|
1723
1885
|
registry = new ActionRegistry();
|
|
@@ -1742,6 +1904,8 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
1742
1904
|
if (v === null) continue;
|
|
1743
1905
|
if (BOOL_ATTRS.includes(a)) c[camel(a)] = parseBool(v);
|
|
1744
1906
|
else if (a === "suggestions") c.suggestions = v.trim().startsWith("[") ? safeJson(v, []) : v.split("|").map((s) => s.trim()).filter(Boolean);
|
|
1907
|
+
else if (a === "image-origins") c.imageOrigins = v.trim().startsWith("[") ? safeJson(v, []) : v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1908
|
+
else if (a === "exclude-selectors") c.excludeSelectors = v.trim().startsWith("[") ? safeJson(v, []) : v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1745
1909
|
else if (a === "z-index") c.zIndex = Number(v);
|
|
1746
1910
|
else c[camel(a)] = v;
|
|
1747
1911
|
}
|
|
@@ -1749,13 +1913,14 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
1749
1913
|
}
|
|
1750
1914
|
/** Updates configuration at runtime (wins over attributes). */
|
|
1751
1915
|
configure(partial) {
|
|
1752
|
-
const { actions, confirm, getPageContext, headers, fetch, domActionsRoot, ...rest } = partial;
|
|
1916
|
+
const { actions, confirm, getPageContext, headers, fetch, domActionsRoot, contextProvider, ...rest } = partial;
|
|
1753
1917
|
const has = (k) => Object.prototype.hasOwnProperty.call(partial, k);
|
|
1754
1918
|
if (has("actions")) this.setPropActions(actions ?? []);
|
|
1755
1919
|
if (has("confirm")) this.hooks.confirm = confirm;
|
|
1756
1920
|
if (has("getPageContext")) this.hooks.getPageContext = getPageContext;
|
|
1757
1921
|
if (has("headers")) this.hooks.headers = headers;
|
|
1758
1922
|
if (has("fetch")) this.hooks.fetch = fetch;
|
|
1923
|
+
if (has("contextProvider")) this.hooks.contextProvider = contextProvider;
|
|
1759
1924
|
if (has("domActionsRoot")) {
|
|
1760
1925
|
if (domActionsRoot === void 0) delete this.hooks.domActionsRoot;
|
|
1761
1926
|
else this.hooks.domActionsRoot = domActionsRoot;
|
|
@@ -1832,14 +1997,47 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
1832
1997
|
/** Sends a message programmatically (opens the widget first). */
|
|
1833
1998
|
async send(text) {
|
|
1834
1999
|
const message = text.trim();
|
|
1835
|
-
if (!message || this.running || this.cooldown.active) return;
|
|
2000
|
+
if (!message || this.running || this.preparingPage || this.cooldown.active) return;
|
|
1836
2001
|
if (this.config.mode !== "inline" && !this.hasAttribute("data-open")) this.open();
|
|
1837
2002
|
await this.activate();
|
|
2003
|
+
let attachment;
|
|
2004
|
+
if (this.els.pageChoice.checked) {
|
|
2005
|
+
if (this.pagePending) {
|
|
2006
|
+
this.showNotice(this.strings.pageUnavailable);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
this.preparingPage = true;
|
|
2010
|
+
const generation = this.pageGeneration;
|
|
2011
|
+
try {
|
|
2012
|
+
const previous = this.sharedPage;
|
|
2013
|
+
const current = await this.captureCurrentPage();
|
|
2014
|
+
if (!this.els.pageChoice.checked || generation !== this.pageGeneration) return;
|
|
2015
|
+
if (!previous || !current || this.pageFingerprint(previous) !== this.pageFingerprint(current)) {
|
|
2016
|
+
this.sharedPage = current;
|
|
2017
|
+
this.renderPagePreview();
|
|
2018
|
+
this.els.pageChoice.checked = false;
|
|
2019
|
+
this.showNotice(this.strings.pageChanged);
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
attachment = current;
|
|
2023
|
+
} catch {
|
|
2024
|
+
this.sharedPage = void 0;
|
|
2025
|
+
this.els.pageChoice.checked = false;
|
|
2026
|
+
this.renderPagePreview();
|
|
2027
|
+
this.showNotice(this.strings.pageUnavailable);
|
|
2028
|
+
return;
|
|
2029
|
+
} finally {
|
|
2030
|
+
this.preparingPage = false;
|
|
2031
|
+
}
|
|
2032
|
+
this.els.pageChoice.checked = false;
|
|
2033
|
+
this.sharedPage = void 0;
|
|
2034
|
+
this.renderPagePreview();
|
|
2035
|
+
}
|
|
1838
2036
|
this.els.input.value = "";
|
|
1839
2037
|
this.autosize();
|
|
1840
2038
|
let next = { message, hidden: false };
|
|
1841
2039
|
for (let round = 0; next; round++) {
|
|
1842
|
-
const reply = await this.runTurn(next.message, next.hidden);
|
|
2040
|
+
const reply = await this.runTurn(next.message, next.hidden, round === 0 ? attachment : void 0);
|
|
1843
2041
|
next = null;
|
|
1844
2042
|
if (reply.status !== "done" || reply.stopped || !this.actionRegistry().size) break;
|
|
1845
2043
|
const { calls } = parseDirectives(reply.content);
|
|
@@ -1858,7 +2056,7 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
1858
2056
|
}
|
|
1859
2057
|
}
|
|
1860
2058
|
/** One request/response with the agent. */
|
|
1861
|
-
async runTurn(message, hidden) {
|
|
2059
|
+
async runTurn(message, hidden, attachment) {
|
|
1862
2060
|
this.pushMessage({ role: "user", content: message, hidden });
|
|
1863
2061
|
const reply = { role: "assistant", content: "", status: "streaming", startedAt: Date.now() };
|
|
1864
2062
|
this.pushMessage(reply);
|
|
@@ -1866,7 +2064,7 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
1866
2064
|
this.emit("dewee:send", { message, agent: this.agentId, conversationId: this.conversationId, auto: hidden || void 0 });
|
|
1867
2065
|
try {
|
|
1868
2066
|
await this.client().ensureSession();
|
|
1869
|
-
await this.client().chat(this.chatBody(message), (e) => this.onStreamEvent(reply, e), this.running.signal);
|
|
2067
|
+
await this.client().chat({ ...this.chatBody(message), ...attachment ? { page_context: attachment } : {} }, (e) => this.onStreamEvent(reply, e), this.running.signal);
|
|
1870
2068
|
if (reply.status === "streaming") reply.status = "done";
|
|
1871
2069
|
} catch (e) {
|
|
1872
2070
|
if (e.name === "AbortError") {
|
|
@@ -2102,6 +2300,12 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2102
2300
|
}
|
|
2103
2301
|
}
|
|
2104
2302
|
disconnectedCallback() {
|
|
2303
|
+
this.pageGeneration++;
|
|
2304
|
+
this.sharedPage = void 0;
|
|
2305
|
+
this.els.pageChoice.checked = false;
|
|
2306
|
+
this.renderPagePreview();
|
|
2307
|
+
disposeMarkdownImages(this.root);
|
|
2308
|
+
for (const message of this.messages) if (message.view) message.view.mdKey = void 0;
|
|
2105
2309
|
this.jobAccessLaunch?.abort();
|
|
2106
2310
|
this.stop();
|
|
2107
2311
|
this.mql?.removeEventListener("change", this.onScheme);
|
|
@@ -2183,6 +2387,8 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2183
2387
|
e.jobAccess.setAttribute("aria-label", s.jobAccess);
|
|
2184
2388
|
e.jobAccess.title = s.jobAccess;
|
|
2185
2389
|
e.input.placeholder = c.placeholder || s.placeholder;
|
|
2390
|
+
e.pageLabel.textContent = s.pageShare;
|
|
2391
|
+
this.updatePageChoice();
|
|
2186
2392
|
e.foot.hidden = !!c.hideBranding;
|
|
2187
2393
|
e.foot.querySelector(".by").textContent = s.poweredBy + " ";
|
|
2188
2394
|
this.renderSubtitle();
|
|
@@ -2190,11 +2396,16 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2190
2396
|
if (this.activated && this.api && this.apiKey !== [c.endpoint, c.gatewayUrl, c.gatewayToken].join("|")) {
|
|
2191
2397
|
this.activated = false;
|
|
2192
2398
|
this.server = null;
|
|
2399
|
+
this.probedPage = false;
|
|
2400
|
+
this.activate();
|
|
2401
|
+
} else if (this.activated && !this.probedPage && (c.pageContext === "metadata" || c.pageContext === "content")) {
|
|
2402
|
+
this.activated = false;
|
|
2193
2403
|
this.activate();
|
|
2194
2404
|
} else if (this.activated && c.agent && c.agent !== this.agentId && this.server) {
|
|
2195
2405
|
this.switchAgent(c.agent);
|
|
2196
2406
|
} else if (this.activated) {
|
|
2197
|
-
this.renderLog();
|
|
2407
|
+
if (!this.messages.length) this.renderLog();
|
|
2408
|
+
else for (const message of this.messages) this.updateMessage(message);
|
|
2198
2409
|
}
|
|
2199
2410
|
}
|
|
2200
2411
|
renderShell() {
|
|
@@ -2215,6 +2426,7 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2215
2426
|
<div class="log" part="log" role="log" aria-live="polite"></div>
|
|
2216
2427
|
<div class="challenge"><slot name="challenge"></slot></div>
|
|
2217
2428
|
<form class="composer" part="composer">
|
|
2429
|
+
<div class="page-context" hidden><label><input type="checkbox" class="page-choice"> <span class="page-label"></span></label><pre class="page-preview" hidden></pre></div>
|
|
2218
2430
|
<div class="cooldown" part="cooldown" aria-hidden="true" hidden></div>
|
|
2219
2431
|
<div class="sr-only announcer" role="status" aria-live="polite"></div>
|
|
2220
2432
|
<div class="box">
|
|
@@ -2241,7 +2453,11 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2241
2453
|
close: $(".close"),
|
|
2242
2454
|
foot: $(".foot"),
|
|
2243
2455
|
cooldown: $(".cooldown"),
|
|
2244
|
-
announcer: $(".announcer")
|
|
2456
|
+
announcer: $(".announcer"),
|
|
2457
|
+
pageChoice: $(".page-choice"),
|
|
2458
|
+
pageWrap: $(".page-context"),
|
|
2459
|
+
pageLabel: $(".page-label"),
|
|
2460
|
+
pagePreview: $(".page-preview")
|
|
2245
2461
|
};
|
|
2246
2462
|
const e = this.els;
|
|
2247
2463
|
e.launcher.addEventListener("click", () => this.toggle());
|
|
@@ -2253,6 +2469,9 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2253
2469
|
e.jobAccess.addEventListener("click", () => {
|
|
2254
2470
|
void this.openJobAccess();
|
|
2255
2471
|
});
|
|
2472
|
+
e.pageChoice.addEventListener("change", () => {
|
|
2473
|
+
void this.choosePage();
|
|
2474
|
+
});
|
|
2256
2475
|
e.select.addEventListener("change", () => this.switchAgent(e.select.value));
|
|
2257
2476
|
$("form").addEventListener("submit", (ev) => {
|
|
2258
2477
|
ev.preventDefault();
|
|
@@ -2277,6 +2496,80 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2277
2496
|
t.style.height = "auto";
|
|
2278
2497
|
t.style.height = Math.min(t.scrollHeight, 140) + "px";
|
|
2279
2498
|
}
|
|
2499
|
+
updatePageChoice() {
|
|
2500
|
+
const mode = this.config.pageContext;
|
|
2501
|
+
const available = (mode === "metadata" || mode === "content") && this.server?.pageContextVersions?.includes(1) === true;
|
|
2502
|
+
this.els.pageWrap.hidden = !available;
|
|
2503
|
+
if (!available) {
|
|
2504
|
+
this.pageGeneration++;
|
|
2505
|
+
this.pagePending = false;
|
|
2506
|
+
this.els.pageChoice.checked = false;
|
|
2507
|
+
this.sharedPage = void 0;
|
|
2508
|
+
this.renderPagePreview();
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
async captureCurrentPage() {
|
|
2512
|
+
const c = this.config;
|
|
2513
|
+
if (c.pageContext !== "metadata" && c.pageContext !== "content" || !this.server?.pageContextVersions?.includes(1)) return void 0;
|
|
2514
|
+
const before = document.location.href;
|
|
2515
|
+
const { capturePageContext, providedPageContext } = await import("./page-context-H3ZROFAV.js");
|
|
2516
|
+
const provider = this.hooks.contextProvider;
|
|
2517
|
+
const configKey = JSON.stringify([c.pageContext, c.contextSelector, c.excludeSelectors]);
|
|
2518
|
+
const base = capturePageContext(document, { mode: provider ? "metadata" : c.pageContext, selector: c.contextSelector, excludeSelectors: c.excludeSelectors });
|
|
2519
|
+
let result = base;
|
|
2520
|
+
if (base && provider) {
|
|
2521
|
+
let timer;
|
|
2522
|
+
try {
|
|
2523
|
+
const supplied = await Promise.race([provider(), new Promise((_, reject) => {
|
|
2524
|
+
timer = setTimeout(() => reject(new Error("Page context timed out")), 5e3);
|
|
2525
|
+
})]);
|
|
2526
|
+
result = providedPageContext(supplied, base, c.pageContext);
|
|
2527
|
+
} finally {
|
|
2528
|
+
if (timer) clearTimeout(timer);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
const now = this.config;
|
|
2532
|
+
if (!this.isConnected || before !== document.location.href || provider !== this.hooks.contextProvider || configKey !== JSON.stringify([now.pageContext, now.contextSelector, now.excludeSelectors])) return void 0;
|
|
2533
|
+
return result;
|
|
2534
|
+
}
|
|
2535
|
+
pageFingerprint(page) {
|
|
2536
|
+
const { captured_at: _capturedAt, ...stable } = page;
|
|
2537
|
+
return JSON.stringify(stable);
|
|
2538
|
+
}
|
|
2539
|
+
renderPagePreview() {
|
|
2540
|
+
const preview = this.els.pagePreview;
|
|
2541
|
+
preview.hidden = !this.sharedPage;
|
|
2542
|
+
preview.textContent = this.sharedPage ? [this.sharedPage.title, this.sharedPage.url, this.sharedPage.description, this.sharedPage.selection, this.sharedPage.content?.text].filter(Boolean).join("\n\n") : "";
|
|
2543
|
+
}
|
|
2544
|
+
async choosePage() {
|
|
2545
|
+
const generation = ++this.pageGeneration;
|
|
2546
|
+
if (!this.els.pageChoice.checked) {
|
|
2547
|
+
this.pagePending = false;
|
|
2548
|
+
this.sharedPage = void 0;
|
|
2549
|
+
this.renderPagePreview();
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
this.sharedPage = void 0;
|
|
2553
|
+
this.renderPagePreview();
|
|
2554
|
+
this.pagePending = true;
|
|
2555
|
+
try {
|
|
2556
|
+
const page = await this.captureCurrentPage();
|
|
2557
|
+
if (generation !== this.pageGeneration || !this.els.pageChoice.checked) return;
|
|
2558
|
+
if (!page) throw new Error("Page unavailable");
|
|
2559
|
+
this.sharedPage = page;
|
|
2560
|
+
this.renderPagePreview();
|
|
2561
|
+
this.emit("dewee:context-ready", { available: true });
|
|
2562
|
+
} catch {
|
|
2563
|
+
if (generation !== this.pageGeneration) return;
|
|
2564
|
+
this.sharedPage = void 0;
|
|
2565
|
+
this.els.pageChoice.checked = false;
|
|
2566
|
+
this.renderPagePreview();
|
|
2567
|
+
this.showNotice(this.strings.pageUnavailable);
|
|
2568
|
+
this.emit("dewee:context-error", { code: "page_unavailable" });
|
|
2569
|
+
} finally {
|
|
2570
|
+
if (generation === this.pageGeneration) this.pagePending = false;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2280
2573
|
updateSendButton() {
|
|
2281
2574
|
const b = this.els.send;
|
|
2282
2575
|
b.innerHTML = this.running ? icons.stop : icons.send;
|
|
@@ -2327,7 +2620,8 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2327
2620
|
this.activated = true;
|
|
2328
2621
|
this.renderLog();
|
|
2329
2622
|
try {
|
|
2330
|
-
this.
|
|
2623
|
+
this.probedPage = this.config.pageContext === "metadata" || this.config.pageContext === "content";
|
|
2624
|
+
this.server = await this.client().getConfig(true, this.probedPage);
|
|
2331
2625
|
this.els.input.maxLength = this.server.maxMessageLength;
|
|
2332
2626
|
const wanted = this.config.agent;
|
|
2333
2627
|
const known = (id) => !!id && (this.server.agents.length === 0 || this.server.agents.some((a) => a.id === id));
|
|
@@ -2339,6 +2633,7 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2339
2633
|
this.showError(null, e);
|
|
2340
2634
|
}
|
|
2341
2635
|
this.renderSubtitle();
|
|
2636
|
+
this.updatePageChoice();
|
|
2342
2637
|
this.emit("dewee:ready", { agent: this.agentId, config: this.server });
|
|
2343
2638
|
}
|
|
2344
2639
|
async switchAgent(id) {
|
|
@@ -2365,6 +2660,7 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2365
2660
|
}
|
|
2366
2661
|
renderLog() {
|
|
2367
2662
|
const log = this.els.log;
|
|
2663
|
+
disposeMarkdownImages(log);
|
|
2368
2664
|
log.replaceChildren();
|
|
2369
2665
|
if (!this.messages.length) log.appendChild(this.renderWelcome());
|
|
2370
2666
|
for (const m of this.messages) log.appendChild(this.renderMessage(m));
|
|
@@ -2467,10 +2763,15 @@ var DeweeChatElement = class extends ElementBase {
|
|
|
2467
2763
|
const shown = this.actionRegistry().size || m.actions ? stripDirectives(m.content) : m.content;
|
|
2468
2764
|
const typing = m.status === "streaming" && !shown;
|
|
2469
2765
|
v.md.hidden = !typing && !shown && !!m.actions?.length;
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
v.md
|
|
2766
|
+
const mdKey = JSON.stringify([shown, typing, m.status === "streaming", c.endpoint, c.imageOrigins, this.strings.thinking]);
|
|
2767
|
+
if (v.mdKey !== mdKey) {
|
|
2768
|
+
v.mdKey = mdKey;
|
|
2769
|
+
disposeMarkdownImages(v.md);
|
|
2770
|
+
if (typing) {
|
|
2771
|
+
v.md.innerHTML = `<span class="typing" aria-label="${this.strings.thinking}"><i></i><i></i><i></i></span>`;
|
|
2772
|
+
} else {
|
|
2773
|
+
v.md.replaceChildren(renderMarkdown(document, shown, { imageOrigins: Array.isArray(c.imageOrigins) ? c.imageOrigins : [], imageProxy: (source, signal) => this.client().imageUrl(source, signal), deferImages: m.status === "streaming" }));
|
|
2774
|
+
}
|
|
2474
2775
|
v.md.classList.toggle("cursor", m.status === "streaming");
|
|
2475
2776
|
}
|
|
2476
2777
|
this.renderActions(v, m.actions ?? []);
|