dsh-lcx-codex 0.4.2 → 0.4.3-pre.13
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 +75 -224
- package/THIRD_PARTY_NOTICES.md +64 -0
- package/cordis.patch.yml +3 -20
- package/lib/auxiliary-usage.js +63 -0
- package/lib/client.js +1398 -167
- package/lib/compact-v2.js +218 -199
- package/lib/dsh-compat.js +294 -100
- package/lib/dsh-responses.js +512 -277
- package/lib/grok-native-search.js +391 -0
- package/lib/index.js +1066 -758
- package/lib/invocation-policy-scope.js +261 -0
- package/lib/json-store.js +57 -31
- package/lib/native-checkpoint.js +520 -194
- package/lib/pi-responses-runtime.js +1571 -0
- package/lib/responses-request.js +109 -121
- package/lib/responses-stream.js +1280 -447
- package/lib/route.js +425 -369
- package/lib/search-accounting.js +86 -0
- package/lib/search-usage.js +86 -0
- package/lib/service-mutex.js +73 -64
- package/lib/token-budget.js +176 -108
- package/lib/transport.js +308 -68
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/search-media.d.ts +16 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/web-run-output.js +189 -18
- package/lib/web-search-alpha.js +1067 -163
- package/lib/web-search-capability.js +80 -65
- package/lib/web-search-hosted.js +321 -33
- package/lib/web-search-ref-store.js +145 -60
- package/package.json +112 -32
- package/ARCHITECTURE.md +0 -117
- package/CHANGELOG.md +0 -224
- package/README_EN.md +0 -277
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/legacy-v3.js +0 -20
- package/lib/responses-replay.js +0 -68
- package/scripts/probe-alpha.mjs +0 -43
- package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/client.js
CHANGED
|
@@ -1,168 +1,1399 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/client/search-media.ts
|
|
4
|
+
var SEARCH_MEDIA_LIMIT = 60;
|
|
5
|
+
var IMAGE_EXTENSION = /\.(?:jpe?g|png|webp|gif|avif)$/i;
|
|
6
|
+
var VIDEO_EXTENSION = /\.(?:mp4|webm|ogv)$/i;
|
|
7
|
+
var URL_TOKEN = /https?:\/\/[^\s<>"'`,。;:!?、“”‘’()【】《》]+/gi;
|
|
8
|
+
var REFERENCE_DEFINITION = /^ {0,3}\[([^\]\r\n]+)\]:[ \t]*(?:<([^>\s]+)>|(\S+))(?:[ \t]+.*)?$/gm;
|
|
9
|
+
var REFERENCE_IMAGE = /!\[([^\]\r\n]*)\](?:\s*\[([^\]\r\n]*)\])?/g;
|
|
10
|
+
var REFERENCE_LINK = /(^|[^!])\[([^\]\r\n]+)\]\s*\[([^\]\r\n]*)\]/g;
|
|
11
|
+
function stripFencedCode(text) {
|
|
12
|
+
const lines = text.split(/(?<=\n)/);
|
|
13
|
+
let fence = null;
|
|
14
|
+
return lines.map((line) => {
|
|
15
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})([^\r\n]*)/);
|
|
16
|
+
if (fence === null) {
|
|
17
|
+
if (/^(?: {4}|\t)/.test(line)) return line.replace(/[^\r\n]/g, " ");
|
|
18
|
+
if (match === null) return line;
|
|
19
|
+
fence = { marker: match[1][0], length: match[1].length };
|
|
20
|
+
return line.replace(/[^\r\n]/g, " ");
|
|
21
|
+
}
|
|
22
|
+
const closes = match !== null && match[1][0] === fence.marker && match[1].length >= fence.length && match[2].trim() === "";
|
|
23
|
+
if (closes) fence = null;
|
|
24
|
+
return line.replace(/[^\r\n]/g, " ");
|
|
25
|
+
}).join("");
|
|
26
|
+
}
|
|
27
|
+
function stripInlineCode(text) {
|
|
28
|
+
let result = "";
|
|
29
|
+
for (let index = 0; index < text.length; ) {
|
|
30
|
+
if (text[index] !== "`") {
|
|
31
|
+
result += text[index++];
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let end = index;
|
|
35
|
+
while (text[end] === "`") end += 1;
|
|
36
|
+
const marker = text.slice(index, end);
|
|
37
|
+
const close = text.indexOf(marker, end);
|
|
38
|
+
if (close === -1) {
|
|
39
|
+
result += marker;
|
|
40
|
+
index = end;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
result += " ".repeat(close + marker.length - index);
|
|
44
|
+
index = close + marker.length;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
function stripHtml(text) {
|
|
49
|
+
return text.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<(https?:\/\/[^<>\s]+)>/gi, "$1").replace(/<[^>]*>/g, " ");
|
|
50
|
+
}
|
|
51
|
+
function destination(value) {
|
|
52
|
+
const trimmed = value.trim();
|
|
53
|
+
if (trimmed.startsWith("<")) {
|
|
54
|
+
const end = trimmed.indexOf(">", 1);
|
|
55
|
+
return end === -1 ? null : trimmed.slice(1, end);
|
|
56
|
+
}
|
|
57
|
+
const match = trimmed.match(/^(?:\\.|\S)+/);
|
|
58
|
+
return match?.[0]?.replace(/\\([()[\]])/g, "$1") ?? null;
|
|
59
|
+
}
|
|
60
|
+
function eraseInlineImages(text, onDestination) {
|
|
61
|
+
const chars = text.split("");
|
|
62
|
+
for (let index = 0; index < text.length - 2; index += 1) {
|
|
63
|
+
if (text[index] !== "!" || text[index + 1] !== "[") continue;
|
|
64
|
+
let labelEnd = index + 2;
|
|
65
|
+
for (; labelEnd < text.length; labelEnd += 1) {
|
|
66
|
+
if (text[labelEnd] === "\\") labelEnd += 1;
|
|
67
|
+
else if (text[labelEnd] === "]") break;
|
|
68
|
+
}
|
|
69
|
+
if (labelEnd >= text.length) continue;
|
|
70
|
+
let open = labelEnd + 1;
|
|
71
|
+
while (text[open] === " " || text[open] === " ") open += 1;
|
|
72
|
+
if (text[open] !== "(") continue;
|
|
73
|
+
let depth = 1;
|
|
74
|
+
let close = open + 1;
|
|
75
|
+
for (; close < text.length && depth > 0; close += 1) {
|
|
76
|
+
if (text[close] === "\\") close += 1;
|
|
77
|
+
else if (text[close] === "(") depth += 1;
|
|
78
|
+
else if (text[close] === ")") depth -= 1;
|
|
79
|
+
}
|
|
80
|
+
if (depth !== 0) continue;
|
|
81
|
+
const value = destination(text.slice(open + 1, close - 1));
|
|
82
|
+
if (value !== null) onDestination(value);
|
|
83
|
+
for (let offset = index; offset < close; offset += 1) chars[offset] = " ";
|
|
84
|
+
index = close - 1;
|
|
85
|
+
}
|
|
86
|
+
return chars.join("");
|
|
87
|
+
}
|
|
88
|
+
function collectInlineLinkDestinations(text, onDestination) {
|
|
89
|
+
const chars = text.split("");
|
|
90
|
+
for (let index = 0; index < text.length - 1; index += 1) {
|
|
91
|
+
if (text[index] !== "[" || text[index - 1] === "!") continue;
|
|
92
|
+
let labelEnd = index + 1;
|
|
93
|
+
for (; labelEnd < text.length; labelEnd += 1) {
|
|
94
|
+
if (text[labelEnd] === "\\") labelEnd += 1;
|
|
95
|
+
else if (text[labelEnd] === "]") break;
|
|
96
|
+
}
|
|
97
|
+
if (labelEnd >= text.length) continue;
|
|
98
|
+
let open = labelEnd + 1;
|
|
99
|
+
while (text[open] === " " || text[open] === " ") open += 1;
|
|
100
|
+
if (text[open] !== "(") continue;
|
|
101
|
+
let depth = 1;
|
|
102
|
+
let close = open + 1;
|
|
103
|
+
for (; close < text.length && depth > 0; close += 1) {
|
|
104
|
+
if (text[close] === "\\") close += 1;
|
|
105
|
+
else if (text[close] === "(") depth += 1;
|
|
106
|
+
else if (text[close] === ")") depth -= 1;
|
|
107
|
+
}
|
|
108
|
+
if (depth !== 0) continue;
|
|
109
|
+
const value = destination(text.slice(open + 1, close - 1));
|
|
110
|
+
if (value !== null) onDestination(value);
|
|
111
|
+
for (let offset = index; offset < close; offset += 1) chars[offset] = " ";
|
|
112
|
+
index = close - 1;
|
|
113
|
+
}
|
|
114
|
+
return chars.join("");
|
|
115
|
+
}
|
|
116
|
+
function trimUrlToken(value) {
|
|
117
|
+
let result = value;
|
|
118
|
+
const pairs = [
|
|
119
|
+
["(", ")"],
|
|
120
|
+
["[", "]"],
|
|
121
|
+
["{", "}"]
|
|
122
|
+
];
|
|
123
|
+
let changed = true;
|
|
124
|
+
while (changed && result.length > 0) {
|
|
125
|
+
changed = false;
|
|
126
|
+
const last = result.at(-1) ?? "";
|
|
127
|
+
if (".,;:!".includes(last)) {
|
|
128
|
+
result = result.slice(0, -1);
|
|
129
|
+
changed = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
for (const [open, close] of pairs) {
|
|
133
|
+
if (last !== close) continue;
|
|
134
|
+
const opens = result.split(open).length - 1;
|
|
135
|
+
const closes = result.split(close).length - 1;
|
|
136
|
+
if (closes > opens) {
|
|
137
|
+
result = result.slice(0, -1);
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function isPublicIpv4(hostname) {
|
|
146
|
+
const parts = hostname.split(".");
|
|
147
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part))) return true;
|
|
148
|
+
const octets = parts.map(Number);
|
|
149
|
+
if (octets.some((part) => part > 255)) return false;
|
|
150
|
+
const [a, b] = octets;
|
|
151
|
+
return !(a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 0 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224);
|
|
152
|
+
}
|
|
153
|
+
function isPublicHostname(value) {
|
|
154
|
+
const hostname = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.+$/, "");
|
|
155
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || hostname.endsWith(".internal") || hostname.endsWith(".lan") || hostname.endsWith(".home"))
|
|
156
|
+
return false;
|
|
157
|
+
if (hostname.includes(":")) {
|
|
158
|
+
const compact = hostname.replace(/^0+/, "");
|
|
159
|
+
return !(compact === "" || compact === "::" || compact === "::1" || compact.startsWith("fc") || compact.startsWith("fd") || /^fe[89ab]/.test(compact) || compact.startsWith("ff") || compact.startsWith("2001:db8:") || compact.startsWith("::ffff:"));
|
|
160
|
+
}
|
|
161
|
+
if (!isPublicIpv4(hostname)) return false;
|
|
162
|
+
if (/^\d+(?:\.\d+){3}$/.test(hostname)) return true;
|
|
163
|
+
return hostname.includes(".");
|
|
164
|
+
}
|
|
165
|
+
function publicHttpUrl(value) {
|
|
166
|
+
if (typeof value !== "string") return null;
|
|
167
|
+
let parsed;
|
|
168
|
+
try {
|
|
169
|
+
parsed = new URL(trimUrlToken(value));
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || !isPublicHostname(parsed.hostname))
|
|
174
|
+
return null;
|
|
175
|
+
return parsed;
|
|
176
|
+
}
|
|
177
|
+
function mediaItem(value) {
|
|
178
|
+
const parsed = publicHttpUrl(value);
|
|
179
|
+
if (parsed === null) return null;
|
|
180
|
+
const imageCdn = parsed.protocol === "https:" && ["images.unsplash.com", "plus.unsplash.com", "images.pexels.com"].includes(parsed.hostname) && !/\.(svg|html?|js)$/i.test(parsed.pathname);
|
|
181
|
+
const kind = IMAGE_EXTENSION.test(parsed.pathname) || imageCdn ? "image" : VIDEO_EXTENSION.test(parsed.pathname) ? "video" : null;
|
|
182
|
+
return kind === null ? null : { kind, url: parsed.href };
|
|
183
|
+
}
|
|
184
|
+
function referenceLabel(value) {
|
|
185
|
+
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
186
|
+
}
|
|
187
|
+
function record(value) {
|
|
188
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
189
|
+
}
|
|
190
|
+
function structuredSearchMedia(meta, expectedTool) {
|
|
191
|
+
if (!record(meta) || !record(meta.lcxHostedMedia)) return [];
|
|
192
|
+
const owned = meta.lcxHostedMedia;
|
|
193
|
+
if (owned.version !== 1 || owned.tool !== expectedTool || !Array.isArray(owned.candidates)) return [];
|
|
194
|
+
const result = [];
|
|
195
|
+
const seen = /* @__PURE__ */ new Set();
|
|
196
|
+
for (const candidate of owned.candidates) {
|
|
197
|
+
if (!record(candidate) || candidate.structured !== true || candidate.kind !== "image" && candidate.kind !== "video") continue;
|
|
198
|
+
const url = publicHttpUrl(candidate.url);
|
|
199
|
+
if (!url || seen.has(url.href)) continue;
|
|
200
|
+
const previewUrl = publicHttpUrl(candidate.previewUrl);
|
|
201
|
+
const sourceUrl = publicHttpUrl(candidate.sourceUrl);
|
|
202
|
+
const poster = publicHttpUrl(candidate.poster);
|
|
203
|
+
seen.add(url.href);
|
|
204
|
+
result.push({
|
|
205
|
+
kind: candidate.kind,
|
|
206
|
+
url: url.href,
|
|
207
|
+
...previewUrl ? { previewUrl: previewUrl.href } : {},
|
|
208
|
+
...sourceUrl ? { sourceUrl: sourceUrl.href } : {},
|
|
209
|
+
...poster ? { poster: poster.href } : {},
|
|
210
|
+
...typeof candidate.caption === "string" && candidate.caption.trim() ? { caption: candidate.caption.trim().slice(0, 500) } : {},
|
|
211
|
+
structured: true
|
|
212
|
+
});
|
|
213
|
+
if (result.length === SEARCH_MEDIA_LIMIT) break;
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
function mergeSearchMedia(structured, fallback) {
|
|
218
|
+
const result = [];
|
|
219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
220
|
+
for (const item of [...structured, ...fallback]) {
|
|
221
|
+
const key = `${item.kind}\0${item.kind === "video" ? item.url : item.url.replace(/#.*$/, "")}`;
|
|
222
|
+
if (seen.has(key)) continue;
|
|
223
|
+
seen.add(key);
|
|
224
|
+
result.push(item);
|
|
225
|
+
if (result.length === SEARCH_MEDIA_LIMIT) break;
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
function extractSearchMedia(text) {
|
|
230
|
+
let visible = stripHtml(stripInlineCode(stripFencedCode(text)));
|
|
231
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
232
|
+
visible.replace(REFERENCE_DEFINITION, (_match, label, angle, bare) => {
|
|
233
|
+
definitions.set(referenceLabel(label), angle ?? bare);
|
|
234
|
+
return _match;
|
|
235
|
+
});
|
|
236
|
+
const nativeImages = /* @__PURE__ */ new Set();
|
|
237
|
+
const addNativeImage = (value) => {
|
|
238
|
+
const item = mediaItem(value);
|
|
239
|
+
if (item?.kind === "image") nativeImages.add(new URL(item.url).href.replace(/#.*$/, ""));
|
|
240
|
+
};
|
|
241
|
+
visible = eraseInlineImages(visible, addNativeImage);
|
|
242
|
+
visible = visible.replace(
|
|
243
|
+
REFERENCE_IMAGE,
|
|
244
|
+
(_match, alt, explicit) => {
|
|
245
|
+
const value = definitions.get(referenceLabel(explicit === void 0 || explicit === "" ? alt : explicit));
|
|
246
|
+
if (value !== void 0) addNativeImage(value);
|
|
247
|
+
return " ".repeat(_match.length);
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
const candidates = [];
|
|
251
|
+
visible = collectInlineLinkDestinations(
|
|
252
|
+
visible,
|
|
253
|
+
(value) => candidates.push(value)
|
|
254
|
+
);
|
|
255
|
+
visible.replace(
|
|
256
|
+
REFERENCE_LINK,
|
|
257
|
+
(_match, prefix, label, explicit) => {
|
|
258
|
+
const value = definitions.get(referenceLabel(explicit === "" ? label : explicit));
|
|
259
|
+
if (value !== void 0) candidates.push(value);
|
|
260
|
+
return _match;
|
|
261
|
+
}
|
|
262
|
+
);
|
|
263
|
+
visible = visible.replace(REFERENCE_DEFINITION, " ");
|
|
264
|
+
for (const match of visible.matchAll(URL_TOKEN)) candidates.push(match[0]);
|
|
265
|
+
const result = [];
|
|
266
|
+
const seen = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const candidate of candidates) {
|
|
268
|
+
const item = mediaItem(candidate);
|
|
269
|
+
if (item === null) continue;
|
|
270
|
+
const key = item.kind === "video" ? item.url : item.url.replace(/#.*$/, "");
|
|
271
|
+
if (nativeImages.has(key) || seen.has(key)) continue;
|
|
272
|
+
seen.add(key);
|
|
273
|
+
result.push(item);
|
|
274
|
+
if (result.length === SEARCH_MEDIA_LIMIT) break;
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/client/inline-media.ts
|
|
280
|
+
var inlineMediaCss = `
|
|
281
|
+
[data-chat-flow-kind="lcx-search-media"]{display:none!important}
|
|
282
|
+
.lcx-media-strip{display:flex;align-items:flex-start;flex-wrap:wrap;gap:8px;margin:8px 0 4px;line-height:1}
|
|
283
|
+
.lcx-media-strip:empty{display:none}
|
|
284
|
+
.lcx-media-strip:not(:has(.lcx-inline-media:not([hidden]))):not(:has(.lcx-media-toggle)){display:none}
|
|
285
|
+
.lcx-media-toggle-row{flex:0 0 100%;min-width:0}
|
|
286
|
+
.lcx-media-toggle{display:flex;align-items:center;justify-content:flex-start;gap:7px;width:fit-content;max-width:100%;min-width:0;padding:2px 0;border:0;border-radius:3px;background:transparent;color:var(--dsw-alias-label-secondary,#748096);font:12px/20px system-ui;cursor:pointer;text-align:left;transition:color .15s}
|
|
287
|
+
.lcx-media-toggle::after{content:'';width:5px;height:5px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:translateY(-2px) rotate(45deg)}
|
|
288
|
+
.lcx-media-toggle[aria-expanded="true"]::after{transform:translateY(1px) rotate(225deg)}
|
|
289
|
+
.lcx-media-toggle:hover{color:var(--dsw-alias-link,#416de0);background:transparent}
|
|
290
|
+
.lcx-inline-media{display:inline-flex;max-width:100%;line-height:1}
|
|
291
|
+
.lcx-inline-media[hidden]{display:none}
|
|
292
|
+
.lcx-media-thumb{display:block;position:relative;max-width:100%;padding:0;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#f5f5f5);color:inherit;cursor:zoom-in;box-shadow:0 2px 6px #00000009;transition:box-shadow .15s,transform .15s,background .15s;font-family:inherit}
|
|
293
|
+
.lcx-media-thumb:hover{box-shadow:0 3px 12px #00000016;transform:translateY(-1px)}
|
|
294
|
+
.lcx-media-thumb img{display:block!important;width:auto!important;height:auto!important;max-width:144px!important;max-height:144px!important;object-fit:contain;margin:0!important;border-radius:0!important}
|
|
295
|
+
.lcx-media-thumb:not([data-video])::after{content:'\u2197';position:absolute;right:6px;bottom:6px;display:grid;place-items:center;width:22px;height:22px;border-radius:7px;background:#14212b99;color:white;font:15px/1 system-ui;backdrop-filter:blur(6px);pointer-events:none}
|
|
296
|
+
.lcx-media-thumb:focus-visible,.lcx-media-close:focus-visible{outline:2px solid var(--dsw-alias-link,#3478f6);outline-offset:3px}
|
|
297
|
+
.lcx-media-thumb[data-video]{cursor:pointer;padding:7px 10px;font-size:12px;font-weight:500;display:flex;align-items:center;gap:8px;line-height:18px;border-radius:10px;color:var(--dsw-alias-link,#416de0);background:color-mix(in srgb,currentColor 7%,transparent);border-color:color-mix(in srgb,currentColor 13%,transparent);box-shadow:none}
|
|
298
|
+
.lcx-media-thumb[data-video]:hover{background:color-mix(in srgb,currentColor 12%,transparent);box-shadow:0 2px 6px #00000008}
|
|
299
|
+
.lcx-media-play-label{display:flex;align-items:center;gap:7px;white-space:nowrap}
|
|
300
|
+
.lcx-media-play-label::before{content:'';width:0;height:0;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:6px solid currentColor}
|
|
301
|
+
.lcx-media-format{font:10px/16px system-ui;letter-spacing:.04em;opacity:.7;border-left:1px solid color-mix(in srgb,currentColor 24%,transparent);padding-left:8px}
|
|
302
|
+
.lcx-media-thumb[data-video]:has(img){padding:0}
|
|
303
|
+
.lcx-media-thumb[data-video]:has(img) .lcx-media-play-label{position:absolute;inset:0;display:flex;justify-content:center;color:white;background:#0003;font-size:0}
|
|
304
|
+
.lcx-media-thumb[data-video]:has(img) .lcx-media-format{display:none}
|
|
305
|
+
.lcx-media-dialog{box-sizing:border-box;border:1px solid #ffffff24;border-radius:16px;padding:48px 16px 16px;background:#15171b;color:#fff;max-width:94vw;max-height:92vh;box-shadow:0 20px 80px #0006}
|
|
306
|
+
.lcx-media-dialog::backdrop{background:#080b12bb;backdrop-filter:blur(5px)}
|
|
307
|
+
.lcx-media-dialog img,.lcx-media-dialog video{display:block;object-fit:contain;max-width:86vw;max-height:74vh;width:auto;height:auto;border-radius:6px;margin:auto}
|
|
308
|
+
.lcx-media-dialog video{width:min(800px,86vw)}
|
|
309
|
+
.lcx-media-close{position:absolute;right:10px;top:8px;width:32px;height:32px;border:0;border-radius:50%;background:#ffffff14;color:white;font-size:23px;cursor:pointer}
|
|
310
|
+
.lcx-media-nav{position:absolute;left:16px;top:8px;display:flex;align-items:center;gap:8px;font:12px/20px system-ui;color:#ddd}
|
|
311
|
+
.lcx-media-nav[hidden]{display:none}
|
|
312
|
+
.lcx-media-nav button{width:30px;height:30px;border:0;border-radius:9px;background:#ffffff14;color:white;font-size:20px;cursor:pointer}
|
|
313
|
+
.lcx-media-nav button:disabled{opacity:.3;cursor:default}
|
|
314
|
+
.lcx-media-nav button:focus-visible,.lcx-media-toggle:focus-visible{outline:2px solid #648dfb;outline-offset:2px}
|
|
315
|
+
.lcx-media-dialog a{display:block;width:fit-content;margin:12px auto 0;color:#c6d7ff;font:12px/18px system-ui;text-decoration:none}
|
|
316
|
+
@media(prefers-reduced-motion:reduce){.lcx-media-thumb{transition:none;transform:none}}
|
|
317
|
+
`;
|
|
318
|
+
var VISIBLE_MEDIA_LIMIT = 4;
|
|
319
|
+
var mediaKey = (item) => item.kind === "video" ? item.url : item.url.replace(/#.*$/, "");
|
|
320
|
+
function previewItems(items) {
|
|
321
|
+
const groups = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const item of items) {
|
|
323
|
+
if (item.kind === "page") continue;
|
|
324
|
+
const url = new URL(item.url);
|
|
325
|
+
const identity = item.kind === "video" ? JSON.stringify(["video", url.origin, url.pathname.replace(/\.(mp4|webm|ogv)$/i, ""), url.search, url.hash]) : JSON.stringify(["image", mediaKey(item)]);
|
|
326
|
+
const group = groups.get(identity) ?? [];
|
|
327
|
+
if (!group.some((value) => value.url === item.url)) group.push(item);
|
|
328
|
+
groups.set(identity, group);
|
|
329
|
+
}
|
|
330
|
+
const result = /* @__PURE__ */ new Map();
|
|
331
|
+
const priority = (url) => ({ mp4: 0, webm: 1, ogv: 2 })[new URL(url).pathname.split(".").at(-1).toLowerCase()] ?? 3;
|
|
332
|
+
for (const group of groups.values()) {
|
|
333
|
+
group.sort((a, b) => priority(a.url) - priority(b.url));
|
|
334
|
+
const item = { ...group[0], sources: group.map((value) => value.url) };
|
|
335
|
+
for (const value of group) result.set(mediaKey(value), item);
|
|
336
|
+
}
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
var activeDialogByDocument = /* @__PURE__ */ new WeakMap();
|
|
340
|
+
function supportsInlineMediaDom(marker) {
|
|
341
|
+
const row = marker.closest('[data-chat-flow-kind="lcx-search-media"]');
|
|
342
|
+
const answer = row?.previousElementSibling;
|
|
343
|
+
const ElementClass = marker.ownerDocument.defaultView?.HTMLElement;
|
|
344
|
+
return Boolean(
|
|
345
|
+
ElementClass && answer instanceof ElementClass && row?.parentElement === answer.parentElement && answer.dataset.chatFlowKind === "assistant-step" && typeof answer.dataset.chatTurn === "string" && answer.dataset.chatTurn !== "" && answer.dataset.chatTurn === row?.dataset.chatTurn
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
function installInlineMedia(marker, items, labels) {
|
|
349
|
+
if (items.length === 0 || !supportsInlineMediaDom(marker)) return () => {
|
|
350
|
+
};
|
|
351
|
+
const row = marker.closest('[data-chat-flow-kind="lcx-search-media"]');
|
|
352
|
+
const answer = row.previousElementSibling;
|
|
353
|
+
const doc = marker.ownerDocument;
|
|
354
|
+
const wanted = previewItems(items.filter((item) => item.structured !== true));
|
|
355
|
+
const structured = [...new Set(previewItems(items.filter((item) => item.structured === true)).values())];
|
|
356
|
+
const previews = /* @__PURE__ */ new Map();
|
|
357
|
+
const strips = /* @__PURE__ */ new Map();
|
|
358
|
+
const entries = /* @__PURE__ */ new Map();
|
|
359
|
+
let expanded = false;
|
|
360
|
+
const toggle = doc.createElement("button");
|
|
361
|
+
toggle.type = "button";
|
|
362
|
+
toggle.className = "lcx-media-toggle";
|
|
363
|
+
const toggleRow = doc.createElement("span");
|
|
364
|
+
toggleRow.className = "lcx-media-toggle-row";
|
|
365
|
+
toggleRow.append(toggle);
|
|
366
|
+
toggle.onclick = () => {
|
|
367
|
+
expanded = !expanded;
|
|
368
|
+
updateVisibility();
|
|
369
|
+
toggle.focus({ preventScroll: true });
|
|
370
|
+
};
|
|
371
|
+
const failed = /* @__PURE__ */ new Set();
|
|
372
|
+
let disposed = false;
|
|
373
|
+
let frame = 0;
|
|
374
|
+
let ownedClose;
|
|
375
|
+
function orderedEntries() {
|
|
376
|
+
return [...answer.querySelectorAll(".lcx-inline-media")].flatMap((preview) => {
|
|
377
|
+
const entry = entries.get(preview);
|
|
378
|
+
return entry ? [{ preview, ...entry }] : [];
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function updateVisibility() {
|
|
382
|
+
const all = orderedEntries();
|
|
383
|
+
all.forEach((entry, index) => {
|
|
384
|
+
const hidden = !expanded && index >= VISIBLE_MEDIA_LIMIT;
|
|
385
|
+
if (entry.preview.hidden !== hidden) entry.preview.hidden = hidden;
|
|
386
|
+
if (!hidden) {
|
|
387
|
+
entry.load?.();
|
|
388
|
+
const saved = entries.get(entry.preview);
|
|
389
|
+
if (saved) saved.load = void 0;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
if (all.length <= VISIBLE_MEDIA_LIMIT) {
|
|
393
|
+
toggleRow.remove();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const text = expanded ? labels.less : labels.more.replace("{count}", String(all.length - VISIBLE_MEDIA_LIMIT));
|
|
397
|
+
if (toggle.textContent !== text) toggle.textContent = text;
|
|
398
|
+
const aria = String(expanded);
|
|
399
|
+
if (toggle.getAttribute("aria-expanded") !== aria) toggle.setAttribute("aria-expanded", aria);
|
|
400
|
+
const parent = all[expanded ? all.length - 1 : VISIBLE_MEDIA_LIMIT - 1].preview.parentElement;
|
|
401
|
+
if (toggleRow.parentElement !== parent || toggleRow !== parent.lastElementChild) parent.append(toggleRow);
|
|
402
|
+
}
|
|
403
|
+
function removePreview(preview) {
|
|
404
|
+
const strip = preview.parentElement;
|
|
405
|
+
entries.get(preview)?.cleanup();
|
|
406
|
+
entries.delete(preview);
|
|
407
|
+
preview.remove();
|
|
408
|
+
if (strip?.classList.contains("lcx-media-strip") && !strip.childElementCount) {
|
|
409
|
+
strip.remove();
|
|
410
|
+
for (const [block, value] of strips) if (value === strip) strips.delete(block);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function placePreview(anchor, preview) {
|
|
414
|
+
const block = anchor.closest("p,li,td,th,figcaption");
|
|
415
|
+
const owner = block && answer.contains(block) ? block : anchor;
|
|
416
|
+
let strip = strips.get(owner);
|
|
417
|
+
if (!strip?.isConnected) {
|
|
418
|
+
strip = doc.createElement("span");
|
|
419
|
+
strip.className = "lcx-media-strip";
|
|
420
|
+
if (owner === anchor) anchor.after(strip);
|
|
421
|
+
else owner.append(strip);
|
|
422
|
+
strips.set(owner, strip);
|
|
423
|
+
}
|
|
424
|
+
strip.append(preview);
|
|
425
|
+
}
|
|
426
|
+
function open(item, trigger, fail) {
|
|
427
|
+
activeDialogByDocument.get(doc)?.();
|
|
428
|
+
const dialog = doc.createElement("dialog");
|
|
429
|
+
dialog.className = "lcx-media-dialog";
|
|
430
|
+
dialog.setAttribute("aria-label", item.kind === "image" ? labels.image : labels.video);
|
|
431
|
+
const close = doc.createElement("button");
|
|
432
|
+
close.type = "button";
|
|
433
|
+
close.className = "lcx-media-close";
|
|
434
|
+
close.textContent = "\xD7";
|
|
435
|
+
close.setAttribute("aria-label", labels.close);
|
|
436
|
+
let media = doc.createElement(item.kind === "image" ? "img" : "video");
|
|
437
|
+
if (media instanceof HTMLImageElement) {
|
|
438
|
+
media.alt = trigger.getAttribute("aria-label") ?? labels.image;
|
|
439
|
+
media.referrerPolicy = "no-referrer";
|
|
440
|
+
} else {
|
|
441
|
+
media.controls = true;
|
|
442
|
+
media.playsInline = true;
|
|
443
|
+
media.preload = "none";
|
|
444
|
+
}
|
|
445
|
+
if (media instanceof HTMLVideoElement) media.src = item.url;
|
|
446
|
+
const source = doc.createElement("a");
|
|
447
|
+
source.href = item.sourceUrl ?? item.url;
|
|
448
|
+
source.target = "_blank";
|
|
449
|
+
source.rel = "noopener noreferrer";
|
|
450
|
+
source.textContent = labels.source;
|
|
451
|
+
let closed = false;
|
|
452
|
+
let sourceIndex = 0;
|
|
453
|
+
let previous;
|
|
454
|
+
let next;
|
|
455
|
+
const cleanup = () => {
|
|
456
|
+
if (closed) return;
|
|
457
|
+
closed = true;
|
|
458
|
+
close.onclick = null;
|
|
459
|
+
dialog.oncancel = null;
|
|
460
|
+
dialog.onclick = null;
|
|
461
|
+
dialog.onkeydown = null;
|
|
462
|
+
previous && (previous.onclick = null);
|
|
463
|
+
next && (next.onclick = null);
|
|
464
|
+
media.onload = null;
|
|
465
|
+
media.onerror = null;
|
|
466
|
+
if (media instanceof HTMLVideoElement) {
|
|
467
|
+
media.pause();
|
|
468
|
+
media.removeAttribute("src");
|
|
469
|
+
media.load();
|
|
470
|
+
}
|
|
471
|
+
dialog.remove();
|
|
472
|
+
if (activeDialogByDocument.get(doc) === cleanup) activeDialogByDocument.delete(doc);
|
|
473
|
+
if (ownedClose === cleanup) ownedClose = void 0;
|
|
474
|
+
if (trigger.isConnected) trigger.focus({ preventScroll: true });
|
|
475
|
+
};
|
|
476
|
+
ownedClose = cleanup;
|
|
477
|
+
activeDialogByDocument.set(doc, cleanup);
|
|
478
|
+
close.onclick = cleanup;
|
|
479
|
+
dialog.oncancel = (event) => {
|
|
480
|
+
event.preventDefault();
|
|
481
|
+
cleanup();
|
|
482
|
+
};
|
|
483
|
+
dialog.onclick = (event) => {
|
|
484
|
+
if (event.target === dialog) {
|
|
485
|
+
const rect = dialog.getBoundingClientRect();
|
|
486
|
+
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) cleanup();
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
media.onerror = () => {
|
|
490
|
+
if (closed) return;
|
|
491
|
+
if (media instanceof HTMLVideoElement && sourceIndex + 1 < item.sources.length) {
|
|
492
|
+
sourceIndex += 1;
|
|
493
|
+
media.src = item.sources[sourceIndex];
|
|
494
|
+
source.href = item.sources[sourceIndex];
|
|
495
|
+
media.load();
|
|
496
|
+
void media.play().catch(() => {
|
|
497
|
+
});
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
cleanup();
|
|
501
|
+
fail();
|
|
502
|
+
};
|
|
503
|
+
dialog.append(close, media, source);
|
|
504
|
+
doc.body.append(dialog);
|
|
505
|
+
if (item.kind === "image") {
|
|
506
|
+
const nav = doc.createElement("span");
|
|
507
|
+
nav.className = "lcx-media-nav";
|
|
508
|
+
const previousButton = doc.createElement("button"), nextButton = doc.createElement("button"), counter = doc.createElement("span");
|
|
509
|
+
previous = previousButton;
|
|
510
|
+
next = nextButton;
|
|
511
|
+
previousButton.type = nextButton.type = "button";
|
|
512
|
+
previousButton.textContent = "\u2039";
|
|
513
|
+
nextButton.textContent = "\u203A";
|
|
514
|
+
previousButton.setAttribute("aria-label", labels.previous);
|
|
515
|
+
nextButton.setAttribute("aria-label", labels.next);
|
|
516
|
+
counter.setAttribute("aria-live", "polite");
|
|
517
|
+
nav.append(previousButton, counter, nextButton);
|
|
518
|
+
dialog.append(nav);
|
|
519
|
+
let currentKey = mediaKey(item);
|
|
520
|
+
const images = () => orderedEntries().filter((entry) => entry.item.kind === "image");
|
|
521
|
+
const select = (index) => {
|
|
522
|
+
if (closed) return;
|
|
523
|
+
const list = images(), entry = list[index];
|
|
524
|
+
if (!entry) return;
|
|
525
|
+
currentKey = mediaKey(entry.item);
|
|
526
|
+
const image = doc.createElement("img");
|
|
527
|
+
image.alt = entry.button.getAttribute("aria-label") ?? labels.image;
|
|
528
|
+
image.referrerPolicy = "no-referrer";
|
|
529
|
+
image.onerror = () => {
|
|
530
|
+
if (closed || media !== image) return;
|
|
531
|
+
entry.fail();
|
|
532
|
+
const remaining = images();
|
|
533
|
+
if (!remaining.length) {
|
|
534
|
+
cleanup();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
select(Math.min(index, remaining.length - 1));
|
|
538
|
+
};
|
|
539
|
+
media.onerror = null;
|
|
540
|
+
media.replaceWith(image);
|
|
541
|
+
media = image;
|
|
542
|
+
source.href = entry.item.sourceUrl ?? entry.item.url;
|
|
543
|
+
image.src = entry.item.url;
|
|
544
|
+
counter.textContent = `${index + 1} / ${list.length}`;
|
|
545
|
+
previousButton.disabled = index === 0;
|
|
546
|
+
nextButton.disabled = index === list.length - 1;
|
|
547
|
+
nav.hidden = list.length < 2;
|
|
548
|
+
};
|
|
549
|
+
const move = (offset) => {
|
|
550
|
+
const list = images();
|
|
551
|
+
select(list.findIndex((entry) => mediaKey(entry.item) === currentKey) + offset);
|
|
552
|
+
};
|
|
553
|
+
previousButton.onclick = () => move(-1);
|
|
554
|
+
nextButton.onclick = () => move(1);
|
|
555
|
+
dialog.onkeydown = (event) => {
|
|
556
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
557
|
+
event.preventDefault();
|
|
558
|
+
move(event.key === "ArrowLeft" ? -1 : 1);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
select(images().findIndex((entry) => mediaKey(entry.item) === mediaKey(item)));
|
|
562
|
+
}
|
|
563
|
+
dialog.showModal();
|
|
564
|
+
close.focus();
|
|
565
|
+
if (media instanceof HTMLVideoElement) void media.play().catch(() => {
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function createPreview(item, description, forget) {
|
|
569
|
+
const key = mediaKey(item);
|
|
570
|
+
if (failed.has(key)) return void 0;
|
|
571
|
+
const preview = doc.createElement("span");
|
|
572
|
+
preview.className = "lcx-inline-media";
|
|
573
|
+
preview.dataset.url = item.url;
|
|
574
|
+
if (item.structured) preview.dataset.structured = "";
|
|
575
|
+
const button = doc.createElement("button");
|
|
576
|
+
button.type = "button";
|
|
577
|
+
button.className = "lcx-media-thumb";
|
|
578
|
+
const label = item.kind === "image" ? labels.image : labels.video;
|
|
579
|
+
button.setAttribute("aria-label", label + " \xB7 " + description);
|
|
580
|
+
let thumbnail;
|
|
581
|
+
const fail = () => {
|
|
582
|
+
failed.add(key);
|
|
583
|
+
removePreview(preview);
|
|
584
|
+
forget?.();
|
|
585
|
+
updateVisibility();
|
|
586
|
+
};
|
|
587
|
+
const cleanup = () => {
|
|
588
|
+
button.onclick = null;
|
|
589
|
+
if (thumbnail) {
|
|
590
|
+
thumbnail.onload = null;
|
|
591
|
+
thumbnail.onerror = null;
|
|
592
|
+
}
|
|
593
|
+
entry.load = void 0;
|
|
594
|
+
};
|
|
595
|
+
const entry = { item, button, fail, cleanup };
|
|
596
|
+
const previewUrl = item.kind === "image" ? item.previewUrl ?? item.url : item.previewUrl ?? item.poster;
|
|
597
|
+
if (previewUrl) {
|
|
598
|
+
const image = doc.createElement("img");
|
|
599
|
+
thumbnail = image;
|
|
600
|
+
image.alt = "";
|
|
601
|
+
image.loading = "lazy";
|
|
602
|
+
image.decoding = "async";
|
|
603
|
+
image.referrerPolicy = "no-referrer";
|
|
604
|
+
button.append(image);
|
|
605
|
+
button.tabIndex = -1;
|
|
606
|
+
button.style.visibility = "hidden";
|
|
607
|
+
preview.style.height = "1px";
|
|
608
|
+
preview.style.margin = "0";
|
|
609
|
+
const reveal = () => {
|
|
610
|
+
button.tabIndex = 0;
|
|
611
|
+
button.style.visibility = "";
|
|
612
|
+
preview.style.height = "";
|
|
613
|
+
preview.style.margin = "";
|
|
614
|
+
};
|
|
615
|
+
image.onload = reveal;
|
|
616
|
+
image.onerror = item.kind === "image" ? fail : () => {
|
|
617
|
+
image.remove();
|
|
618
|
+
reveal();
|
|
619
|
+
};
|
|
620
|
+
entry.load = () => {
|
|
621
|
+
image.src = previewUrl;
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
if (item.kind === "video") {
|
|
625
|
+
button.dataset.video = "";
|
|
626
|
+
const play = doc.createElement("span");
|
|
627
|
+
play.className = "lcx-media-play-label";
|
|
628
|
+
play.textContent = labels.video;
|
|
629
|
+
button.append(play);
|
|
630
|
+
const format = item.sources.map((value) => /\.(mp4|webm|ogv)$/i.exec(new URL(value).pathname)?.[1]).filter(Boolean).join(" / ");
|
|
631
|
+
if (format) {
|
|
632
|
+
const badge = doc.createElement("span");
|
|
633
|
+
badge.className = "lcx-media-format";
|
|
634
|
+
badge.textContent = format.toUpperCase();
|
|
635
|
+
badge.setAttribute("aria-hidden", "true");
|
|
636
|
+
button.append(badge);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
button.onclick = () => open(item, button, fail);
|
|
640
|
+
preview.append(button);
|
|
641
|
+
entries.set(preview, entry);
|
|
642
|
+
return preview;
|
|
643
|
+
}
|
|
644
|
+
for (const item of structured) {
|
|
645
|
+
const preview = createPreview(item, item.caption ?? new URL(item.url).hostname);
|
|
646
|
+
if (!preview) continue;
|
|
647
|
+
let strip = strips.get(answer);
|
|
648
|
+
if (!strip) {
|
|
649
|
+
strip = doc.createElement("span");
|
|
650
|
+
strip.className = "lcx-media-strip";
|
|
651
|
+
answer.append(strip);
|
|
652
|
+
strips.set(answer, strip);
|
|
653
|
+
}
|
|
654
|
+
strip.append(preview);
|
|
655
|
+
}
|
|
656
|
+
const canPreview = (anchor) => !anchor.closest("pre,code,[data-streaming],[data-turn-process-inline],.lcx-inline-media");
|
|
657
|
+
function scan() {
|
|
658
|
+
frame = 0;
|
|
659
|
+
if (disposed) return;
|
|
660
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
661
|
+
const selected = /* @__PURE__ */ new Set();
|
|
662
|
+
for (const anchor of answer.querySelectorAll("a[href]")) {
|
|
663
|
+
if (!canPreview(anchor)) continue;
|
|
664
|
+
let url;
|
|
113
665
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
666
|
+
url = new URL(anchor.href);
|
|
667
|
+
} catch {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) continue;
|
|
671
|
+
const baseItem = wanted.get(url.href.replace(/#.*$/, ""));
|
|
672
|
+
const item = wanted.get(url.href) ?? (baseItem?.kind === "image" ? baseItem : void 0);
|
|
673
|
+
if (!item) continue;
|
|
674
|
+
const key = mediaKey(item);
|
|
675
|
+
if (selected.has(key) || failed.has(key)) continue;
|
|
676
|
+
selected.add(key);
|
|
677
|
+
candidates.set(anchor, { item, url });
|
|
678
|
+
}
|
|
679
|
+
for (const [anchor, current] of previews) {
|
|
680
|
+
if (!candidates.has(anchor) || !current.preview.isConnected || current.href !== anchor.href) {
|
|
681
|
+
removePreview(current.preview);
|
|
682
|
+
previews.delete(anchor);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for (const [anchor, { item, url }] of candidates) {
|
|
686
|
+
if (previews.has(anchor)) continue;
|
|
687
|
+
const preview = createPreview(
|
|
688
|
+
item,
|
|
689
|
+
anchor.textContent?.trim() || url.hostname,
|
|
690
|
+
() => previews.delete(anchor)
|
|
691
|
+
);
|
|
692
|
+
if (!preview) continue;
|
|
693
|
+
placePreview(anchor, preview);
|
|
694
|
+
previews.set(anchor, { preview, href: anchor.href });
|
|
695
|
+
}
|
|
696
|
+
updateVisibility();
|
|
697
|
+
}
|
|
698
|
+
scan();
|
|
699
|
+
const observer = new MutationObserver(() => {
|
|
700
|
+
if (!disposed && !frame) frame = requestAnimationFrame(scan);
|
|
701
|
+
});
|
|
702
|
+
observer.observe(answer, {
|
|
703
|
+
attributes: true,
|
|
704
|
+
attributeFilter: ["data-streaming", "data-turn-process-inline", "href"],
|
|
705
|
+
childList: true,
|
|
706
|
+
subtree: true
|
|
707
|
+
});
|
|
708
|
+
return () => {
|
|
709
|
+
if (disposed) return;
|
|
710
|
+
disposed = true;
|
|
711
|
+
observer.disconnect();
|
|
712
|
+
if (frame) {
|
|
713
|
+
cancelAnimationFrame(frame);
|
|
714
|
+
frame = 0;
|
|
715
|
+
}
|
|
716
|
+
ownedClose?.();
|
|
717
|
+
toggle.onclick = null;
|
|
718
|
+
toggleRow.remove();
|
|
719
|
+
for (const preview of [...entries.keys()]) removePreview(preview);
|
|
720
|
+
previews.clear();
|
|
721
|
+
entries.clear();
|
|
722
|
+
for (const strip of strips.values()) strip.remove();
|
|
723
|
+
strips.clear();
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/search-accounting.ts
|
|
728
|
+
var object = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
729
|
+
var count = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
730
|
+
var zeroBuckets = () => ({ uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 });
|
|
731
|
+
var isSearchTool = (name) => name === "web_search" || name === "websearch_gpt_advanced";
|
|
732
|
+
function auxiliaryUsageOf(event, toolName) {
|
|
733
|
+
if (!object(event) || event.type !== "tool/result" || !object(event.data?.meta)) return [];
|
|
734
|
+
if (!isSearchTool(toolName)) return [];
|
|
735
|
+
const raw = event.data.meta.auxiliaryUsage;
|
|
736
|
+
if (!Array.isArray(raw)) return [];
|
|
737
|
+
const ids = /* @__PURE__ */ new Set(), result = [];
|
|
738
|
+
for (const item of raw) {
|
|
739
|
+
if (!object(item) || typeof item.requestId !== "string" || !item.requestId || typeof item.provider !== "string" || !item.provider || typeof item.model !== "string" || !item.model || !object(item.usage) || ids.has(item.requestId)) continue;
|
|
740
|
+
const u = item.usage;
|
|
741
|
+
if (![u.inputTokens, u.outputTokens, u.totalTokens, u.cacheReadTokens, u.cacheWriteTokens].every(count) || u.inputTokens + u.outputTokens + u.cacheReadTokens + u.cacheWriteTokens !== u.totalTokens) continue;
|
|
742
|
+
ids.add(item.requestId);
|
|
743
|
+
result.push(item);
|
|
744
|
+
}
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
function addUsage(base, records) {
|
|
748
|
+
if (!records.length) return base;
|
|
749
|
+
const next = { ...base };
|
|
750
|
+
for (const { usage: u } of records) {
|
|
751
|
+
next.uncachedInputTokens += u.inputTokens;
|
|
752
|
+
next.outputTokens += u.outputTokens;
|
|
753
|
+
next.cacheReadTokens += u.cacheReadTokens;
|
|
754
|
+
next.cacheWriteTokens += u.cacheWriteTokens;
|
|
755
|
+
}
|
|
756
|
+
if (!Object.values(next).every(count)) throw new Error("LCX search usage exceeds safe counters");
|
|
757
|
+
return next;
|
|
758
|
+
}
|
|
759
|
+
function mergeBuckets(base, extra) {
|
|
760
|
+
if (!object(base) || Object.values(extra).every((n) => n === 0)) return base;
|
|
761
|
+
const next = { ...base };
|
|
762
|
+
for (const key of Object.keys(extra)) {
|
|
763
|
+
if (count(base[key])) next[key] = base[key] + extra[key];
|
|
764
|
+
}
|
|
765
|
+
return next;
|
|
766
|
+
}
|
|
767
|
+
function addTurnUsage(base, records) {
|
|
768
|
+
if (!object(base) || !records.length || !count(base.totalTokens)) return base;
|
|
769
|
+
const next = mergeBuckets(base, addUsage(zeroBuckets(), records));
|
|
770
|
+
next.totalTokens = base.totalTokens + records.reduce((n, r) => n + r.usage.totalTokens, 0);
|
|
771
|
+
delete next.reasoningTokens;
|
|
772
|
+
if (Array.isArray(base.routes)) {
|
|
773
|
+
const routes = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const r of [...base.routes, ...records]) routes.set(`${r.provider}\0${r.model}`, { provider: r.provider, model: r.model });
|
|
775
|
+
next.routes = [...routes.values()];
|
|
776
|
+
}
|
|
777
|
+
return next;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/client/search-usage-ui.ts
|
|
781
|
+
var searchUsageDefinition = {
|
|
782
|
+
kind: "lcx-search-usage",
|
|
783
|
+
match(event) {
|
|
784
|
+
if (event.type === "turn/start") return { id: String(event.data.turn), role: "start" };
|
|
785
|
+
if (event.type === "turn/end" || event.type === "tool/result" || event.type === "tool/call") return { id: String(event.data.turn), role: "update" };
|
|
786
|
+
return null;
|
|
787
|
+
},
|
|
788
|
+
start(_context, match) {
|
|
789
|
+
if (match.event.type !== "turn/start") throw new Error("LCX billing requires a complete turn");
|
|
790
|
+
return { turn: match.event.data.turn, records: [], pending: {}, complete: false };
|
|
791
|
+
},
|
|
792
|
+
update(context, match) {
|
|
793
|
+
const event = match.event, state = context.state, pending = { ...state.pending };
|
|
794
|
+
if (event.type === "tool/call" && isSearchTool(event.data.name)) pending[event.data.callId] = event.data.name;
|
|
795
|
+
const callId = event.type === "tool/result" ? event.data.message.source.callId : void 0;
|
|
796
|
+
const extra = auxiliaryUsageOf(event, callId ? pending[callId] : void 0);
|
|
797
|
+
if (callId) delete pending[callId];
|
|
798
|
+
return { ...state, pending, records: [...state.records, ...extra], complete: event.type === "turn/end" };
|
|
799
|
+
},
|
|
800
|
+
publication: (match) => match.event.type === "turn/end" ? "immediate" : "none",
|
|
801
|
+
buildLocationData(context, scope) {
|
|
802
|
+
const s = context.state;
|
|
803
|
+
return scope === "turn" && s?.complete ? { kind: "turn", turn: s.turn, key: "lcx-search-usage", value: s.records } : null;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
function installUsageSlots(slots, createElement) {
|
|
807
|
+
const cleanups = [];
|
|
808
|
+
for (const [name, key] of [
|
|
809
|
+
["conversation.composer.dock", "stats"],
|
|
810
|
+
["conversation.composer.bar", ""],
|
|
811
|
+
["conversation.chat.node", "turn-tail"]
|
|
812
|
+
]) {
|
|
813
|
+
let effect;
|
|
814
|
+
try {
|
|
815
|
+
effect = slots.inject(name, () => {
|
|
816
|
+
let entries;
|
|
817
|
+
try {
|
|
818
|
+
entries = slots.entriesOfSlot(name);
|
|
819
|
+
} catch {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (!Array.isArray(entries) || entries.length === 0) return;
|
|
823
|
+
const candidates = key === "" ? [entries[0]] : entries.filter((e) => e?.options?.id === key || e?.options?.key === key);
|
|
824
|
+
if (candidates.length !== 1) return;
|
|
825
|
+
const base = candidates[0], original = base?.component;
|
|
826
|
+
if (!base || typeof original !== "function" || !base.options || typeof base.options !== "object") return;
|
|
827
|
+
const descriptor = Object.getOwnPropertyDescriptor(base, "component");
|
|
828
|
+
if (descriptor && descriptor.set === void 0 && descriptor.writable === false) return;
|
|
829
|
+
function WithSearchUsage(props) {
|
|
830
|
+
if (key === "turn-tail") {
|
|
831
|
+
const location = props?.node?.location;
|
|
832
|
+
const records = (location?.kind === "turn" || location?.kind === "step") && location.turn?.data?.get ? location.turn.data.get("lcx-search-usage") : void 0;
|
|
833
|
+
if (!records?.length) return createElement(original, props);
|
|
834
|
+
return createElement(original, { ...props, node: { ...props.node, data: { ...props.node.data, tokenUsage: addTurnUsage(props.node.data.tokenUsage, records) } } });
|
|
835
|
+
}
|
|
836
|
+
if (typeof props?.useProjection !== "function") return createElement(original, props);
|
|
837
|
+
const extra = props.useProjection("lcxSearchUsage");
|
|
838
|
+
const breakdown = props.useProjection("contextBreakdown");
|
|
839
|
+
const useProjection = (projection) => {
|
|
840
|
+
const value = props.useProjection(projection);
|
|
841
|
+
if (projection === "tokenUsage" && extra?.auxiliary) return mergeBuckets(value, extra.auxiliary);
|
|
842
|
+
if (projection === "contextPressure" && extra?.aggregateContext && object(breakdown)) {
|
|
843
|
+
const tokens = breakdown.systemTokens + breakdown.toolsTokens + breakdown.messageTokens;
|
|
844
|
+
if (Number.isSafeInteger(tokens) && tokens >= 0 && object(value)) return { ...value, pressureTokens: tokens, projectedTokens: tokens };
|
|
845
|
+
}
|
|
846
|
+
return value;
|
|
847
|
+
};
|
|
848
|
+
return createElement(original, { ...props, useProjection });
|
|
849
|
+
}
|
|
850
|
+
try {
|
|
851
|
+
base.component = WithSearchUsage;
|
|
852
|
+
} catch {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (base.component !== WithSearchUsage) return;
|
|
856
|
+
return () => {
|
|
857
|
+
if (base.component === WithSearchUsage) base.component = original;
|
|
858
|
+
};
|
|
859
|
+
});
|
|
860
|
+
} catch {
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (typeof effect === "function") cleanups.push(effect);
|
|
864
|
+
}
|
|
865
|
+
return () => {
|
|
866
|
+
for (const dispose of cleanups.reverse()) dispose();
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/client/index.tsx
|
|
871
|
+
var SEARCH_MEDIA_KIND = "lcx-search-media";
|
|
872
|
+
window.__ModuleLoader__.load({
|
|
873
|
+
id: "dsh-lcx-codex",
|
|
874
|
+
factory: (require2) => {
|
|
875
|
+
const module = { exports: {} };
|
|
876
|
+
const exports = module.exports;
|
|
877
|
+
const React = require2("react");
|
|
878
|
+
const { createSnapshotStore } = require2(
|
|
879
|
+
"@deepseek-ai/dsh-client-store"
|
|
880
|
+
);
|
|
881
|
+
const NAMESPACE = "lcx-codex";
|
|
882
|
+
const mediaStore = createSnapshotStore(
|
|
883
|
+
{ enabled: false }
|
|
884
|
+
);
|
|
885
|
+
const FIELDS = [
|
|
886
|
+
"enabled",
|
|
887
|
+
"webSearch",
|
|
888
|
+
"advancedHostedSearch",
|
|
889
|
+
"alphaSearch",
|
|
890
|
+
"grokNativeWebSearch",
|
|
891
|
+
"grokNativeXSearch",
|
|
892
|
+
"searchMediaPreview"
|
|
893
|
+
];
|
|
894
|
+
const DEFAULTS = Object.fromEntries(
|
|
895
|
+
FIELDS.map((field) => [field, false])
|
|
896
|
+
);
|
|
897
|
+
const css = `.lcx-card{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;list-style:none}.lcx-head{width:100%;display:flex;justify-content:space-between;padding:14px 16px;border:0;background:transparent;color:inherit}.lcx-body{border-top:1px solid var(--dsw-alias-border-l2);padding:12px 16px}.lcx-row{display:flex;gap:9px;padding:8px 0}.lcx-row small,.lcx-help{display:block;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.lcx-group{border-top:1px solid var(--dsw-alias-border-l2);margin-top:10px;padding-top:14px}.lcx-group strong{font-size:14px}.lcx-foot{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.lcx-foot button{padding:6px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:transparent;color:inherit}` + inlineMediaCss;
|
|
898
|
+
function mountCss() {
|
|
899
|
+
if (typeof document === "undefined") return () => {
|
|
900
|
+
};
|
|
901
|
+
if (document.querySelector('style[data-plugin-css="dsh-lcx-codex"]'))
|
|
902
|
+
return () => {
|
|
903
|
+
};
|
|
904
|
+
const tag = document.createElement("style");
|
|
905
|
+
tag.dataset.pluginCss = "dsh-lcx-codex";
|
|
906
|
+
tag.textContent = css;
|
|
907
|
+
document.head.appendChild(tag);
|
|
908
|
+
return () => tag.remove();
|
|
909
|
+
}
|
|
910
|
+
const copy = {
|
|
911
|
+
zh: {
|
|
912
|
+
title: "Responses / Codex \u80FD\u529B",
|
|
913
|
+
desc: "LCX \u8DDF\u968F DSH \u5F53\u524D\u4F1A\u8BDD\u9009\u62E9\u7684 GPT Responses route\uFF1B\u6A21\u578B\u3001endpoint \u548C credential \u7EE7\u7EED\u53EA\u7531 DSH \u7BA1\u7406\u3002",
|
|
914
|
+
enabled: "\u542F\u7528 LCX\uFF08\u63A5\u7BA1\u5F53\u524D GPT Responses \u4F1A\u8BDD\uFF09",
|
|
915
|
+
enabledHelp: "\u6B64\u5F00\u5173\u4EC5\u63A5\u7BA1 GPT Responses \u4F1A\u8BDD\uFF1BGrok \u539F\u751F\u641C\u7D22\u7531\u4E0B\u65B9\u72EC\u7ACB\u5F00\u5173\u63A7\u5236\uFF0C\u5176\u4ED6\u975E GPT \u6A21\u578B\u7EE7\u7EED\u4F7F\u7528 DSH \u539F\u751F\u8DEF\u5F84\u3002",
|
|
916
|
+
web: "\u4F7F\u7528 GPT Hosted Search \u4F5C\u4E3A DSH web_search \u540E\u7AEF",
|
|
917
|
+
webHelp: "\u4E0D\u4F1A\u65B0\u589E\u7B2C\u4E8C\u4E2A\u666E\u901A\u641C\u7D22\u5DE5\u5177\uFF1B\u666E\u901A web_search \u81EA\u52A8\u8DDF\u968F\u5F53\u524D Agent \u7684 GPT Responses route\u3002",
|
|
918
|
+
advanced: "\u542F\u7528\u9AD8\u7EA7 Hosted \u5DE5\u5177\uFF08websearch_gpt_advanced\uFF09",
|
|
919
|
+
advancedHelp: "\u53EA\u5728\u9700\u8981\u57DF\u540D\u8FC7\u6EE4\u3001\u4F4D\u7F6E\u3001search context\u3001\u56FE\u7247\u7B49\u539F\u751F Hosted \u53C2\u6570\u65F6\u4F7F\u7528\uFF1B\u9ED8\u8BA4\u5173\u95ED\u4EE5\u4FDD\u6301\u5DE5\u5177 schema \u7A33\u5B9A\u3002",
|
|
920
|
+
alpha: "\u542F\u7528 Alpha command\uFF08websearch_alpha\uFF09",
|
|
921
|
+
alphaHelp: "\u4EC5 capability probe \u5BF9\u5F53\u524D route/schema \u9A8C\u8BC1\u901A\u8FC7\u540E\u624D\u771F\u6B63\u6CE8\u518C\u3002",
|
|
922
|
+
mediaPreview: "\u641C\u7D22\u5A92\u4F53\u9884\u89C8",
|
|
923
|
+
mediaPreviewHelp: "\u5728\u56DE\u7B54\u4E0B\u663E\u793A\u53EF\u7528\u7684\u641C\u7D22\u56FE\u7247\u6216\u76F4\u94FE\u9884\u89C8\uFF0C\u70B9\u51FB\u653E\u5927\u6216\u64AD\u653E\uFF1B\u4E0D\u53EF\u9884\u89C8\u65F6\u4FDD\u7559\u539F\u59CB\u56DE\u7B54\u4E0E\u7F51\u9875\u94FE\u63A5\u3002\u6B64\u8BBE\u7F6E\u53EA\u6539\u53D8\u754C\u9762\u663E\u793A\u3002",
|
|
924
|
+
mediaTitle: "\u5A92\u4F53\u9884\u89C8",
|
|
925
|
+
mediaPlay: "\u64AD\u653E\u89C6\u9891",
|
|
926
|
+
mediaEnlarge: "\u653E\u5927\u56FE\u7247",
|
|
927
|
+
mediaClose: "\u5173\u95ED\u9884\u89C8",
|
|
928
|
+
mediaMore: "\u5C55\u5F00\u5176\u4F59 {count} \u9879",
|
|
929
|
+
mediaLess: "\u6536\u8D77\u9884\u89C8",
|
|
930
|
+
mediaPrevious: "\u4E0A\u4E00\u5F20",
|
|
931
|
+
mediaNext: "\u4E0B\u4E00\u5F20",
|
|
932
|
+
mediaResolve: "\u52A0\u8F7D\u7D20\u6750\u9884\u89C8",
|
|
933
|
+
mediaLoading: "\u6B63\u5728\u83B7\u53D6\u5A92\u4F53\u2026",
|
|
934
|
+
mediaUnavailable: "\u6682\u4E0D\u80FD\u9884\u89C8",
|
|
935
|
+
mediaAll: "\u5168\u90E8",
|
|
936
|
+
mediaImages: "\u56FE\u7247",
|
|
937
|
+
mediaVideos: "\u89C6\u9891",
|
|
938
|
+
mediaImage: "\u56FE\u7247",
|
|
939
|
+
mediaVideo: "\u89C6\u9891",
|
|
940
|
+
mediaFilter: "\u5A92\u4F53\u7C7B\u578B",
|
|
941
|
+
mediaOpen: "\u6253\u5F00\u539F\u59CB\u5A92\u4F53",
|
|
942
|
+
grokTitle: "Grok \u539F\u751F\u641C\u7D22",
|
|
943
|
+
grokDesc: "\u4F7F\u7528 DSH \u5F53\u524D\u9009\u62E9\u7684 Grok \u6A21\u578B\u53CA\u5176\u670D\u52A1\u914D\u7F6E\uFF1B\u4E0E GPT \u529F\u80FD\u5F00\u5173\u72EC\u7ACB\u3002\u5F00\u542F\u4EFB\u4E00\u641C\u7D22\u540E\uFF0CGrok \u4EC5\u4F7F\u7528\u539F\u751F\u641C\u7D22\uFF0C\u7F51\u9875\u8BFB\u53D6\u548C\u5176\u4ED6\u5DE5\u5177\u4ECD\u53EF\u7528\u3002",
|
|
944
|
+
grokWeb: "\u542F\u7528\u539F\u751F Web Search",
|
|
945
|
+
grokWebHelp: "\u4F7F\u7528 Grok \u7684\u539F\u751F\u7F51\u9875\u641C\u7D22\u3002",
|
|
946
|
+
grokX: "\u542F\u7528\u539F\u751F X Search",
|
|
947
|
+
grokXHelp: "\u4F7F\u7528 Grok \u7684\u539F\u751F X \u641C\u7D22\uFF1B\u5355\u72EC\u5F00\u542F\u4E5F\u4E0D\u4F1A\u4F7F\u7528 DSH \u641C\u7D22\u3002",
|
|
948
|
+
save: "\u4FDD\u5B58",
|
|
949
|
+
discard: "\u653E\u5F03\u4FEE\u6539",
|
|
950
|
+
saving: "\u4FDD\u5B58\u4E2D\u2026",
|
|
951
|
+
saveError: "\u672A\u80FD\u786E\u8BA4\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u4FEE\u6539\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u91CD\u8BD5\u6216\u653E\u5F03\u4FEE\u6539\u4EE5\u67E5\u770B\u5F53\u524D\u8BBE\u7F6E\u3002"
|
|
952
|
+
},
|
|
953
|
+
en: {
|
|
954
|
+
title: "Responses / Codex capabilities",
|
|
955
|
+
desc: "LCX follows the GPT Responses route selected by the current DSH session. Model, endpoint and credentials remain DSH-owned.",
|
|
956
|
+
enabled: "Enable LCX (own current GPT Responses conversation)",
|
|
957
|
+
enabledHelp: "Applies only to GPT Responses conversations. Grok native search is controlled independently below; other non-GPT models continue through DSH normally.",
|
|
958
|
+
web: "Use GPT Hosted Search as DSH web_search backend",
|
|
959
|
+
webHelp: "Keeps DSH web_search as the single ordinary search tool and follows the active Agent GPT Responses route.",
|
|
960
|
+
advanced: "Enable advanced Hosted tool (websearch_gpt_advanced)",
|
|
961
|
+
advancedHelp: "Only for native Hosted controls such as domains, location, context size and image search; off by default for stable tool schemas.",
|
|
962
|
+
alpha: "Enable Alpha command (websearch_alpha)",
|
|
963
|
+
alphaHelp: "Registered only after a matching capability probe.",
|
|
964
|
+
mediaPreview: "Search media previews",
|
|
965
|
+
mediaPreviewHelp: "Preview available search images or direct media links below the answer. Unavailable media leaves the original answer and links intact. This setting changes presentation only.",
|
|
966
|
+
mediaTitle: "Media previews",
|
|
967
|
+
mediaPlay: "Play video",
|
|
968
|
+
mediaEnlarge: "Enlarge image",
|
|
969
|
+
mediaClose: "Close preview",
|
|
970
|
+
mediaMore: "Show {count} more",
|
|
971
|
+
mediaLess: "Show fewer",
|
|
972
|
+
mediaPrevious: "Previous image",
|
|
973
|
+
mediaNext: "Next image",
|
|
974
|
+
mediaResolve: "Load media preview",
|
|
975
|
+
mediaLoading: "Resolving media\u2026",
|
|
976
|
+
mediaUnavailable: "Preview unavailable",
|
|
977
|
+
mediaAll: "All",
|
|
978
|
+
mediaImages: "Photos",
|
|
979
|
+
mediaVideos: "Videos",
|
|
980
|
+
mediaImage: "Photo",
|
|
981
|
+
mediaVideo: "Video",
|
|
982
|
+
mediaFilter: "Media type",
|
|
983
|
+
mediaOpen: "Open original media",
|
|
984
|
+
grokTitle: "Grok native search",
|
|
985
|
+
grokDesc: "Uses the Grok model and provider profile currently selected in DSH, independently of the GPT switch. When either search is enabled, Grok uses native search while page reading and other tools remain available.",
|
|
986
|
+
grokWeb: "Enable native Web Search",
|
|
987
|
+
grokWebHelp: "Use Grok's native web search.",
|
|
988
|
+
grokX: "Enable native X Search",
|
|
989
|
+
grokXHelp: "Use Grok's native X search. Enabling it alone also avoids DSH search.",
|
|
990
|
+
save: "Save",
|
|
991
|
+
discard: "Discard",
|
|
992
|
+
saving: "Saving\u2026",
|
|
993
|
+
saveError: "Could not confirm settings were saved. Changes are kept; retry or discard to view current settings."
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
function valueFrom(snapshot) {
|
|
997
|
+
const value = snapshot.value ?? {};
|
|
998
|
+
return {
|
|
999
|
+
...DEFAULTS,
|
|
1000
|
+
...Object.fromEntries(
|
|
1001
|
+
FIELDS.map((field) => [field, Boolean(value[field])])
|
|
1002
|
+
)
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
const hostedMediaTool = (value) => value === "web_search" || value === "websearch_gpt_advanced";
|
|
1006
|
+
const searchMediaDefinition = {
|
|
1007
|
+
kind: SEARCH_MEDIA_KIND,
|
|
1008
|
+
target: "chat",
|
|
1009
|
+
match(event) {
|
|
1010
|
+
if (event.type === "turn/start")
|
|
1011
|
+
return { id: String(event.data.turn), role: "start" };
|
|
1012
|
+
if (event.type === "tool/result" || event.type === "tool/call")
|
|
1013
|
+
return { id: String(event.data.turn), role: "update" };
|
|
1014
|
+
if (event.type !== "assistant/message" || event.surfaceOp !== "append" || event.data.interrupted === true)
|
|
1015
|
+
return null;
|
|
1016
|
+
const source = event.data.message.source;
|
|
1017
|
+
if (source.kind !== "model" || !/^(?:gpt|grok)/i.test(source.model))
|
|
1018
|
+
return null;
|
|
1019
|
+
return { id: String(event.data.turn), role: "update" };
|
|
1020
|
+
},
|
|
1021
|
+
start(_context, match) {
|
|
1022
|
+
if (match.event.type !== "turn/start")
|
|
1023
|
+
throw new Error("search media state requires turn/start");
|
|
1024
|
+
return {
|
|
1025
|
+
anchorSeq: 0,
|
|
1026
|
+
location: match.location,
|
|
1027
|
+
items: [],
|
|
1028
|
+
structuredItems: [],
|
|
1029
|
+
provider: "",
|
|
1030
|
+
model: "",
|
|
1031
|
+
pending: {},
|
|
1032
|
+
ready: false
|
|
1033
|
+
};
|
|
1034
|
+
},
|
|
1035
|
+
update(context, match) {
|
|
1036
|
+
if (match.event.type === "tool/call") {
|
|
1037
|
+
if (!hostedMediaTool(match.event.data.name)) return context.state;
|
|
1038
|
+
return {
|
|
1039
|
+
...context.state,
|
|
1040
|
+
pending: {
|
|
1041
|
+
...context.state.pending,
|
|
1042
|
+
[match.event.data.callId]: match.event.data.name
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
if (match.event.type === "tool/result") {
|
|
1047
|
+
const callId = match.event.data.message.source.callId;
|
|
1048
|
+
const tool = context.state.pending[callId];
|
|
1049
|
+
if (tool === void 0) return context.state;
|
|
1050
|
+
const pending = { ...context.state.pending };
|
|
1051
|
+
delete pending[callId];
|
|
1052
|
+
return {
|
|
1053
|
+
...context.state,
|
|
1054
|
+
pending,
|
|
1055
|
+
structuredItems: mergeSearchMedia(
|
|
1056
|
+
context.state.structuredItems,
|
|
1057
|
+
structuredSearchMedia(match.event.data.meta, tool)
|
|
1058
|
+
)
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
if (match.event.type !== "assistant/message") return context.state;
|
|
1062
|
+
const message = match.event.data.message;
|
|
1063
|
+
const source = message.source;
|
|
1064
|
+
if (source.kind !== "model") return context.state;
|
|
1065
|
+
const text = message.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
1066
|
+
return {
|
|
1067
|
+
...context.state,
|
|
1068
|
+
anchorSeq: match.event.seq,
|
|
1069
|
+
location: match.location,
|
|
1070
|
+
items: context.state.structuredItems.length ? context.state.structuredItems : extractSearchMedia(text),
|
|
1071
|
+
provider: source.provider,
|
|
1072
|
+
model: source.model,
|
|
1073
|
+
ready: true
|
|
1074
|
+
};
|
|
1075
|
+
},
|
|
1076
|
+
publication: (match) => match.event.type === "assistant/message" ? "immediate" : "none",
|
|
1077
|
+
buildViewNode(context) {
|
|
1078
|
+
const state = context.state;
|
|
1079
|
+
if (state === void 0 || !state.ready || state.items.length === 0) return null;
|
|
1080
|
+
const node = {
|
|
1081
|
+
key: context.key,
|
|
1082
|
+
kind: SEARCH_MEDIA_KIND,
|
|
1083
|
+
id: context.id,
|
|
1084
|
+
target: "chat",
|
|
1085
|
+
anchorSeq: state.anchorSeq,
|
|
1086
|
+
location: state.location,
|
|
1087
|
+
visibility: "visible",
|
|
1088
|
+
data: {
|
|
1089
|
+
items: state.items,
|
|
1090
|
+
provider: state.provider,
|
|
1091
|
+
model: state.model
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
return node;
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
class Controller {
|
|
1098
|
+
scope;
|
|
1099
|
+
draft;
|
|
1100
|
+
dirty;
|
|
1101
|
+
saving;
|
|
1102
|
+
saveError;
|
|
1103
|
+
store;
|
|
1104
|
+
stop;
|
|
1105
|
+
constructor(scope) {
|
|
1106
|
+
this.scope = scope;
|
|
1107
|
+
this.draft = null;
|
|
1108
|
+
this.dirty = false;
|
|
1109
|
+
this.saving = false;
|
|
1110
|
+
this.saveError = false;
|
|
1111
|
+
this.store = createSnapshotStore(this.projection());
|
|
1112
|
+
mediaStore.set({ enabled: this.value().searchMediaPreview });
|
|
1113
|
+
this.stop = scope.subscribe(() => {
|
|
1114
|
+
if (!this.dirty) this.draft = null;
|
|
1115
|
+
this.publish();
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
snapshot() {
|
|
1119
|
+
return this.scope.getSnapshot();
|
|
1120
|
+
}
|
|
1121
|
+
value() {
|
|
1122
|
+
return valueFrom(this.snapshot());
|
|
1123
|
+
}
|
|
1124
|
+
draftValue() {
|
|
1125
|
+
return { ...this.value(), ...this.draft ?? {} };
|
|
1126
|
+
}
|
|
1127
|
+
projection() {
|
|
1128
|
+
const s = this.snapshot(), value = this.draftValue(), fields = Object.fromEntries(
|
|
1129
|
+
FIELDS.map((field) => [field, { value: Boolean(value[field]) }])
|
|
1130
|
+
);
|
|
1131
|
+
return {
|
|
1132
|
+
available: s.status === "ready",
|
|
1133
|
+
writable: s.writable,
|
|
1134
|
+
dirty: this.dirty,
|
|
1135
|
+
saving: this.saving,
|
|
1136
|
+
saveError: this.saveError,
|
|
1137
|
+
...fields
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
publish() {
|
|
1141
|
+
mediaStore.set({ enabled: this.value().searchMediaPreview });
|
|
1142
|
+
this.store.set(this.projection());
|
|
1143
|
+
}
|
|
1144
|
+
edit(field, value) {
|
|
1145
|
+
if (!FIELDS.includes(field) || this.saving || !this.snapshot().writable) return;
|
|
1146
|
+
this.draft = {
|
|
1147
|
+
...this.draft,
|
|
1148
|
+
[field]: Boolean(value)
|
|
1149
|
+
};
|
|
1150
|
+
if (this.value()[field] === Boolean(value)) delete this.draft[field];
|
|
1151
|
+
this.dirty = Object.keys(this.draft).length > 0;
|
|
1152
|
+
this.saveError = false;
|
|
1153
|
+
this.publish();
|
|
1154
|
+
}
|
|
1155
|
+
discard() {
|
|
1156
|
+
if (this.saving) return;
|
|
1157
|
+
this.draft = null;
|
|
1158
|
+
this.dirty = false;
|
|
1159
|
+
this.saveError = false;
|
|
1160
|
+
this.publish();
|
|
1161
|
+
}
|
|
1162
|
+
async save() {
|
|
1163
|
+
if (!this.dirty || this.saving || !this.snapshot().writable || this.snapshot().status !== "ready") return;
|
|
1164
|
+
const next = this.draftValue(), prev = this.value();
|
|
1165
|
+
const fields = FIELDS.filter((field) => next[field] !== prev[field]);
|
|
1166
|
+
this.saving = true;
|
|
1167
|
+
this.saveError = false;
|
|
1168
|
+
this.publish();
|
|
1169
|
+
try {
|
|
1170
|
+
if (fields.length) {
|
|
1171
|
+
await this.scope.mutate(fields.map((field) => ({
|
|
1172
|
+
op: "set",
|
|
1173
|
+
path: [field],
|
|
1174
|
+
value: next[field]
|
|
1175
|
+
})));
|
|
1176
|
+
if (this.snapshot().status !== "ready" || fields.some((field) => this.value()[field] !== next[field]))
|
|
1177
|
+
throw new Error("Settings mutation was not confirmed");
|
|
1178
|
+
}
|
|
1179
|
+
this.draft = null;
|
|
1180
|
+
this.dirty = false;
|
|
1181
|
+
} catch {
|
|
1182
|
+
this.saveError = true;
|
|
1183
|
+
} finally {
|
|
1184
|
+
this.saving = false;
|
|
1185
|
+
this.publish();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
inject() {
|
|
1189
|
+
return {
|
|
1190
|
+
hooks: {
|
|
1191
|
+
lcxCard: this.store,
|
|
1192
|
+
mediaPreview: mediaStore
|
|
1193
|
+
},
|
|
1194
|
+
setMediaPreview: (value) => this.edit("searchMediaPreview", value),
|
|
1195
|
+
edit: (field, value) => this.edit(field, value),
|
|
1196
|
+
save: () => void this.save(),
|
|
1197
|
+
discard: () => this.discard()
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function Row({
|
|
1202
|
+
id,
|
|
1203
|
+
label,
|
|
1204
|
+
help,
|
|
1205
|
+
checked,
|
|
1206
|
+
disabled,
|
|
1207
|
+
onChange
|
|
1208
|
+
}) {
|
|
1209
|
+
return React.createElement(
|
|
1210
|
+
"div",
|
|
1211
|
+
{ className: "lcx-row" },
|
|
1212
|
+
React.createElement("input", {
|
|
1213
|
+
id,
|
|
1214
|
+
type: "checkbox",
|
|
1215
|
+
checked,
|
|
1216
|
+
disabled,
|
|
1217
|
+
onChange: (event) => onChange(event.target.checked)
|
|
1218
|
+
}),
|
|
1219
|
+
React.createElement(
|
|
1220
|
+
"label",
|
|
1221
|
+
{ htmlFor: id },
|
|
1222
|
+
label,
|
|
1223
|
+
React.createElement("small", null, help)
|
|
1224
|
+
)
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
function Card(props) {
|
|
1228
|
+
const t = props.t, s = props.useLcxCard((state) => state), [open, setOpen] = React.useState(false);
|
|
1229
|
+
if (!s.available) return null;
|
|
1230
|
+
const disabled = !s.writable || s.saving;
|
|
1231
|
+
return React.createElement(
|
|
1232
|
+
"li",
|
|
1233
|
+
{ className: "lcx-card" },
|
|
1234
|
+
React.createElement(
|
|
1235
|
+
"button",
|
|
1236
|
+
{
|
|
1237
|
+
className: "lcx-head",
|
|
1238
|
+
type: "button",
|
|
1239
|
+
onClick: () => setOpen(!open)
|
|
1240
|
+
},
|
|
1241
|
+
React.createElement("strong", null, t("title")),
|
|
1242
|
+
React.createElement("span", null, open ? "\u2303" : "\u2304")
|
|
1243
|
+
),
|
|
1244
|
+
open ? React.createElement(
|
|
1245
|
+
"div",
|
|
1246
|
+
{ className: "lcx-body" },
|
|
1247
|
+
React.createElement("p", { className: "lcx-help" }, t("desc")),
|
|
1248
|
+
React.createElement(Row, {
|
|
1249
|
+
id: "lcx-media-preview",
|
|
1250
|
+
label: t("mediaPreview"),
|
|
1251
|
+
help: t("mediaPreviewHelp"),
|
|
1252
|
+
checked: s.searchMediaPreview.value,
|
|
1253
|
+
disabled,
|
|
1254
|
+
onChange: props.setMediaPreview
|
|
1255
|
+
}),
|
|
1256
|
+
React.createElement(Row, {
|
|
1257
|
+
id: "lcx-enabled",
|
|
1258
|
+
label: t("enabled"),
|
|
1259
|
+
help: t("enabledHelp"),
|
|
1260
|
+
checked: s.enabled.value,
|
|
1261
|
+
disabled,
|
|
1262
|
+
onChange: (value) => props.edit("enabled", value)
|
|
1263
|
+
}),
|
|
1264
|
+
React.createElement(Row, {
|
|
1265
|
+
id: "lcx-web",
|
|
1266
|
+
label: t("web"),
|
|
1267
|
+
help: t("webHelp"),
|
|
1268
|
+
checked: s.webSearch.value,
|
|
1269
|
+
disabled: disabled || !s.enabled.value,
|
|
1270
|
+
onChange: (value) => props.edit("webSearch", value)
|
|
1271
|
+
}),
|
|
1272
|
+
React.createElement(Row, {
|
|
1273
|
+
id: "lcx-advanced",
|
|
1274
|
+
label: t("advanced"),
|
|
1275
|
+
help: t("advancedHelp"),
|
|
1276
|
+
checked: s.advancedHostedSearch.value,
|
|
1277
|
+
disabled: disabled || !s.enabled.value || !s.webSearch.value,
|
|
1278
|
+
onChange: (value) => props.edit("advancedHostedSearch", value)
|
|
1279
|
+
}),
|
|
1280
|
+
React.createElement(Row, {
|
|
1281
|
+
id: "lcx-alpha",
|
|
1282
|
+
label: t("alpha"),
|
|
1283
|
+
help: t("alphaHelp"),
|
|
1284
|
+
checked: s.alphaSearch.value,
|
|
1285
|
+
disabled: disabled || !s.enabled.value,
|
|
1286
|
+
onChange: (value) => props.edit("alphaSearch", value)
|
|
1287
|
+
}),
|
|
1288
|
+
React.createElement(
|
|
1289
|
+
"section",
|
|
1290
|
+
{ className: "lcx-group" },
|
|
1291
|
+
React.createElement("strong", null, t("grokTitle")),
|
|
1292
|
+
React.createElement("p", { className: "lcx-help" }, t("grokDesc")),
|
|
1293
|
+
React.createElement(Row, {
|
|
1294
|
+
id: "lcx-grok-web",
|
|
1295
|
+
label: t("grokWeb"),
|
|
1296
|
+
help: t("grokWebHelp"),
|
|
1297
|
+
checked: s.grokNativeWebSearch.value,
|
|
1298
|
+
disabled,
|
|
1299
|
+
onChange: (value) => props.edit("grokNativeWebSearch", value)
|
|
1300
|
+
}),
|
|
1301
|
+
React.createElement(Row, {
|
|
1302
|
+
id: "lcx-grok-x",
|
|
1303
|
+
label: t("grokX"),
|
|
1304
|
+
help: t("grokXHelp"),
|
|
1305
|
+
checked: s.grokNativeXSearch.value,
|
|
1306
|
+
disabled,
|
|
1307
|
+
onChange: (value) => props.edit("grokNativeXSearch", value)
|
|
1308
|
+
})
|
|
1309
|
+
),
|
|
1310
|
+
React.createElement(
|
|
1311
|
+
"p",
|
|
1312
|
+
{ role: "alert", hidden: !s.saveError },
|
|
1313
|
+
s.saveError ? t("saveError") : ""
|
|
1314
|
+
),
|
|
1315
|
+
React.createElement(
|
|
1316
|
+
"div",
|
|
1317
|
+
{ className: "lcx-foot" },
|
|
1318
|
+
React.createElement(
|
|
1319
|
+
"button",
|
|
1320
|
+
{ disabled: !s.dirty || disabled, onClick: props.discard },
|
|
1321
|
+
t("discard")
|
|
1322
|
+
),
|
|
1323
|
+
React.createElement(
|
|
1324
|
+
"button",
|
|
1325
|
+
{ disabled: !s.dirty || disabled, onClick: props.save },
|
|
1326
|
+
s.saving ? t("saving") : t("save")
|
|
1327
|
+
)
|
|
1328
|
+
)
|
|
1329
|
+
) : null
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
function InlineMedia({ node, t }) {
|
|
1333
|
+
const [marker, setMarker] = React.useState(null);
|
|
1334
|
+
React.useEffect(() => {
|
|
1335
|
+
if (!marker) return;
|
|
1336
|
+
return installInlineMedia(marker, node.data.items, { image: t("mediaEnlarge"), video: t("mediaPlay"), close: t("mediaClose"), source: t("mediaOpen"), more: t("mediaMore"), less: t("mediaLess"), previous: t("mediaPrevious"), next: t("mediaNext") });
|
|
1337
|
+
}, [marker, node.data.items, t]);
|
|
1338
|
+
return React.createElement("span", { ref: setMarker, "aria-hidden": true });
|
|
1339
|
+
}
|
|
1340
|
+
function MediaNode(props) {
|
|
1341
|
+
const enabled = props.useMediaPreview((state) => state?.enabled === true);
|
|
1342
|
+
return enabled ? React.createElement(InlineMedia, { node: props.node, t: props.t }) : null;
|
|
1343
|
+
}
|
|
1344
|
+
const inject = ["slots", "locale", "settingsScope", "uiConversation"];
|
|
1345
|
+
function apply(ctx) {
|
|
1346
|
+
const slots = ctx.slots ?? ctx.get("slots"), svc = ctx.settingsScope ?? ctx.get("settingsScope"), locale = ctx.locale ?? ctx.get("locale"), uiConversation = ctx.uiConversation ?? ctx.get("uiConversation");
|
|
1347
|
+
if (!slots || !svc || !locale || !uiConversation) return;
|
|
1348
|
+
if (typeof slots.entriesOfSlot === "function") {
|
|
1349
|
+
ctx.effect(() => uiConversation.events.register(searchUsageDefinition), "lcx search billing data");
|
|
1350
|
+
ctx.effect(() => installUsageSlots(slots, React.createElement), "lcx search billing slots");
|
|
1351
|
+
}
|
|
1352
|
+
ctx.effect(mountCss, "lcx-codex styles");
|
|
1353
|
+
for (const language of ["zh", "en"])
|
|
1354
|
+
ctx.effect(() => locale.register(NAMESPACE, language, copy[language]), `lcx-codex ${language} dictionary`);
|
|
1355
|
+
ctx.effect(
|
|
1356
|
+
() => uiConversation.events.register(searchMediaDefinition),
|
|
1357
|
+
"lcx-codex search media definition"
|
|
1358
|
+
);
|
|
1359
|
+
const installSlot = (name, label, register) => ctx.effect(() => {
|
|
1360
|
+
const cleanup = slots.inject(name, register);
|
|
1361
|
+
return () => {
|
|
1362
|
+
if (typeof cleanup === "function") cleanup();
|
|
1363
|
+
};
|
|
1364
|
+
}, label);
|
|
1365
|
+
const controller = new Controller(svc.bind({ namespace: NAMESPACE }));
|
|
1366
|
+
installSlot(
|
|
1367
|
+
"settings.plugin.item",
|
|
1368
|
+
"lcx-codex settings slot",
|
|
1369
|
+
() => slots.register(
|
|
1370
|
+
{
|
|
1371
|
+
name: "settings.plugin.item",
|
|
1372
|
+
key: NAMESPACE,
|
|
1373
|
+
locale: NAMESPACE,
|
|
1374
|
+
inject: () => controller.inject()
|
|
1375
|
+
},
|
|
1376
|
+
Card
|
|
1377
|
+
)
|
|
1378
|
+
);
|
|
1379
|
+
installSlot(
|
|
1380
|
+
"conversation.chat.node",
|
|
1381
|
+
"lcx-codex media slot",
|
|
1382
|
+
() => slots.register(
|
|
1383
|
+
{
|
|
1384
|
+
name: "conversation.chat.node",
|
|
1385
|
+
key: SEARCH_MEDIA_KIND,
|
|
1386
|
+
locale: NAMESPACE,
|
|
1387
|
+
inject: () => ({ hooks: { mediaPreview: mediaStore } })
|
|
1388
|
+
},
|
|
1389
|
+
MediaNode
|
|
1390
|
+
)
|
|
1391
|
+
);
|
|
1392
|
+
ctx.effect(() => () => controller.stop(), "lcx-codex settings card");
|
|
1393
|
+
}
|
|
1394
|
+
exports.apply = apply;
|
|
1395
|
+
exports.inject = inject;
|
|
1396
|
+
return module.exports;
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
})();
|