nodebb-plugin-pdf-secure2 1.4.3 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +6 -1
- package/lib/controllers.js +149 -28
- package/lib/gemini-chat.js +480 -426
- package/lib/nonce-store.js +4 -4
- package/lib/pdf-handler.js +0 -1
- package/lib/topic-access.js +96 -0
- package/library.js +70 -5
- package/package.json +1 -1
- package/plugin.json +4 -0
- package/static/lib/admin.js +25 -0
- package/static/lib/main.js +2 -73
- package/static/templates/admin/plugins/pdf-secure.tpl +18 -2
- package/static/viewer-app.js +18 -62
- package/static/viewer.html +257 -55
package/static/viewer.html
CHANGED
|
@@ -888,6 +888,84 @@
|
|
|
888
888
|
border: 1px solid rgba(220, 38, 38, 0.25);
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
+
/* AI message action buttons (copy, regenerate) */
|
|
892
|
+
.chatMsgActions {
|
|
893
|
+
display: flex;
|
|
894
|
+
gap: 8px;
|
|
895
|
+
margin-top: 6px;
|
|
896
|
+
opacity: 0;
|
|
897
|
+
transition: opacity 0.15s;
|
|
898
|
+
}
|
|
899
|
+
.chatMsg.ai:hover .chatMsgActions { opacity: 1; }
|
|
900
|
+
.chatMsgAction {
|
|
901
|
+
background: none;
|
|
902
|
+
border: none;
|
|
903
|
+
color: var(--text-secondary);
|
|
904
|
+
font-size: 11px;
|
|
905
|
+
cursor: pointer;
|
|
906
|
+
display: flex;
|
|
907
|
+
align-items: center;
|
|
908
|
+
gap: 3px;
|
|
909
|
+
padding: 2px 4px;
|
|
910
|
+
border-radius: 4px;
|
|
911
|
+
transition: color 0.15s, background 0.15s;
|
|
912
|
+
}
|
|
913
|
+
.chatMsgAction:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); }
|
|
914
|
+
.chatMsgAction.copied { color: #4ade80; }
|
|
915
|
+
.chatMsgAction svg { width: 12px; height: 12px; fill: currentColor; }
|
|
916
|
+
|
|
917
|
+
/* AI badge timestamp */
|
|
918
|
+
.aiBadgeTime { font-weight: 400; opacity: 0.6; }
|
|
919
|
+
|
|
920
|
+
/* Scroll to bottom button */
|
|
921
|
+
.chatScrollDown {
|
|
922
|
+
position: absolute;
|
|
923
|
+
bottom: 80px;
|
|
924
|
+
left: 50%;
|
|
925
|
+
transform: translateX(-50%);
|
|
926
|
+
width: 32px; height: 32px;
|
|
927
|
+
border-radius: 50%;
|
|
928
|
+
background: var(--bg-secondary);
|
|
929
|
+
border: 1px solid var(--border-color);
|
|
930
|
+
color: var(--text-primary);
|
|
931
|
+
font-size: 16px;
|
|
932
|
+
cursor: pointer;
|
|
933
|
+
opacity: 0;
|
|
934
|
+
pointer-events: none;
|
|
935
|
+
transition: opacity 0.2s;
|
|
936
|
+
z-index: 5;
|
|
937
|
+
display: flex;
|
|
938
|
+
align-items: center;
|
|
939
|
+
justify-content: center;
|
|
940
|
+
}
|
|
941
|
+
.chatScrollDown:hover { background: var(--bg-tertiary); }
|
|
942
|
+
.chatScrollDown.visible { opacity: 1; pointer-events: auto; }
|
|
943
|
+
|
|
944
|
+
/* Blockquote in AI messages */
|
|
945
|
+
.chatMsg blockquote {
|
|
946
|
+
border-left: 3px solid var(--accent);
|
|
947
|
+
margin: 6px 0;
|
|
948
|
+
padding: 4px 10px;
|
|
949
|
+
color: var(--text-secondary);
|
|
950
|
+
font-style: italic;
|
|
951
|
+
background: rgba(255,255,255,0.03);
|
|
952
|
+
border-radius: 0 4px 4px 0;
|
|
953
|
+
}
|
|
954
|
+
body[data-tier="vip"] .chatMsg blockquote { border-left-color: #ffd700; }
|
|
955
|
+
|
|
956
|
+
/* Table overflow scroll hint */
|
|
957
|
+
.chatMsg .tableWrap.scrollable::after {
|
|
958
|
+
content: '';
|
|
959
|
+
position: absolute;
|
|
960
|
+
top: 0; right: 0; bottom: 0;
|
|
961
|
+
width: 20px;
|
|
962
|
+
background: linear-gradient(to right, transparent, var(--bg-tertiary));
|
|
963
|
+
pointer-events: none;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/* Loading elapsed timer */
|
|
967
|
+
.chatLoadingElapsed { font-size: 10px; color: var(--text-secondary); opacity: 0.5; margin-left: 4px; }
|
|
968
|
+
|
|
891
969
|
/* Injection warning banner */
|
|
892
970
|
.chatInjectionWarning {
|
|
893
971
|
background: rgba(255, 170, 0, 0.12);
|
|
@@ -1004,6 +1082,30 @@
|
|
|
1004
1082
|
}
|
|
1005
1083
|
body[data-tier="vip"] .vipTipsBanner { display: block; }
|
|
1006
1084
|
|
|
1085
|
+
/* Detail mode toggle */
|
|
1086
|
+
.vipTipsRow { display: flex; justify-content: space-between; align-items: center; }
|
|
1087
|
+
.detailToggle { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
|
|
1088
|
+
.detailToggle input { display: none; }
|
|
1089
|
+
.detailToggleSlider {
|
|
1090
|
+
width: 28px; height: 16px;
|
|
1091
|
+
background: rgba(255,255,255,0.15);
|
|
1092
|
+
border-radius: 8px;
|
|
1093
|
+
position: relative;
|
|
1094
|
+
transition: background 0.2s;
|
|
1095
|
+
}
|
|
1096
|
+
.detailToggleSlider::after {
|
|
1097
|
+
content: '';
|
|
1098
|
+
position: absolute; left: 2px; top: 2px;
|
|
1099
|
+
width: 12px; height: 12px;
|
|
1100
|
+
background: var(--text-secondary);
|
|
1101
|
+
border-radius: 50%;
|
|
1102
|
+
transition: transform 0.2s, background 0.2s;
|
|
1103
|
+
}
|
|
1104
|
+
.detailToggle input:checked + .detailToggleSlider { background: rgba(255,215,0,0.3); }
|
|
1105
|
+
.detailToggle input:checked + .detailToggleSlider::after { transform: translateX(12px); background: #ffd700; }
|
|
1106
|
+
.detailToggleText { font-size: 10px; color: var(--text-secondary); font-weight: 500; transition: color 0.2s; }
|
|
1107
|
+
.detailToggle input:checked ~ .detailToggleText { color: #ffd700; }
|
|
1108
|
+
|
|
1007
1109
|
/* Suggestion chips */
|
|
1008
1110
|
.chatSuggestions {
|
|
1009
1111
|
display: flex;
|
|
@@ -3011,7 +3113,14 @@
|
|
|
3011
3113
|
<button class="closeBtn" id="closeChatSidebar">×</button>
|
|
3012
3114
|
</div>
|
|
3013
3115
|
<div class="vipTipsBanner" id="vipTipsBanner">
|
|
3014
|
-
<div class="
|
|
3116
|
+
<div class="vipTipsRow">
|
|
3117
|
+
<div class="vipTipsLabel">VIP — Benden isteyebileceklerin:</div>
|
|
3118
|
+
<label class="detailToggle" id="detailToggleLabel">
|
|
3119
|
+
<input type="checkbox" id="detailToggle">
|
|
3120
|
+
<span class="detailToggleSlider"></span>
|
|
3121
|
+
<span class="detailToggleText">Detaylı</span>
|
|
3122
|
+
</label>
|
|
3123
|
+
</div>
|
|
3015
3124
|
<div class="vipTipsItems">
|
|
3016
3125
|
<span class="vipTip">detayli aciklama</span>
|
|
3017
3126
|
<span class="vipTip">ornek ver</span>
|
|
@@ -3021,6 +3130,7 @@
|
|
|
3021
3130
|
</div>
|
|
3022
3131
|
</div>
|
|
3023
3132
|
<div id="chatMessages"></div>
|
|
3133
|
+
<button id="chatScrollDown" class="chatScrollDown">↓</button>
|
|
3024
3134
|
<div class="chatCharCount" id="chatCharCount"></div>
|
|
3025
3135
|
<div class="chatQuotaBar" id="chatQuotaBar">
|
|
3026
3136
|
<div class="chatQuotaTrack"><div class="chatQuotaFill" id="chatQuotaFill"></div></div>
|
|
@@ -3089,6 +3199,18 @@
|
|
|
3089
3199
|
return originalToBlob.apply(this, arguments);
|
|
3090
3200
|
};
|
|
3091
3201
|
|
|
3202
|
+
// Block getImageData for PDF page canvases (prevents raw pixel extraction)
|
|
3203
|
+
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
|
|
3204
|
+
CanvasRenderingContext2D.prototype.getImageData = function () {
|
|
3205
|
+
if (this.canvas && this.canvas.closest && this.canvas.closest('.page') && this.canvas.closest('#viewerContainer')) {
|
|
3206
|
+
// Return blank ImageData of requested size
|
|
3207
|
+
var w = arguments[2] || 1;
|
|
3208
|
+
var h = arguments[3] || 1;
|
|
3209
|
+
return new ImageData(w, h);
|
|
3210
|
+
}
|
|
3211
|
+
return originalGetImageData.apply(this, arguments);
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3092
3214
|
pdfjsLib.GlobalWorkerOptions.workerSrc = '';
|
|
3093
3215
|
|
|
3094
3216
|
// State - now private, not accessible from console
|
|
@@ -3651,38 +3773,9 @@
|
|
|
3651
3773
|
}
|
|
3652
3774
|
|
|
3653
3775
|
try {
|
|
3654
|
-
// ============================================
|
|
3655
|
-
// SPA CACHE - Check if parent has cached buffer
|
|
3656
|
-
// ============================================
|
|
3657
3776
|
let pdfBuffer = null;
|
|
3658
3777
|
|
|
3659
|
-
|
|
3660
|
-
// Request cached buffer from parent
|
|
3661
|
-
const cachePromise = new Promise((resolve) => {
|
|
3662
|
-
const handler = (event) => {
|
|
3663
|
-
if (event.data && event.data.type === 'pdf-secure-cache-response' && event.data.filename === config.filename) {
|
|
3664
|
-
window.removeEventListener('message', handler);
|
|
3665
|
-
resolve(event.data.buffer);
|
|
3666
|
-
}
|
|
3667
|
-
};
|
|
3668
|
-
window.addEventListener('message', handler);
|
|
3669
|
-
|
|
3670
|
-
// Timeout after 100ms
|
|
3671
|
-
setTimeout(() => {
|
|
3672
|
-
window.removeEventListener('message', handler);
|
|
3673
|
-
resolve(null);
|
|
3674
|
-
}, 100);
|
|
3675
|
-
|
|
3676
|
-
window.parent.postMessage({ type: 'pdf-secure-cache-request', filename: config.filename }, window.location.origin);
|
|
3677
|
-
});
|
|
3678
|
-
|
|
3679
|
-
pdfBuffer = await cachePromise;
|
|
3680
|
-
if (pdfBuffer) {
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
|
|
3684
|
-
// If no cache, fetch from server
|
|
3685
|
-
if (!pdfBuffer) {
|
|
3778
|
+
{
|
|
3686
3779
|
// Nonce and key are embedded in HTML config (not fetched from API)
|
|
3687
3780
|
const nonce = config.nonce;
|
|
3688
3781
|
const decryptKey = config.dk;
|
|
@@ -3698,23 +3791,12 @@
|
|
|
3698
3791
|
|
|
3699
3792
|
const encodedBuffer = await pdfRes.arrayBuffer();
|
|
3700
3793
|
|
|
3701
|
-
// Decrypt AES-256-GCM encrypted data
|
|
3702
|
-
if (decryptKey
|
|
3703
|
-
|
|
3704
|
-
} else {
|
|
3705
|
-
pdfBuffer = encodedBuffer;
|
|
3794
|
+
// Decrypt AES-256-GCM encrypted data (refuse to load without encryption)
|
|
3795
|
+
if (!decryptKey || !decryptIv) {
|
|
3796
|
+
throw new Error('Missing encryption parameters');
|
|
3706
3797
|
}
|
|
3798
|
+
pdfBuffer = await aesGcmDecode(encodedBuffer, decryptKey, decryptIv);
|
|
3707
3799
|
|
|
3708
|
-
// Send buffer to parent for caching (premium only - non-premium must not leak decoded buffer)
|
|
3709
|
-
if ((_cfg.isPremium !== false || _cfg.isLite) && window.parent && window.parent !== window) {
|
|
3710
|
-
// Clone buffer for parent (we keep original)
|
|
3711
|
-
const bufferCopy = pdfBuffer.slice(0);
|
|
3712
|
-
window.parent.postMessage({
|
|
3713
|
-
type: 'pdf-secure-buffer',
|
|
3714
|
-
filename: config.filename,
|
|
3715
|
-
buffer: bufferCopy
|
|
3716
|
-
}, window.location.origin, [bufferCopy]); // Transferable
|
|
3717
|
-
}
|
|
3718
3800
|
}
|
|
3719
3801
|
|
|
3720
3802
|
|
|
@@ -3758,6 +3840,18 @@
|
|
|
3758
3840
|
};
|
|
3759
3841
|
}
|
|
3760
3842
|
|
|
3843
|
+
// Security: Wipe decryption keys from config to prevent memory inspection
|
|
3844
|
+
if (config) {
|
|
3845
|
+
config.dk = null;
|
|
3846
|
+
config.iv = null;
|
|
3847
|
+
config.nonce = null;
|
|
3848
|
+
}
|
|
3849
|
+
if (_cfg) {
|
|
3850
|
+
_cfg.dk = null;
|
|
3851
|
+
_cfg.iv = null;
|
|
3852
|
+
_cfg.nonce = null;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3761
3855
|
|
|
3762
3856
|
} catch (err) {
|
|
3763
3857
|
|
|
@@ -3962,6 +4056,17 @@
|
|
|
3962
4056
|
const chatIsPremium = _cfg && _cfg.isPremium;
|
|
3963
4057
|
const chatIsVip = _cfg && _cfg.isVip;
|
|
3964
4058
|
|
|
4059
|
+
// Detail mode toggle (VIP only)
|
|
4060
|
+
var chatDetailMode = false;
|
|
4061
|
+
var detailToggleEl = document.getElementById('detailToggle');
|
|
4062
|
+
if (chatIsVip && detailToggleEl) {
|
|
4063
|
+
try { chatDetailMode = localStorage.getItem('pdfChat_detailMode') === 'true'; detailToggleEl.checked = chatDetailMode; } catch (e) { /* localStorage unavailable */ }
|
|
4064
|
+
detailToggleEl.addEventListener('change', function () {
|
|
4065
|
+
chatDetailMode = detailToggleEl.checked;
|
|
4066
|
+
try { localStorage.setItem('pdfChat_detailMode', String(chatDetailMode)); } catch (e) { /* ignore */ }
|
|
4067
|
+
});
|
|
4068
|
+
}
|
|
4069
|
+
|
|
3965
4070
|
// Show chat button if chatEnabled (visible to all users, not just premium)
|
|
3966
4071
|
if (_cfg && _cfg.chatEnabled) {
|
|
3967
4072
|
chatBtnEl.style.display = '';
|
|
@@ -4053,6 +4158,18 @@
|
|
|
4053
4158
|
|
|
4054
4159
|
chatBtnEl.onclick = toggleChat;
|
|
4055
4160
|
|
|
4161
|
+
// Scroll-to-bottom button
|
|
4162
|
+
var chatScrollDownBtn = document.getElementById('chatScrollDown');
|
|
4163
|
+
if (chatScrollDownBtn) {
|
|
4164
|
+
chatMessagesEl.addEventListener('scroll', function () {
|
|
4165
|
+
var atBottom = chatMessagesEl.scrollHeight - chatMessagesEl.scrollTop - chatMessagesEl.clientHeight < 60;
|
|
4166
|
+
chatScrollDownBtn.classList.toggle('visible', !atBottom);
|
|
4167
|
+
});
|
|
4168
|
+
chatScrollDownBtn.onclick = function () {
|
|
4169
|
+
chatMessagesEl.scrollTo({ top: chatMessagesEl.scrollHeight, behavior: 'smooth' });
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
|
|
4056
4173
|
// VIP tip chips — click to populate chat input
|
|
4057
4174
|
var vipTipChips = document.querySelectorAll('.vipTip');
|
|
4058
4175
|
var vipTipPrompts = {
|
|
@@ -4067,7 +4184,7 @@
|
|
|
4067
4184
|
var prompt = vipTipPrompts[chip.textContent.trim()];
|
|
4068
4185
|
if (prompt && chatInputEl) {
|
|
4069
4186
|
chatInputEl.value = prompt;
|
|
4070
|
-
|
|
4187
|
+
sendChatMessage();
|
|
4071
4188
|
}
|
|
4072
4189
|
};
|
|
4073
4190
|
});
|
|
@@ -4172,6 +4289,17 @@
|
|
|
4172
4289
|
i++; continue;
|
|
4173
4290
|
}
|
|
4174
4291
|
|
|
4292
|
+
// Blockquote: > text
|
|
4293
|
+
if (trimmed.startsWith('> ')) {
|
|
4294
|
+
flushPara();
|
|
4295
|
+
var bq = document.createElement('blockquote');
|
|
4296
|
+
var bqTokens = tokenizeInline(trimmed.slice(2));
|
|
4297
|
+
for (var t = 0; t < bqTokens.length; t++) bq.appendChild(bqTokens[t]);
|
|
4298
|
+
parent.appendChild(bq);
|
|
4299
|
+
hasOutput = true;
|
|
4300
|
+
i++; continue;
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4175
4303
|
// Table: lines containing | with a separator row
|
|
4176
4304
|
if (trimmed.includes('|') && i + 1 < lines.length) {
|
|
4177
4305
|
var tLines = [];
|
|
@@ -4189,6 +4317,14 @@
|
|
|
4189
4317
|
wrap.appendChild(table);
|
|
4190
4318
|
wrap.appendChild(createCopyBtn(tableToText(table)));
|
|
4191
4319
|
parent.appendChild(wrap);
|
|
4320
|
+
// Detect horizontal overflow for scroll hint
|
|
4321
|
+
requestAnimationFrame(function () {
|
|
4322
|
+
if (wrap.scrollWidth > wrap.clientWidth) wrap.classList.add('scrollable');
|
|
4323
|
+
});
|
|
4324
|
+
wrap.addEventListener('scroll', function () {
|
|
4325
|
+
var atEnd = wrap.scrollLeft + wrap.clientWidth >= wrap.scrollWidth - 5;
|
|
4326
|
+
wrap.classList.toggle('scrollable', !atEnd);
|
|
4327
|
+
});
|
|
4192
4328
|
hasOutput = true;
|
|
4193
4329
|
i = j; continue;
|
|
4194
4330
|
}
|
|
@@ -4451,6 +4587,18 @@
|
|
|
4451
4587
|
continue;
|
|
4452
4588
|
}
|
|
4453
4589
|
}
|
|
4590
|
+
// Strikethrough: ~~...~~
|
|
4591
|
+
if (line[i] === '~' && line[i + 1] === '~') {
|
|
4592
|
+
var endS = line.indexOf('~~', i + 2);
|
|
4593
|
+
if (endS !== -1 && endS - i < 5002) {
|
|
4594
|
+
flushBuf();
|
|
4595
|
+
var del = document.createElement('del');
|
|
4596
|
+
del.textContent = line.slice(i + 2, endS);
|
|
4597
|
+
nodes.push(del);
|
|
4598
|
+
i = endS + 2;
|
|
4599
|
+
continue;
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4454
4602
|
buf += line[i];
|
|
4455
4603
|
i++;
|
|
4456
4604
|
}
|
|
@@ -4472,6 +4620,11 @@
|
|
|
4472
4620
|
svg.appendChild(path);
|
|
4473
4621
|
badge.appendChild(svg);
|
|
4474
4622
|
badge.appendChild(document.createTextNode(' AI'));
|
|
4623
|
+
var ts = document.createElement('span');
|
|
4624
|
+
ts.className = 'aiBadgeTime';
|
|
4625
|
+
var now = new Date();
|
|
4626
|
+
ts.textContent = ' \u2022 ' + now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
|
4627
|
+
badge.appendChild(ts);
|
|
4475
4628
|
return badge;
|
|
4476
4629
|
}
|
|
4477
4630
|
|
|
@@ -4487,6 +4640,32 @@
|
|
|
4487
4640
|
}
|
|
4488
4641
|
msg.appendChild(createAiBadge());
|
|
4489
4642
|
msg.appendChild(renderMarkdownSafe(text));
|
|
4643
|
+
|
|
4644
|
+
// Action buttons (regenerate)
|
|
4645
|
+
var actions = document.createElement('div');
|
|
4646
|
+
actions.className = 'chatMsgActions';
|
|
4647
|
+
|
|
4648
|
+
var regenBtn = document.createElement('button');
|
|
4649
|
+
regenBtn.className = 'chatMsgAction';
|
|
4650
|
+
regenBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> Tekrar sor';
|
|
4651
|
+
regenBtn.onclick = function () {
|
|
4652
|
+
if (chatHistory.length >= 2) {
|
|
4653
|
+
var lastQ = chatHistory[chatHistory.length - 2];
|
|
4654
|
+
if (lastQ.role === 'user') {
|
|
4655
|
+
chatHistory.pop();
|
|
4656
|
+
chatHistory.pop();
|
|
4657
|
+
var msgs = chatMessagesEl.querySelectorAll('.chatMsg');
|
|
4658
|
+
if (msgs.length >= 2) {
|
|
4659
|
+
msgs[msgs.length - 1].remove();
|
|
4660
|
+
msgs[msgs.length - 2].remove();
|
|
4661
|
+
}
|
|
4662
|
+
chatInputEl.value = lastQ.text;
|
|
4663
|
+
sendChatMessage();
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
};
|
|
4667
|
+
actions.appendChild(regenBtn);
|
|
4668
|
+
msg.appendChild(actions);
|
|
4490
4669
|
} else if (role === 'user') {
|
|
4491
4670
|
var now = new Date();
|
|
4492
4671
|
var timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
|
@@ -4504,18 +4683,34 @@
|
|
|
4504
4683
|
}
|
|
4505
4684
|
|
|
4506
4685
|
function showChatLoading() {
|
|
4507
|
-
|
|
4686
|
+
var loadText = chatDetailMode ? 'Detayl\u0131 analiz haz\u0131rlan\u0131yor' : 'D\u00fc\u015f\u00fcn\u00fcyor';
|
|
4687
|
+
var msg = document.createElement('div');
|
|
4508
4688
|
msg.className = 'chatMsg ai chatLoading';
|
|
4509
4689
|
msg.id = 'chatLoadingMsg';
|
|
4510
|
-
msg.innerHTML = '<span class="chatLoadingText">
|
|
4690
|
+
msg.innerHTML = '<span class="chatLoadingText">' + loadText + '</span><span class="chatLoadingDots"><span>.</span><span>.</span><span>.</span></span>';
|
|
4691
|
+
var elapsed = document.createElement('span');
|
|
4692
|
+
elapsed.className = 'chatLoadingElapsed';
|
|
4693
|
+
elapsed.style.display = 'none';
|
|
4694
|
+
msg.appendChild(elapsed);
|
|
4695
|
+
var startTime = Date.now();
|
|
4696
|
+
msg._timer = setInterval(function () {
|
|
4697
|
+
var secs = Math.floor((Date.now() - startTime) / 1000);
|
|
4698
|
+
if (secs >= 5) {
|
|
4699
|
+
elapsed.style.display = '';
|
|
4700
|
+
elapsed.textContent = '(' + secs + 's)';
|
|
4701
|
+
}
|
|
4702
|
+
}, 1000);
|
|
4511
4703
|
chatMessagesEl.appendChild(msg);
|
|
4512
4704
|
chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;
|
|
4513
4705
|
return msg;
|
|
4514
4706
|
}
|
|
4515
4707
|
|
|
4516
4708
|
function removeChatLoading() {
|
|
4517
|
-
|
|
4518
|
-
if (el)
|
|
4709
|
+
var el = document.getElementById('chatLoadingMsg');
|
|
4710
|
+
if (el) {
|
|
4711
|
+
if (el._timer) clearInterval(el._timer);
|
|
4712
|
+
el.remove();
|
|
4713
|
+
}
|
|
4519
4714
|
}
|
|
4520
4715
|
|
|
4521
4716
|
// Quota usage bar
|
|
@@ -4587,7 +4782,7 @@
|
|
|
4587
4782
|
var existingChips = suggestions.querySelectorAll('.chatSuggestionChip');
|
|
4588
4783
|
existingChips.forEach(function (c) { c.classList.add('loading'); });
|
|
4589
4784
|
|
|
4590
|
-
fetch((_cfg.relativePath || '') + '/api/v3/plugins/pdf-secure/suggestions?filename=' + encodeURIComponent(_cfg.filename), {
|
|
4785
|
+
fetch((_cfg.relativePath || '') + '/api/v3/plugins/pdf-secure/suggestions?filename=' + encodeURIComponent(_cfg.filename) + '&tid=' + encodeURIComponent(_cfg.tid || 0), {
|
|
4591
4786
|
headers: { 'x-csrf-token': _cfg.csrfToken || '' },
|
|
4592
4787
|
})
|
|
4593
4788
|
.then(function (r) { return r.json(); })
|
|
@@ -4626,9 +4821,14 @@
|
|
|
4626
4821
|
suggestions.appendChild(chip);
|
|
4627
4822
|
});
|
|
4628
4823
|
|
|
4629
|
-
//
|
|
4630
|
-
|
|
4631
|
-
|
|
4824
|
+
// VIP: auto-load context-aware suggestions immediately
|
|
4825
|
+
// Premium: lazy-load on hover/focus (avoids unnecessary API calls)
|
|
4826
|
+
if (chatIsVip) {
|
|
4827
|
+
setTimeout(loadAiSuggestions, 0);
|
|
4828
|
+
} else {
|
|
4829
|
+
suggestions.addEventListener('mouseenter', loadAiSuggestions, { once: true });
|
|
4830
|
+
suggestions.addEventListener('focusin', loadAiSuggestions, { once: true });
|
|
4831
|
+
}
|
|
4632
4832
|
|
|
4633
4833
|
chatMessagesEl.appendChild(suggestions);
|
|
4634
4834
|
chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;
|
|
@@ -4813,8 +5013,10 @@
|
|
|
4813
5013
|
},
|
|
4814
5014
|
body: JSON.stringify({
|
|
4815
5015
|
filename: _cfg.filename,
|
|
5016
|
+
tid: _cfg.tid || 0,
|
|
4816
5017
|
question: question,
|
|
4817
5018
|
history: chatHistory,
|
|
5019
|
+
detailMode: chatDetailMode,
|
|
4818
5020
|
}),
|
|
4819
5021
|
});
|
|
4820
5022
|
|