@wlv-zedd/dsh-btw-plugin 1.0.3 → 1.0.5

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/lib/client.js CHANGED
@@ -1,646 +1,646 @@
1
- window.__ModuleLoader__.load({
2
- id: "@wlv-zedd/dsh-btw-plugin",
3
- factory: (require) => {
4
- var module = { exports: {} };
5
- var exports = module.exports;
6
- const React = require("react");
7
- const { useState, useEffect, useRef } = React;
8
-
9
- const inject = ["slots", "layout"];
10
-
11
- // ── Icons ─────────────────────────────────────────────────────────────
12
- const IconQuestion = () => (
13
- React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" },
14
- React.createElement("circle", { cx: 8, cy: 8, r: 7 }),
15
- React.createElement("path", { d: "M6 6.5C6 5.395 6.895 4.5 8 4.5C9.105 4.5 10 5.395 10 6.5C10 7.37 9.443 8.11 8.651 8.358C8.272 8.477 8 8.826 8 9.222V9.75" }),
16
- React.createElement("circle", { cx: 8, cy: 12, r: 0.75, fill: "currentColor" })
17
- )
18
- );
19
-
20
- const IconChevronLeft = () => (
21
- React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
22
- React.createElement("path", { d: "M10 12L6 8L10 4" })
23
- )
24
- );
25
-
26
- const IconChevronRight = () => (
27
- React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
28
- React.createElement("path", { d: "M6 4L10 8L6 12" })
29
- )
30
- );
31
-
32
- const IconChevronDown = () => (
33
- React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
34
- React.createElement("path", { d: "M4 6L8 10L12 6" })
35
- )
36
- );
37
-
38
- const IconChevronUp = () => (
39
- React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
40
- React.createElement("path", { d: "M4 10L8 6L12 10" })
41
- )
42
- );
43
-
44
- const IconClose = () => (
45
- React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
46
- React.createElement("path", { d: "M4 4L12 12M12 4L4 12" })
47
- )
48
- );
49
-
50
- const IconCopy = () => (
51
- React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" },
52
- React.createElement("rect", { x: 5.5, y: 5.5, width: 8, height: 8, rx: 1.5 }),
53
- React.createElement("path", { d: "M10.5 5.5V3a1.5 1.5 0 00-1.5-1.5H3A1.5 1.5 0 001.5 3v6A1.5 1.5 0 003 10.5h2.5" })
54
- )
55
- );
56
-
57
- const IconCheck = () => (
58
- React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "#22c55e", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
59
- React.createElement("path", { d: "M3.5 8.5l3 3 6-7" })
60
- )
61
- );
62
-
63
- const IconTrash = () => (
64
- React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
65
- React.createElement("path", { d: "M2.5 4.5h11M5.5 4.5V3a1 1 0 011-1h3a1 1 0 011 1v1.5M6.5 7.5v5M9.5 7.5v5M3.5 4.5l.8 9.2a1 1 0 001 .8h5.4a1 1 0 001-.8l.8-9.2" })
66
- )
67
- );
68
-
69
- const IconCpu = () => (
70
- React.createElement("svg", { width: 12, height: 12, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
71
- React.createElement("rect", { x: 3, y: 3, width: 10, height: 10, rx: 2 }),
72
- React.createElement("path", { d: "M6 1v2M10 1v2M6 13v2M10 13v2M1 6h2M1 10h2M13 6h2M13 10h2" })
73
- )
74
- );
75
-
76
- const IconRotateCcw = () => (
77
- React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
78
- React.createElement("path", { d: "M2 8a6 6 0 1 0 1.5-4" }),
79
- React.createElement("path", { d: "M2 3v5h5" })
80
- )
81
- );
82
-
83
- const IconSpinner = ({ size = 12, color = "currentColor" }) => (
84
- React.createElement("svg", {
85
- width: size,
86
- height: size,
87
- viewBox: "0 0 16 16",
88
- fill: "none",
89
- stroke: color,
90
- strokeWidth: 2,
91
- strokeLinecap: "round",
92
- style: { animation: "dsh-spin 0.85s linear infinite", display: "inline-block", flexShrink: 0 }
93
- },
94
- React.createElement("circle", { cx: 8, cy: 8, r: 6, strokeOpacity: 0.25 }),
95
- React.createElement("path", { d: "M14 8a6 6 0 0 0-6-6" })
96
- )
97
- );
98
-
99
- const THINKING_FRAMES = [
100
- "T", "Th", "Thi", "Thin", "Think", "Thinki", "Thinkin", "Thinking",
101
- "Thinking.", "Thinking..", "Thinking..."
102
- ];
103
-
104
- const ThinkingBadge = ({ retryAttempt, maxRetries }) => {
105
- const [frame, setFrame] = useState(0);
106
- const isRetrying = typeof retryAttempt === "number" && retryAttempt > 0;
107
-
108
- useEffect(() => {
109
- if (isRetrying) return;
110
- const timer = setInterval(() => {
111
- setFrame((prev) => (prev + 1) % THINKING_FRAMES.length);
112
- }, 140);
113
- return () => clearInterval(timer);
114
- }, [isRetrying]);
115
-
116
- const badgeColor = isRetrying ? "#f59e0b" : "#3b82f6";
117
- const badgeBg = isRetrying ? "rgba(245, 158, 11, 0.15)" : "rgba(59, 130, 246, 0.15)";
118
- const displayText = isRetrying ? `Retrying (${retryAttempt}/${maxRetries || 5})...` : THINKING_FRAMES[frame];
119
-
120
- return React.createElement("span", {
121
- style: {
122
- display: "inline-flex",
123
- alignItems: "center",
124
- gap: 5,
125
- height: 20,
126
- fontSize: 11,
127
- lineHeight: "20px",
128
- fontWeight: 600,
129
- color: badgeColor,
130
- background: badgeBg,
131
- padding: "0 7px",
132
- borderRadius: 4,
133
- boxSizing: "border-box"
134
- }
135
- },
136
- React.createElement(IconSpinner, { size: 11, color: badgeColor }),
137
- React.createElement("span", {
138
- style: {
139
- display: "inline-block",
140
- minWidth: isRetrying ? 85 : 64,
141
- textAlign: "left"
142
- }
143
- }, displayText)
144
- );
145
- };
146
-
147
- const QuantumEqualizer = ({ isRetrying }) => {
148
- const bars = [
149
- { h: 14, delay: "0.2s" },
150
- { h: 22, delay: "0.4s" },
151
- { h: 28, delay: "0.0s" },
152
- { h: 20, delay: "0.3s" },
153
- { h: 14, delay: "0.5s" }
154
- ];
155
-
156
- const barGradient = isRetrying
157
- ? "linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%)"
158
- : "linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%)";
159
- const barShadow = isRetrying
160
- ? "0 0 8px rgba(245, 158, 11, 0.55)"
161
- : "0 0 8px rgba(59, 130, 246, 0.55)";
162
- const animName = isRetrying ? "dsh-equalizer-pulse-amber" : "dsh-equalizer-pulse";
163
-
164
- return React.createElement("div", {
165
- style: {
166
- display: "flex",
167
- alignItems: "center",
168
- justifyContent: "center",
169
- gap: 6,
170
- width: "100%",
171
- height: 36,
172
- padding: "4px 0",
173
- boxSizing: "border-box"
174
- }
175
- },
176
- bars.map((bar, idx) =>
177
- React.createElement("div", {
178
- key: idx,
179
- style: {
180
- width: 3.5,
181
- height: bar.h,
182
- borderRadius: 999,
183
- background: barGradient,
184
- boxShadow: barShadow,
185
- animation: `${animName} 1.15s ease-in-out infinite`,
186
- animationDelay: bar.delay,
187
- transformOrigin: "center"
188
- }
189
- })
190
- )
191
- );
192
- };
193
-
194
- if (typeof document !== "undefined" && !document.getElementById("dsh-btw-spin-style")) {
195
- const style = document.createElement("style");
196
- style.id = "dsh-btw-spin-style";
197
- style.textContent = `
198
- @keyframes dsh-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
199
- @keyframes dsh-equalizer-pulse {
200
- 0%, 100% { transform: scaleY(0.28); opacity: 0.45; }
201
- 50% { transform: scaleY(1); opacity: 1; filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.85)); }
202
- }
203
- @keyframes dsh-equalizer-pulse-amber {
204
- 0%, 100% { transform: scaleY(0.28); opacity: 0.45; }
205
- 50% { transform: scaleY(1); opacity: 1; filter: drop-shadow(0 0 6px rgba(245, 158, 11, 0.85)); }
206
- }
207
- `;
208
- document.head.appendChild(style);
209
- }
210
-
211
- // ── Markdown Parser & Formatter ──────────────────────────────────────
212
- function renderInlineMarkdown(text) {
213
- if (!text) return null;
214
- const tokens = [];
215
- let key = 0;
216
- const regex = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\([^)]+\))/g;
217
- let match;
218
- let lastIndex = 0;
219
-
220
- while ((match = regex.exec(text)) !== null) {
221
- if (match.index > lastIndex) {
222
- tokens.push(text.substring(lastIndex, match.index));
223
- }
224
- const token = match[0];
225
- if (token.startsWith("`") && token.endsWith("`")) {
226
- tokens.push(
227
- React.createElement("code", {
228
- key: key++,
229
- style: {
230
- fontFamily: "var(--dsh-font-mono, monospace)",
231
- fontSize: "0.9em",
232
- padding: "1px 5px",
233
- background: "rgba(255, 255, 255, 0.1)",
234
- borderRadius: 4,
235
- color: "#e2e8f0"
236
- }
237
- }, token.slice(1, -1))
238
- );
239
- } else if (token.startsWith("**") && token.endsWith("**")) {
240
- tokens.push(
241
- React.createElement("strong", {
242
- key: key++,
243
- style: { fontWeight: 700, color: "var(--dsw-alias-label-primary, #ffffff)" }
244
- }, token.slice(2, -2))
245
- );
246
- } else if (token.startsWith("*") && token.endsWith("*")) {
247
- tokens.push(
248
- React.createElement("em", {
249
- key: key++,
250
- style: { fontStyle: "italic" }
251
- }, token.slice(1, -1))
252
- );
253
- } else if (token.startsWith("[") && token.includes("](")) {
254
- const linkMatch = token.match(/\[([^\]]+)\]\(([^)]+)\)/);
255
- if (linkMatch) {
256
- tokens.push(
257
- React.createElement("a", {
258
- key: key++,
259
- href: linkMatch[2],
260
- target: "_blank",
261
- rel: "noopener noreferrer",
262
- style: { color: "#3b82f6", textDecoration: "underline" }
263
- }, linkMatch[1])
264
- );
265
- } else {
266
- tokens.push(token);
267
- }
268
- } else {
269
- tokens.push(token);
270
- }
271
- lastIndex = regex.lastIndex;
272
- }
273
-
274
- if (lastIndex < text.length) {
275
- tokens.push(text.substring(lastIndex));
276
- }
277
-
278
- return tokens.length > 0 ? tokens : text;
279
- }
280
-
281
- function renderTextChunk(chunk, keyPrefix) {
282
- const lines = chunk.split("\n");
283
- const elements = [];
284
- let k = 0;
285
-
286
- for (let i = 0; i < lines.length; i++) {
287
- const line = lines[i];
288
- const trimmed = line.trim();
289
- if (!trimmed) {
290
- if (i > 0 && i < lines.length - 1) {
291
- elements.push(React.createElement("div", { key: `${keyPrefix}-${k++}`, style: { height: 6 } }));
292
- }
293
- continue;
294
- }
295
-
296
- const listMatch = line.match(/^(\s*)([-*]|\d+\.)\s+(.+)$/);
297
- if (listMatch) {
298
- const indent = listMatch[1].length * 10;
299
- const marker = listMatch[2].endsWith(".") ? listMatch[2] : "•";
300
- elements.push(
301
- React.createElement("div", {
302
- key: `${keyPrefix}-${k++}`,
303
- style: {
304
- display: "flex",
305
- alignItems: "flex-start",
306
- gap: 6,
307
- marginLeft: indent + 6,
308
- margin: "2px 0"
309
- }
310
- },
311
- React.createElement("span", {
312
- style: {
313
- color: "#3b82f6",
314
- fontWeight: 600,
315
- fontSize: 12,
316
- minWidth: marker.length > 1 ? 16 : 10,
317
- userSelect: "none",
318
- flexShrink: 0
319
- }
320
- }, marker),
321
- React.createElement("div", { style: { flex: 1 } }, renderInlineMarkdown(listMatch[3]))
322
- )
323
- );
324
- } else {
325
- elements.push(
326
- React.createElement("div", {
327
- key: `${keyPrefix}-${k++}`,
328
- style: { margin: "2px 0" }
329
- }, renderInlineMarkdown(line))
330
- );
331
- }
332
- }
333
-
334
- return React.createElement("div", { key: `chunk-${keyPrefix}` }, elements);
335
- }
336
-
337
- function renderMarkdownBlocks(markdownText) {
338
- if (!markdownText) return null;
339
- const blocks = [];
340
- const codeBlockRegex = /```([a-zA-Z0-9_-]*)\n([\s\S]*?)```/g;
341
- let lastIndex = 0;
342
- let match;
343
- let blockKey = 0;
344
-
345
- while ((match = codeBlockRegex.exec(markdownText)) !== null) {
346
- if (match.index > lastIndex) {
347
- const textChunk = markdownText.substring(lastIndex, match.index);
348
- blocks.push(renderTextChunk(textChunk, blockKey++));
349
- }
350
- const lang = match[1];
351
- const code = match[2];
352
- blocks.push(
353
- React.createElement("pre", {
354
- key: blockKey++,
355
- style: {
356
- background: "rgba(0, 0, 0, 0.4)",
357
- border: "0.5px solid rgba(255, 255, 255, 0.1)",
358
- borderRadius: 6,
359
- padding: "8px 10px",
360
- margin: "6px 0",
361
- overflowX: "auto",
362
- fontSize: 12,
363
- fontFamily: "var(--dsh-font-mono, monospace)",
364
- color: "#e2e8f0"
365
- }
366
- }, React.createElement("code", null, code.trim()))
367
- );
368
- lastIndex = codeBlockRegex.lastIndex;
369
- }
370
-
371
- if (lastIndex < markdownText.length) {
372
- const textChunk = markdownText.substring(lastIndex);
373
- blocks.push(renderTextChunk(textChunk, blockKey++));
374
- }
375
-
376
- return blocks;
377
- }
378
-
379
- // ── Floating Docked Banner Component ───────────────────────────────────
380
- function BtwDockedBanner({ sessionId }) {
381
- const [items, setItems] = useState([]);
382
- const [currentIndex, setCurrentIndex] = useState(0);
383
- const [dismissedId, setDismissedId] = useState(null);
384
- const [copied, setCopied] = useState(false);
385
- const [collapsed, setCollapsed] = useState(false);
386
- const [models, setModels] = useState([{ id: "auto", name: "Inherit Session Model" }]);
387
- const [selectedModel, setSelectedModel] = useState("auto");
388
- const prevCountRef = useRef(0);
389
-
390
- useEffect(() => {
391
- fetch('/api/dsh-btw/models')
392
- .then(res => res.json())
393
- .then(data => {
394
- if (data.ok && Array.isArray(data.models)) {
395
- setModels(data.models);
396
- if (data.currentModel) setSelectedModel(data.currentModel);
397
- }
398
- })
399
- .catch(() => {});
400
- }, []);
401
-
402
- const handleModelChange = (e) => {
403
- e.stopPropagation();
404
- const nextModel = e.target.value;
405
- setSelectedModel(nextModel);
406
- fetch('/api/dsh-btw/model', {
407
- method: 'POST',
408
- headers: { 'Content-Type': 'application/json' },
409
- body: JSON.stringify({ model: nextModel })
410
- }).catch(() => {});
411
- };
412
-
413
- const fetchFeed = () => {
414
- if (!sessionId) return;
415
- fetch(`/api/dsh-btw/feed?sessionId=${encodeURIComponent(sessionId)}`)
416
- .then(res => res.json())
417
- .then(data => {
418
- if (data.ok && Array.isArray(data.items)) {
419
- setItems(data.items);
420
- if (data.items.length > prevCountRef.current) {
421
- setDismissedId(null);
422
- setCurrentIndex(0);
423
- setCollapsed(false);
424
- }
425
- prevCountRef.current = data.items.length;
426
- }
427
- })
428
- .catch(() => {});
429
- };
430
-
431
- useEffect(() => {
432
- fetchFeed();
433
- const hasLoading = items.some(i => i.loading);
434
- const interval = setInterval(fetchFeed, hasLoading ? 500 : 2500);
435
- return () => clearInterval(interval);
436
- }, [sessionId, items]);
437
-
438
- if (!sessionId || items.length === 0) return null;
439
- const currentItem = items[currentIndex];
440
- if (!currentItem || currentItem.id === dismissedId) return null;
441
-
442
- const total = items.length;
443
- const hasOlder = currentIndex < total - 1;
444
- const hasNewer = currentIndex > 0;
445
-
446
- const handleCopy = (e) => {
447
- e.stopPropagation();
448
- if (currentItem.loading) return;
449
- navigator.clipboard.writeText(currentItem.answer).then(() => {
450
- setCopied(true);
451
- setTimeout(() => setCopied(false), 2000);
452
- });
453
- };
454
-
455
- const handleDelete = (e) => {
456
- e.stopPropagation();
457
- const idToDelete = currentItem.id;
458
- setItems(prev => prev.filter(i => i.id !== idToDelete));
459
- if (currentIndex >= items.length - 1 && currentIndex > 0) setCurrentIndex(i => i - 1);
460
- fetch('/api/dsh-btw/delete', {
461
- method: 'POST',
462
- headers: { 'Content-Type': 'application/json' },
463
- body: JSON.stringify({ sessionId, itemId: idToDelete })
464
- }).catch(() => {});
465
- };
466
-
467
- const handleRetry = (e) => {
468
- if (e) e.stopPropagation();
469
- if (currentItem.loading) return;
470
- setItems(prev => prev.map(i => i.id === currentItem.id ? { ...i, loading: true, answer: "" } : i));
471
- fetch('/api/dsh-btw/retry', {
472
- method: 'POST',
473
- headers: { 'Content-Type': 'application/json' },
474
- body: JSON.stringify({ sessionId, itemId: currentItem.id })
475
- }).catch(() => {});
476
- };
477
-
478
- const formattedDate = new Date(currentItem.timestamp).toLocaleDateString('en-US', {
479
- month: 'long', day: 'numeric', year: 'numeric'
480
- });
481
-
482
- return React.createElement("section", {
483
- style: {
484
- boxSizing: "border-box", display: "flex", flexDirection: "column", gap: 8,
485
- margin: "0 auto 8px auto", width: "100%", maxWidth: "var(--dsh-composer-card-max-width, 768px)",
486
- padding: "8px 12px", background: "var(--dsw-specific-tip, #18181b)",
487
- border: "0.5px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.1))",
488
- borderRadius: 12, boxShadow: "0 4px 16px rgba(0, 0, 0, 0.25)"
489
- }
490
- },
491
- React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, minHeight: 24 } },
492
- React.createElement("button", {
493
- type: "button",
494
- onClick: () => setCollapsed(c => !c),
495
- style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0, flex: 1, padding: 0, border: "none", background: "transparent", cursor: "pointer", color: "inherit", textAlign: "left" }
496
- },
497
- React.createElement("span", { style: { color: "#3b82f6", display: "inline-flex" } }, React.createElement(IconQuestion)),
498
- React.createElement("span", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #f4f4f5)" } }, "Side Question:"),
499
- React.createElement("span", {
500
- title: currentItem.question,
501
- style: { fontSize: 13, color: "var(--dsw-alias-label-secondary, #a1a1aa)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, cursor: "default" }
502
- }, currentItem.question),
503
- currentItem.loading
504
- ? React.createElement(ThinkingBadge, {
505
- retryAttempt: currentItem.retryAttempt,
506
- maxRetries: currentItem.maxRetries
507
- })
508
- : React.createElement("span", {
509
- style: {
510
- display: "inline-flex",
511
- alignItems: "center",
512
- height: 20,
513
- fontSize: 11,
514
- lineHeight: "20px",
515
- color: "var(--dsw-alias-label-tertiary, #71717a)",
516
- background: "rgba(255, 255, 255, 0.06)",
517
- padding: "0 6px",
518
- borderRadius: 4,
519
- boxSizing: "border-box"
520
- }
521
- }, formattedDate)
522
- ),
523
- React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } },
524
- React.createElement("div", {
525
- style: {
526
- display: "inline-flex",
527
- alignItems: "center",
528
- gap: 4,
529
- height: 20,
530
- padding: "0 6px",
531
- borderRadius: 4,
532
- background: "rgba(255, 255, 255, 0.06)",
533
- border: "0.5px solid rgba(255, 255, 255, 0.12)",
534
- color: "var(--dsw-alias-label-secondary, #a1a1aa)",
535
- fontSize: 11,
536
- boxSizing: "border-box"
537
- }
538
- },
539
- React.createElement("span", { style: { display: "inline-flex", color: "#3b82f6" } }, React.createElement(IconCpu)),
540
- React.createElement("select", {
541
- value: selectedModel,
542
- onChange: handleModelChange,
543
- onClick: (e) => e.stopPropagation(),
544
- title: "Select Model for BTW Side Questions",
545
- style: {
546
- background: "transparent",
547
- border: "none",
548
- color: "inherit",
549
- fontSize: 11,
550
- fontFamily: "inherit",
551
- outline: "none",
552
- cursor: "pointer",
553
- maxWidth: 140,
554
- height: 18,
555
- lineHeight: "18px",
556
- padding: 0,
557
- margin: 0,
558
- textOverflow: "ellipsis"
559
- }
560
- },
561
- models.map(m => React.createElement("option", {
562
- key: m.id,
563
- value: m.id,
564
- style: { background: "#18181b", color: "#f4f4f5" }
565
- }, m.name))
566
- )
567
- ),
568
- React.createElement("div", { style: { display: "inline-flex", alignItems: "center", gap: 2, marginRight: 4, fontSize: 11, color: "#71717a" } },
569
- React.createElement("button", { type: "button", disabled: !hasOlder, onClick: () => hasOlder && setCurrentIndex(i => i + 1), style: { border: "none", background: "transparent", cursor: hasOlder ? "pointer" : "default", color: "inherit", opacity: hasOlder ? 1 : 0.3 } }, React.createElement(IconChevronLeft)),
570
- React.createElement("span", { style: { fontVariantNumeric: "tabular-nums" } }, `${total - currentIndex}/${total}`),
571
- React.createElement("button", { type: "button", disabled: !hasNewer, onClick: () => hasNewer && setCurrentIndex(i => i - 1), style: { border: "none", background: "transparent", cursor: hasNewer ? "pointer" : "default", color: "inherit", opacity: hasNewer ? 1 : 0.3 } }, React.createElement(IconChevronRight))
572
- ),
573
- (currentItem.answer && currentItem.answer.startsWith("Unable to resolve side question:")) &&
574
- React.createElement("button", {
575
- type: "button",
576
- onClick: handleRetry,
577
- disabled: currentItem.loading,
578
- title: "Retry query with selected model",
579
- style: { border: "none", background: "transparent", cursor: "pointer", color: "#fca5a5", padding: 4, display: "inline-flex", alignItems: "center" }
580
- }, React.createElement(IconRotateCcw)),
581
- React.createElement("button", { type: "button", onClick: handleCopy, disabled: currentItem.loading, title: copied ? "Copied!" : "Copy answer", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, copied ? React.createElement(IconCheck) : React.createElement(IconCopy)),
582
- React.createElement("button", { type: "button", onClick: handleDelete, title: "Delete question", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, React.createElement(IconTrash)),
583
- React.createElement("button", { type: "button", onClick: () => setCollapsed(c => !c), title: collapsed ? "Expand" : "Collapse", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, collapsed ? React.createElement(IconChevronDown) : React.createElement(IconChevronUp)),
584
- React.createElement("button", { type: "button", onClick: (e) => { e.stopPropagation(); setDismissedId(currentItem.id); }, title: "Close banner", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, React.createElement(IconClose))
585
- )
586
- ),
587
- !collapsed && React.createElement("div", {
588
- style: {
589
- fontSize: 13,
590
- lineHeight: "20px",
591
- color: "var(--dsw-alias-label-primary, #e4e4e7)",
592
- wordBreak: "break-word",
593
- paddingTop: 8,
594
- paddingBottom: 4,
595
- borderTop: "0.5px solid rgba(255, 255, 255, 0.08)",
596
- overflow: "hidden"
597
- }
598
- }, currentItem.loading
599
- ? React.createElement(QuantumEqualizer, {
600
- isRetrying: Boolean(currentItem.retryAttempt && currentItem.retryAttempt > 0)
601
- })
602
- : (currentItem.answer && currentItem.answer.startsWith("Unable to resolve side question:")
603
- ? React.createElement("div", {
604
- style: {
605
- color: "#fca5a5",
606
- background: "rgba(239, 68, 68, 0.1)",
607
- border: "0.5px solid rgba(239, 68, 68, 0.25)",
608
- borderRadius: 6,
609
- padding: "8px 10px",
610
- lineHeight: "18px"
611
- }
612
- },
613
- React.createElement("span", { style: { flex: 1 } }, currentItem.answer)
614
- )
615
- : renderMarkdownBlocks(currentItem.answer)
616
- )
617
- )
618
- );
619
- }
620
-
621
- // ── Cordis Slot Registration ──────────────────────────────────────────
622
- function apply(ctx) {
623
- // 1. Injects floating banner above message composer
624
- ctx.slots.inject("conversation.input.dock", () =>
625
- ctx.slots.register({
626
- name: "conversation.input.dock",
627
- id: "dsh-btw-banner",
628
- order: 15,
629
- inject: (sessionId) => ({ sessionId })
630
- }, BtwDockedBanner)
631
- );
632
-
633
- // 2. Suppress /btw command row in chat transcript
634
- ctx.slots.inject("conversation.chat.commandview", () =>
635
- ctx.slots.register({
636
- name: "conversation.chat.commandview",
637
- key: "btw"
638
- }, () => null)
639
- );
640
- }
641
-
642
- exports.apply = apply;
643
- exports.inject = inject;
644
- return module.exports;
645
- }
646
- });
1
+ window.__ModuleLoader__.load({
2
+ id: "@wlv-zedd/dsh-btw-plugin",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ const React = require("react");
7
+ const { useState, useEffect, useRef } = React;
8
+
9
+ const inject = ["slots"];
10
+
11
+ // ── Icons ─────────────────────────────────────────────────────────────
12
+ const IconQuestion = () => (
13
+ React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" },
14
+ React.createElement("circle", { cx: 8, cy: 8, r: 7 }),
15
+ React.createElement("path", { d: "M6 6.5C6 5.395 6.895 4.5 8 4.5C9.105 4.5 10 5.395 10 6.5C10 7.37 9.443 8.11 8.651 8.358C8.272 8.477 8 8.826 8 9.222V9.75" }),
16
+ React.createElement("circle", { cx: 8, cy: 12, r: 0.75, fill: "currentColor" })
17
+ )
18
+ );
19
+
20
+ const IconChevronLeft = () => (
21
+ React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
22
+ React.createElement("path", { d: "M10 12L6 8L10 4" })
23
+ )
24
+ );
25
+
26
+ const IconChevronRight = () => (
27
+ React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
28
+ React.createElement("path", { d: "M6 4L10 8L6 12" })
29
+ )
30
+ );
31
+
32
+ const IconChevronDown = () => (
33
+ React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
34
+ React.createElement("path", { d: "M4 6L8 10L12 6" })
35
+ )
36
+ );
37
+
38
+ const IconChevronUp = () => (
39
+ React.createElement("svg", { width: 14, height: 14, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
40
+ React.createElement("path", { d: "M4 10L8 6L12 10" })
41
+ )
42
+ );
43
+
44
+ const IconClose = () => (
45
+ React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
46
+ React.createElement("path", { d: "M4 4L12 12M12 4L4 12" })
47
+ )
48
+ );
49
+
50
+ const IconCopy = () => (
51
+ React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round" },
52
+ React.createElement("rect", { x: 5.5, y: 5.5, width: 8, height: 8, rx: 1.5 }),
53
+ React.createElement("path", { d: "M10.5 5.5V3a1.5 1.5 0 00-1.5-1.5H3A1.5 1.5 0 001.5 3v6A1.5 1.5 0 003 10.5h2.5" })
54
+ )
55
+ );
56
+
57
+ const IconCheck = () => (
58
+ React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "#22c55e", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" },
59
+ React.createElement("path", { d: "M3.5 8.5l3 3 6-7" })
60
+ )
61
+ );
62
+
63
+ const IconTrash = () => (
64
+ React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
65
+ React.createElement("path", { d: "M2.5 4.5h11M5.5 4.5V3a1 1 0 011-1h3a1 1 0 011 1v1.5M6.5 7.5v5M9.5 7.5v5M3.5 4.5l.8 9.2a1 1 0 001 .8h5.4a1 1 0 001-.8l.8-9.2" })
66
+ )
67
+ );
68
+
69
+ const IconCpu = () => (
70
+ React.createElement("svg", { width: 12, height: 12, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
71
+ React.createElement("rect", { x: 3, y: 3, width: 10, height: 10, rx: 2 }),
72
+ React.createElement("path", { d: "M6 1v2M10 1v2M6 13v2M10 13v2M1 6h2M1 10h2M13 6h2M13 10h2" })
73
+ )
74
+ );
75
+
76
+ const IconRotateCcw = () => (
77
+ React.createElement("svg", { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" },
78
+ React.createElement("path", { d: "M2 8a6 6 0 1 0 1.5-4" }),
79
+ React.createElement("path", { d: "M2 3v5h5" })
80
+ )
81
+ );
82
+
83
+ const IconSpinner = ({ size = 12, color = "currentColor" }) => (
84
+ React.createElement("svg", {
85
+ width: size,
86
+ height: size,
87
+ viewBox: "0 0 16 16",
88
+ fill: "none",
89
+ stroke: color,
90
+ strokeWidth: 2,
91
+ strokeLinecap: "round",
92
+ style: { animation: "dsh-spin 0.85s linear infinite", display: "inline-block", flexShrink: 0 }
93
+ },
94
+ React.createElement("circle", { cx: 8, cy: 8, r: 6, strokeOpacity: 0.25 }),
95
+ React.createElement("path", { d: "M14 8a6 6 0 0 0-6-6" })
96
+ )
97
+ );
98
+
99
+ const THINKING_FRAMES = [
100
+ "T", "Th", "Thi", "Thin", "Think", "Thinki", "Thinkin", "Thinking",
101
+ "Thinking.", "Thinking..", "Thinking..."
102
+ ];
103
+
104
+ const ThinkingBadge = ({ retryAttempt, maxRetries }) => {
105
+ const [frame, setFrame] = useState(0);
106
+ const isRetrying = typeof retryAttempt === "number" && retryAttempt > 0;
107
+
108
+ useEffect(() => {
109
+ if (isRetrying) return;
110
+ const timer = setInterval(() => {
111
+ setFrame((prev) => (prev + 1) % THINKING_FRAMES.length);
112
+ }, 140);
113
+ return () => clearInterval(timer);
114
+ }, [isRetrying]);
115
+
116
+ const badgeColor = isRetrying ? "#f59e0b" : "#3b82f6";
117
+ const badgeBg = isRetrying ? "rgba(245, 158, 11, 0.15)" : "rgba(59, 130, 246, 0.15)";
118
+ const displayText = isRetrying ? `Retrying (${retryAttempt}/${maxRetries || 5})...` : THINKING_FRAMES[frame];
119
+
120
+ return React.createElement("span", {
121
+ style: {
122
+ display: "inline-flex",
123
+ alignItems: "center",
124
+ gap: 5,
125
+ height: 20,
126
+ fontSize: 11,
127
+ lineHeight: "20px",
128
+ fontWeight: 600,
129
+ color: badgeColor,
130
+ background: badgeBg,
131
+ padding: "0 7px",
132
+ borderRadius: 4,
133
+ boxSizing: "border-box"
134
+ }
135
+ },
136
+ React.createElement(IconSpinner, { size: 11, color: badgeColor }),
137
+ React.createElement("span", {
138
+ style: {
139
+ display: "inline-block",
140
+ minWidth: isRetrying ? 85 : 64,
141
+ textAlign: "left"
142
+ }
143
+ }, displayText)
144
+ );
145
+ };
146
+
147
+ const QuantumEqualizer = ({ isRetrying }) => {
148
+ const bars = [
149
+ { h: 14, delay: "0.2s" },
150
+ { h: 22, delay: "0.4s" },
151
+ { h: 28, delay: "0.0s" },
152
+ { h: 20, delay: "0.3s" },
153
+ { h: 14, delay: "0.5s" }
154
+ ];
155
+
156
+ const barGradient = isRetrying
157
+ ? "linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%)"
158
+ : "linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%)";
159
+ const barShadow = isRetrying
160
+ ? "0 0 8px rgba(245, 158, 11, 0.55)"
161
+ : "0 0 8px rgba(59, 130, 246, 0.55)";
162
+ const animName = isRetrying ? "dsh-equalizer-pulse-amber" : "dsh-equalizer-pulse";
163
+
164
+ return React.createElement("div", {
165
+ style: {
166
+ display: "flex",
167
+ alignItems: "center",
168
+ justifyContent: "center",
169
+ gap: 6,
170
+ width: "100%",
171
+ height: 36,
172
+ padding: "4px 0",
173
+ boxSizing: "border-box"
174
+ }
175
+ },
176
+ bars.map((bar, idx) =>
177
+ React.createElement("div", {
178
+ key: idx,
179
+ style: {
180
+ width: 3.5,
181
+ height: bar.h,
182
+ borderRadius: 999,
183
+ background: barGradient,
184
+ boxShadow: barShadow,
185
+ animation: `${animName} 1.15s ease-in-out infinite`,
186
+ animationDelay: bar.delay,
187
+ transformOrigin: "center"
188
+ }
189
+ })
190
+ )
191
+ );
192
+ };
193
+
194
+ if (typeof document !== "undefined" && !document.getElementById("dsh-btw-spin-style")) {
195
+ const style = document.createElement("style");
196
+ style.id = "dsh-btw-spin-style";
197
+ style.textContent = `
198
+ @keyframes dsh-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
199
+ @keyframes dsh-equalizer-pulse {
200
+ 0%, 100% { transform: scaleY(0.28); opacity: 0.45; }
201
+ 50% { transform: scaleY(1); opacity: 1; filter: drop-shadow(0 0 6px rgba(59, 130, 246, 0.85)); }
202
+ }
203
+ @keyframes dsh-equalizer-pulse-amber {
204
+ 0%, 100% { transform: scaleY(0.28); opacity: 0.45; }
205
+ 50% { transform: scaleY(1); opacity: 1; filter: drop-shadow(0 0 6px rgba(245, 158, 11, 0.85)); }
206
+ }
207
+ `;
208
+ document.head.appendChild(style);
209
+ }
210
+
211
+ // ── Markdown Parser & Formatter ──────────────────────────────────────
212
+ function renderInlineMarkdown(text) {
213
+ if (!text) return null;
214
+ const tokens = [];
215
+ let key = 0;
216
+ const regex = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\([^)]+\))/g;
217
+ let match;
218
+ let lastIndex = 0;
219
+
220
+ while ((match = regex.exec(text)) !== null) {
221
+ if (match.index > lastIndex) {
222
+ tokens.push(text.substring(lastIndex, match.index));
223
+ }
224
+ const token = match[0];
225
+ if (token.startsWith("`") && token.endsWith("`")) {
226
+ tokens.push(
227
+ React.createElement("code", {
228
+ key: key++,
229
+ style: {
230
+ fontFamily: "var(--dsh-font-mono, monospace)",
231
+ fontSize: "0.9em",
232
+ padding: "1px 5px",
233
+ background: "rgba(255, 255, 255, 0.1)",
234
+ borderRadius: 4,
235
+ color: "#e2e8f0"
236
+ }
237
+ }, token.slice(1, -1))
238
+ );
239
+ } else if (token.startsWith("**") && token.endsWith("**")) {
240
+ tokens.push(
241
+ React.createElement("strong", {
242
+ key: key++,
243
+ style: { fontWeight: 700, color: "var(--dsw-alias-label-primary, #ffffff)" }
244
+ }, token.slice(2, -2))
245
+ );
246
+ } else if (token.startsWith("*") && token.endsWith("*")) {
247
+ tokens.push(
248
+ React.createElement("em", {
249
+ key: key++,
250
+ style: { fontStyle: "italic" }
251
+ }, token.slice(1, -1))
252
+ );
253
+ } else if (token.startsWith("[") && token.includes("](")) {
254
+ const linkMatch = token.match(/\[([^\]]+)\]\(([^)]+)\)/);
255
+ if (linkMatch) {
256
+ tokens.push(
257
+ React.createElement("a", {
258
+ key: key++,
259
+ href: linkMatch[2],
260
+ target: "_blank",
261
+ rel: "noopener noreferrer",
262
+ style: { color: "#3b82f6", textDecoration: "underline" }
263
+ }, linkMatch[1])
264
+ );
265
+ } else {
266
+ tokens.push(token);
267
+ }
268
+ } else {
269
+ tokens.push(token);
270
+ }
271
+ lastIndex = regex.lastIndex;
272
+ }
273
+
274
+ if (lastIndex < text.length) {
275
+ tokens.push(text.substring(lastIndex));
276
+ }
277
+
278
+ return tokens.length > 0 ? tokens : text;
279
+ }
280
+
281
+ function renderTextChunk(chunk, keyPrefix) {
282
+ const lines = chunk.split("\n");
283
+ const elements = [];
284
+ let k = 0;
285
+
286
+ for (let i = 0; i < lines.length; i++) {
287
+ const line = lines[i];
288
+ const trimmed = line.trim();
289
+ if (!trimmed) {
290
+ if (i > 0 && i < lines.length - 1) {
291
+ elements.push(React.createElement("div", { key: `${keyPrefix}-${k++}`, style: { height: 6 } }));
292
+ }
293
+ continue;
294
+ }
295
+
296
+ const listMatch = line.match(/^(\s*)([-*]|\d+\.)\s+(.+)$/);
297
+ if (listMatch) {
298
+ const indent = listMatch[1].length * 10;
299
+ const marker = listMatch[2].endsWith(".") ? listMatch[2] : "•";
300
+ elements.push(
301
+ React.createElement("div", {
302
+ key: `${keyPrefix}-${k++}`,
303
+ style: {
304
+ display: "flex",
305
+ alignItems: "flex-start",
306
+ gap: 6,
307
+ marginLeft: indent + 6,
308
+ margin: "2px 0"
309
+ }
310
+ },
311
+ React.createElement("span", {
312
+ style: {
313
+ color: "#3b82f6",
314
+ fontWeight: 600,
315
+ fontSize: 12,
316
+ minWidth: marker.length > 1 ? 16 : 10,
317
+ userSelect: "none",
318
+ flexShrink: 0
319
+ }
320
+ }, marker),
321
+ React.createElement("div", { style: { flex: 1 } }, renderInlineMarkdown(listMatch[3]))
322
+ )
323
+ );
324
+ } else {
325
+ elements.push(
326
+ React.createElement("div", {
327
+ key: `${keyPrefix}-${k++}`,
328
+ style: { margin: "2px 0" }
329
+ }, renderInlineMarkdown(line))
330
+ );
331
+ }
332
+ }
333
+
334
+ return React.createElement("div", { key: `chunk-${keyPrefix}` }, elements);
335
+ }
336
+
337
+ function renderMarkdownBlocks(markdownText) {
338
+ if (!markdownText) return null;
339
+ const blocks = [];
340
+ const codeBlockRegex = /```([a-zA-Z0-9_-]*)\n([\s\S]*?)```/g;
341
+ let lastIndex = 0;
342
+ let match;
343
+ let blockKey = 0;
344
+
345
+ while ((match = codeBlockRegex.exec(markdownText)) !== null) {
346
+ if (match.index > lastIndex) {
347
+ const textChunk = markdownText.substring(lastIndex, match.index);
348
+ blocks.push(renderTextChunk(textChunk, blockKey++));
349
+ }
350
+ const lang = match[1];
351
+ const code = match[2];
352
+ blocks.push(
353
+ React.createElement("pre", {
354
+ key: blockKey++,
355
+ style: {
356
+ background: "rgba(0, 0, 0, 0.4)",
357
+ border: "0.5px solid rgba(255, 255, 255, 0.1)",
358
+ borderRadius: 6,
359
+ padding: "8px 10px",
360
+ margin: "6px 0",
361
+ overflowX: "auto",
362
+ fontSize: 12,
363
+ fontFamily: "var(--dsh-font-mono, monospace)",
364
+ color: "#e2e8f0"
365
+ }
366
+ }, React.createElement("code", null, code.trim()))
367
+ );
368
+ lastIndex = codeBlockRegex.lastIndex;
369
+ }
370
+
371
+ if (lastIndex < markdownText.length) {
372
+ const textChunk = markdownText.substring(lastIndex);
373
+ blocks.push(renderTextChunk(textChunk, blockKey++));
374
+ }
375
+
376
+ return blocks;
377
+ }
378
+
379
+ // ── Floating Docked Banner Component ───────────────────────────────────
380
+ function BtwDockedBanner({ sessionId }) {
381
+ const [items, setItems] = useState([]);
382
+ const [currentIndex, setCurrentIndex] = useState(0);
383
+ const [dismissedId, setDismissedId] = useState(null);
384
+ const [copied, setCopied] = useState(false);
385
+ const [collapsed, setCollapsed] = useState(false);
386
+ const [models, setModels] = useState([{ id: "auto", name: "Inherit Session Model" }]);
387
+ const [selectedModel, setSelectedModel] = useState("auto");
388
+ const prevCountRef = useRef(0);
389
+
390
+ useEffect(() => {
391
+ fetch('/api/dsh-btw/models')
392
+ .then(res => res.json())
393
+ .then(data => {
394
+ if (data.ok && Array.isArray(data.models)) {
395
+ setModels(data.models);
396
+ if (data.currentModel) setSelectedModel(data.currentModel);
397
+ }
398
+ })
399
+ .catch(() => {});
400
+ }, []);
401
+
402
+ const handleModelChange = (e) => {
403
+ e.stopPropagation();
404
+ const nextModel = e.target.value;
405
+ setSelectedModel(nextModel);
406
+ fetch('/api/dsh-btw/model', {
407
+ method: 'POST',
408
+ headers: { 'Content-Type': 'application/json' },
409
+ body: JSON.stringify({ model: nextModel })
410
+ }).catch(() => {});
411
+ };
412
+
413
+ const fetchFeed = () => {
414
+ if (!sessionId) return;
415
+ fetch(`/api/dsh-btw/feed?sessionId=${encodeURIComponent(sessionId)}`)
416
+ .then(res => res.json())
417
+ .then(data => {
418
+ if (data.ok && Array.isArray(data.items)) {
419
+ setItems(data.items);
420
+ if (data.items.length > prevCountRef.current) {
421
+ setDismissedId(null);
422
+ setCurrentIndex(0);
423
+ setCollapsed(false);
424
+ }
425
+ prevCountRef.current = data.items.length;
426
+ }
427
+ })
428
+ .catch(() => {});
429
+ };
430
+
431
+ useEffect(() => {
432
+ fetchFeed();
433
+ const hasLoading = items.some(i => i.loading);
434
+ const interval = setInterval(fetchFeed, hasLoading ? 500 : 2500);
435
+ return () => clearInterval(interval);
436
+ }, [sessionId, items]);
437
+
438
+ if (!sessionId || items.length === 0) return null;
439
+ const currentItem = items[currentIndex];
440
+ if (!currentItem || currentItem.id === dismissedId) return null;
441
+
442
+ const total = items.length;
443
+ const hasOlder = currentIndex < total - 1;
444
+ const hasNewer = currentIndex > 0;
445
+
446
+ const handleCopy = (e) => {
447
+ e.stopPropagation();
448
+ if (currentItem.loading) return;
449
+ navigator.clipboard.writeText(currentItem.answer).then(() => {
450
+ setCopied(true);
451
+ setTimeout(() => setCopied(false), 2000);
452
+ });
453
+ };
454
+
455
+ const handleDelete = (e) => {
456
+ e.stopPropagation();
457
+ const idToDelete = currentItem.id;
458
+ setItems(prev => prev.filter(i => i.id !== idToDelete));
459
+ if (currentIndex >= items.length - 1 && currentIndex > 0) setCurrentIndex(i => i - 1);
460
+ fetch('/api/dsh-btw/delete', {
461
+ method: 'POST',
462
+ headers: { 'Content-Type': 'application/json' },
463
+ body: JSON.stringify({ sessionId, itemId: idToDelete })
464
+ }).catch(() => {});
465
+ };
466
+
467
+ const handleRetry = (e) => {
468
+ if (e) e.stopPropagation();
469
+ if (currentItem.loading) return;
470
+ setItems(prev => prev.map(i => i.id === currentItem.id ? { ...i, loading: true, answer: "" } : i));
471
+ fetch('/api/dsh-btw/retry', {
472
+ method: 'POST',
473
+ headers: { 'Content-Type': 'application/json' },
474
+ body: JSON.stringify({ sessionId, itemId: currentItem.id })
475
+ }).catch(() => {});
476
+ };
477
+
478
+ const formattedDate = new Date(currentItem.timestamp).toLocaleDateString('en-US', {
479
+ month: 'long', day: 'numeric', year: 'numeric'
480
+ });
481
+
482
+ return React.createElement("section", {
483
+ style: {
484
+ boxSizing: "border-box", display: "flex", flexDirection: "column", gap: 8,
485
+ margin: "0 auto 8px auto", width: "100%", maxWidth: "var(--dsh-composer-card-max-width, 768px)",
486
+ padding: "8px 12px", background: "var(--dsw-specific-tip, #18181b)",
487
+ border: "0.5px solid var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.1))",
488
+ borderRadius: 12, boxShadow: "0 4px 16px rgba(0, 0, 0, 0.25)"
489
+ }
490
+ },
491
+ React.createElement("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, minHeight: 24 } },
492
+ React.createElement("button", {
493
+ type: "button",
494
+ onClick: () => setCollapsed(c => !c),
495
+ style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0, flex: 1, padding: 0, border: "none", background: "transparent", cursor: "pointer", color: "inherit", textAlign: "left" }
496
+ },
497
+ React.createElement("span", { style: { color: "#3b82f6", display: "inline-flex" } }, React.createElement(IconQuestion)),
498
+ React.createElement("span", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #f4f4f5)" } }, "Side Question:"),
499
+ React.createElement("span", {
500
+ title: currentItem.question,
501
+ style: { fontSize: 13, color: "var(--dsw-alias-label-secondary, #a1a1aa)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1, cursor: "default" }
502
+ }, currentItem.question),
503
+ currentItem.loading
504
+ ? React.createElement(ThinkingBadge, {
505
+ retryAttempt: currentItem.retryAttempt,
506
+ maxRetries: currentItem.maxRetries
507
+ })
508
+ : React.createElement("span", {
509
+ style: {
510
+ display: "inline-flex",
511
+ alignItems: "center",
512
+ height: 20,
513
+ fontSize: 11,
514
+ lineHeight: "20px",
515
+ color: "var(--dsw-alias-label-tertiary, #71717a)",
516
+ background: "rgba(255, 255, 255, 0.06)",
517
+ padding: "0 6px",
518
+ borderRadius: 4,
519
+ boxSizing: "border-box"
520
+ }
521
+ }, formattedDate)
522
+ ),
523
+ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } },
524
+ React.createElement("div", {
525
+ style: {
526
+ display: "inline-flex",
527
+ alignItems: "center",
528
+ gap: 4,
529
+ height: 20,
530
+ padding: "0 6px",
531
+ borderRadius: 4,
532
+ background: "rgba(255, 255, 255, 0.06)",
533
+ border: "0.5px solid rgba(255, 255, 255, 0.12)",
534
+ color: "var(--dsw-alias-label-secondary, #a1a1aa)",
535
+ fontSize: 11,
536
+ boxSizing: "border-box"
537
+ }
538
+ },
539
+ React.createElement("span", { style: { display: "inline-flex", color: "#3b82f6" } }, React.createElement(IconCpu)),
540
+ React.createElement("select", {
541
+ value: selectedModel,
542
+ onChange: handleModelChange,
543
+ onClick: (e) => e.stopPropagation(),
544
+ title: "Select Model for BTW Side Questions",
545
+ style: {
546
+ background: "transparent",
547
+ border: "none",
548
+ color: "inherit",
549
+ fontSize: 11,
550
+ fontFamily: "inherit",
551
+ outline: "none",
552
+ cursor: "pointer",
553
+ maxWidth: 140,
554
+ height: 18,
555
+ lineHeight: "18px",
556
+ padding: 0,
557
+ margin: 0,
558
+ textOverflow: "ellipsis"
559
+ }
560
+ },
561
+ models.map(m => React.createElement("option", {
562
+ key: m.id,
563
+ value: m.id,
564
+ style: { background: "#18181b", color: "#f4f4f5" }
565
+ }, m.name))
566
+ )
567
+ ),
568
+ React.createElement("div", { style: { display: "inline-flex", alignItems: "center", gap: 2, marginRight: 4, fontSize: 11, color: "#71717a" } },
569
+ React.createElement("button", { type: "button", disabled: !hasOlder, onClick: () => hasOlder && setCurrentIndex(i => i + 1), style: { border: "none", background: "transparent", cursor: hasOlder ? "pointer" : "default", color: "inherit", opacity: hasOlder ? 1 : 0.3 } }, React.createElement(IconChevronLeft)),
570
+ React.createElement("span", { style: { fontVariantNumeric: "tabular-nums" } }, `${total - currentIndex}/${total}`),
571
+ React.createElement("button", { type: "button", disabled: !hasNewer, onClick: () => hasNewer && setCurrentIndex(i => i - 1), style: { border: "none", background: "transparent", cursor: hasNewer ? "pointer" : "default", color: "inherit", opacity: hasNewer ? 1 : 0.3 } }, React.createElement(IconChevronRight))
572
+ ),
573
+ (currentItem.answer && currentItem.answer.startsWith("Unable to resolve side question:")) &&
574
+ React.createElement("button", {
575
+ type: "button",
576
+ onClick: handleRetry,
577
+ disabled: currentItem.loading,
578
+ title: "Retry query with selected model",
579
+ style: { border: "none", background: "transparent", cursor: "pointer", color: "#fca5a5", padding: 4, display: "inline-flex", alignItems: "center" }
580
+ }, React.createElement(IconRotateCcw)),
581
+ React.createElement("button", { type: "button", onClick: handleCopy, disabled: currentItem.loading, title: copied ? "Copied!" : "Copy answer", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, copied ? React.createElement(IconCheck) : React.createElement(IconCopy)),
582
+ React.createElement("button", { type: "button", onClick: handleDelete, title: "Delete question", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, React.createElement(IconTrash)),
583
+ React.createElement("button", { type: "button", onClick: () => setCollapsed(c => !c), title: collapsed ? "Expand" : "Collapse", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, collapsed ? React.createElement(IconChevronDown) : React.createElement(IconChevronUp)),
584
+ React.createElement("button", { type: "button", onClick: (e) => { e.stopPropagation(); setDismissedId(currentItem.id); }, title: "Close banner", style: { border: "none", background: "transparent", cursor: "pointer", color: "#a1a1aa", padding: 4 } }, React.createElement(IconClose))
585
+ )
586
+ ),
587
+ !collapsed && React.createElement("div", {
588
+ style: {
589
+ fontSize: 13,
590
+ lineHeight: "20px",
591
+ color: "var(--dsw-alias-label-primary, #e4e4e7)",
592
+ wordBreak: "break-word",
593
+ paddingTop: 8,
594
+ paddingBottom: 4,
595
+ borderTop: "0.5px solid rgba(255, 255, 255, 0.08)",
596
+ overflow: "hidden"
597
+ }
598
+ }, currentItem.loading
599
+ ? React.createElement(QuantumEqualizer, {
600
+ isRetrying: Boolean(currentItem.retryAttempt && currentItem.retryAttempt > 0)
601
+ })
602
+ : (currentItem.answer && currentItem.answer.startsWith("Unable to resolve side question:")
603
+ ? React.createElement("div", {
604
+ style: {
605
+ color: "#fca5a5",
606
+ background: "rgba(239, 68, 68, 0.1)",
607
+ border: "0.5px solid rgba(239, 68, 68, 0.25)",
608
+ borderRadius: 6,
609
+ padding: "8px 10px",
610
+ lineHeight: "18px"
611
+ }
612
+ },
613
+ React.createElement("span", { style: { flex: 1 } }, currentItem.answer)
614
+ )
615
+ : renderMarkdownBlocks(currentItem.answer)
616
+ )
617
+ )
618
+ );
619
+ }
620
+
621
+ // ── Cordis Slot Registration ──────────────────────────────────────────
622
+ function apply(ctx) {
623
+ // 1. Injects floating banner above message composer
624
+ ctx.slots.inject("conversation.input.dock", () =>
625
+ ctx.slots.register({
626
+ name: "conversation.input.dock",
627
+ id: "dsh-btw-banner",
628
+ order: 15,
629
+ inject: (sessionId) => ({ sessionId })
630
+ }, BtwDockedBanner)
631
+ );
632
+
633
+ // 2. Suppress /btw command row in chat transcript
634
+ ctx.slots.inject("conversation.chat.commandview", () =>
635
+ ctx.slots.register({
636
+ name: "conversation.chat.commandview",
637
+ key: "btw"
638
+ }, () => null)
639
+ );
640
+ }
641
+
642
+ exports.apply = apply;
643
+ exports.inject = inject;
644
+ return module.exports;
645
+ }
646
+ });