nodebb-plugin-pdf-secure2 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +6 -1
- package/lib/controllers.js +33 -8
- package/lib/gemini-chat.js +452 -409
- package/lib/nonce-store.js +4 -4
- package/lib/pdf-handler.js +0 -1
- package/lib/topic-access.js +96 -0
- package/library.js +65 -5
- package/package.json +1 -1
- package/plugin.json +4 -0
- package/static/lib/main.js +3 -74
- package/static/templates/admin/plugins/pdf-secure.tpl +21 -0
- package/static/viewer-app.js +18 -62
- package/static/viewer.html +696 -70
package/static/viewer.html
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
8
8
|
<title>PDF Viewer</title>
|
|
9
9
|
|
|
10
|
+
<!-- KaTeX (math rendering) -->
|
|
11
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" crossorigin="anonymous">
|
|
12
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" crossorigin="anonymous"></script>
|
|
13
|
+
|
|
10
14
|
<!-- PDF.js -->
|
|
11
15
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js" integrity="sha384-/1qUCSGwTur9vjf/z9lmu/eCUYbpOTgSjmpbMQZ1/CtX2v/WcAIKqRv+U1DUCG6e" crossorigin="anonymous"></script>
|
|
12
16
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css" integrity="sha384-OPLBIVpTWn5IeFhdZcMhX+XMJ4ZMKDN6ykKH+ZxgPvlpLnTW8qrpBePnYNjyeBQM" crossorigin="anonymous">
|
|
@@ -749,6 +753,110 @@
|
|
|
749
753
|
padding: 0;
|
|
750
754
|
}
|
|
751
755
|
|
|
756
|
+
/* Markdown tables in chat */
|
|
757
|
+
.chatMsg .tableWrap {
|
|
758
|
+
overflow-x: auto;
|
|
759
|
+
margin: 6px 0;
|
|
760
|
+
}
|
|
761
|
+
.chatMsg table {
|
|
762
|
+
width: 100%;
|
|
763
|
+
border-collapse: collapse;
|
|
764
|
+
font-size: 12px;
|
|
765
|
+
}
|
|
766
|
+
.chatMsg th, .chatMsg td {
|
|
767
|
+
border: 1px solid rgba(255,255,255,0.15);
|
|
768
|
+
padding: 4px 8px;
|
|
769
|
+
text-align: left;
|
|
770
|
+
}
|
|
771
|
+
.chatMsg th {
|
|
772
|
+
background: rgba(255,255,255,0.08);
|
|
773
|
+
font-weight: 600;
|
|
774
|
+
}
|
|
775
|
+
.chatMsg tbody tr:nth-child(even) {
|
|
776
|
+
background: rgba(255,255,255,0.03);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/* Markdown lists in chat */
|
|
780
|
+
.chatMsg ul, .chatMsg ol {
|
|
781
|
+
margin: 4px 0;
|
|
782
|
+
padding-left: 20px;
|
|
783
|
+
}
|
|
784
|
+
.chatMsg li {
|
|
785
|
+
margin: 2px 0;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/* Markdown headers in chat */
|
|
789
|
+
.chatMsg h3, .chatMsg h4, .chatMsg h5 {
|
|
790
|
+
margin: 8px 0 4px;
|
|
791
|
+
font-weight: 600;
|
|
792
|
+
line-height: 1.3;
|
|
793
|
+
}
|
|
794
|
+
.chatMsg h3 { font-size: 15px; }
|
|
795
|
+
.chatMsg h4 { font-size: 14px; }
|
|
796
|
+
.chatMsg h5 { font-size: 13px; }
|
|
797
|
+
|
|
798
|
+
/* Markdown HR in chat */
|
|
799
|
+
.chatMsg hr {
|
|
800
|
+
border: none;
|
|
801
|
+
border-top: 1px solid rgba(255,255,255,0.15);
|
|
802
|
+
margin: 8px 0;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/* Copy button for code blocks and tables */
|
|
806
|
+
.chatMsg pre, .chatMsg .tableWrap {
|
|
807
|
+
position: relative;
|
|
808
|
+
}
|
|
809
|
+
.chatCopyBtn {
|
|
810
|
+
position: absolute;
|
|
811
|
+
top: 4px;
|
|
812
|
+
right: 4px;
|
|
813
|
+
background: rgba(255,255,255,0.1);
|
|
814
|
+
border: 1px solid rgba(255,255,255,0.15);
|
|
815
|
+
border-radius: 4px;
|
|
816
|
+
color: var(--text-secondary);
|
|
817
|
+
font-size: 10px;
|
|
818
|
+
padding: 2px 6px;
|
|
819
|
+
cursor: pointer;
|
|
820
|
+
opacity: 0;
|
|
821
|
+
transition: opacity 0.15s;
|
|
822
|
+
z-index: 1;
|
|
823
|
+
display: flex;
|
|
824
|
+
align-items: center;
|
|
825
|
+
gap: 3px;
|
|
826
|
+
}
|
|
827
|
+
.chatMsg pre:hover .chatCopyBtn,
|
|
828
|
+
.chatMsg .tableWrap:hover .chatCopyBtn {
|
|
829
|
+
opacity: 1;
|
|
830
|
+
}
|
|
831
|
+
.chatCopyBtn:hover {
|
|
832
|
+
background: rgba(255,255,255,0.2);
|
|
833
|
+
color: var(--text-primary);
|
|
834
|
+
}
|
|
835
|
+
.chatCopyBtn.copied {
|
|
836
|
+
color: #4ade80;
|
|
837
|
+
opacity: 1;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/* LaTeX block math */
|
|
841
|
+
.chatMsg .katex-block {
|
|
842
|
+
overflow-x: auto;
|
|
843
|
+
margin: 6px 0;
|
|
844
|
+
padding: 4px 0;
|
|
845
|
+
}
|
|
846
|
+
.chatMsg .katex-inline {
|
|
847
|
+
display: inline;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/* VIP tip chips clickable */
|
|
851
|
+
.vipTipsBanner .vipTip {
|
|
852
|
+
cursor: pointer;
|
|
853
|
+
transition: background 0.15s, color 0.15s;
|
|
854
|
+
}
|
|
855
|
+
.vipTipsBanner .vipTip:hover {
|
|
856
|
+
background: rgba(255,215,0,0.22);
|
|
857
|
+
color: #ffd700;
|
|
858
|
+
}
|
|
859
|
+
|
|
752
860
|
/* AI badge on AI messages */
|
|
753
861
|
.chatMsg.ai .aiBadge {
|
|
754
862
|
display: flex;
|
|
@@ -780,6 +888,84 @@
|
|
|
780
888
|
border: 1px solid rgba(220, 38, 38, 0.25);
|
|
781
889
|
}
|
|
782
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
|
+
|
|
783
969
|
/* Injection warning banner */
|
|
784
970
|
.chatInjectionWarning {
|
|
785
971
|
background: rgba(255, 170, 0, 0.12);
|
|
@@ -862,6 +1048,64 @@
|
|
|
862
1048
|
body[data-tier="vip"] .chatSuggestionChip:hover { border-color: #ffd700; color: #ffd700; background: rgba(255, 215, 0, 0.12); }
|
|
863
1049
|
body[data-tier="vip"] .chatSummaryBtn:hover { color: #ffd700; border-color: #ffd700; }
|
|
864
1050
|
|
|
1051
|
+
/* VIP feature tips banner */
|
|
1052
|
+
.vipTipsBanner {
|
|
1053
|
+
display: none;
|
|
1054
|
+
padding: 6px 12px;
|
|
1055
|
+
background: linear-gradient(135deg, rgba(255,215,0,0.08), rgba(255,170,0,0.05));
|
|
1056
|
+
border-bottom: 1px solid rgba(255,215,0,0.15);
|
|
1057
|
+
font-size: 11px;
|
|
1058
|
+
color: var(--text-secondary);
|
|
1059
|
+
line-height: 1.4;
|
|
1060
|
+
}
|
|
1061
|
+
.vipTipsBanner .vipTipsLabel {
|
|
1062
|
+
color: #ffd700;
|
|
1063
|
+
font-weight: 600;
|
|
1064
|
+
font-size: 10px;
|
|
1065
|
+
letter-spacing: 0.3px;
|
|
1066
|
+
margin-bottom: 2px;
|
|
1067
|
+
}
|
|
1068
|
+
.vipTipsBanner .vipTipsItems {
|
|
1069
|
+
display: flex;
|
|
1070
|
+
flex-wrap: wrap;
|
|
1071
|
+
gap: 4px;
|
|
1072
|
+
margin-top: 4px;
|
|
1073
|
+
}
|
|
1074
|
+
.vipTipsBanner .vipTip {
|
|
1075
|
+
background: rgba(255,215,0,0.1);
|
|
1076
|
+
border: 1px solid rgba(255,215,0,0.15);
|
|
1077
|
+
border-radius: 10px;
|
|
1078
|
+
padding: 2px 8px;
|
|
1079
|
+
font-size: 10px;
|
|
1080
|
+
color: rgba(255,215,0,0.8);
|
|
1081
|
+
white-space: nowrap;
|
|
1082
|
+
}
|
|
1083
|
+
body[data-tier="vip"] .vipTipsBanner { display: block; }
|
|
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
|
+
|
|
865
1109
|
/* Suggestion chips */
|
|
866
1110
|
.chatSuggestions {
|
|
867
1111
|
display: flex;
|
|
@@ -2868,7 +3112,25 @@
|
|
|
2868
3112
|
</button>
|
|
2869
3113
|
<button class="closeBtn" id="closeChatSidebar">×</button>
|
|
2870
3114
|
</div>
|
|
3115
|
+
<div class="vipTipsBanner" id="vipTipsBanner">
|
|
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>
|
|
3124
|
+
<div class="vipTipsItems">
|
|
3125
|
+
<span class="vipTip">detayli aciklama</span>
|
|
3126
|
+
<span class="vipTip">ornek ver</span>
|
|
3127
|
+
<span class="vipTip">sinav sorusu</span>
|
|
3128
|
+
<span class="vipTip">karsilastirma tablosu</span>
|
|
3129
|
+
<span class="vipTip">ezber teknigi</span>
|
|
3130
|
+
</div>
|
|
3131
|
+
</div>
|
|
2871
3132
|
<div id="chatMessages"></div>
|
|
3133
|
+
<button id="chatScrollDown" class="chatScrollDown">↓</button>
|
|
2872
3134
|
<div class="chatCharCount" id="chatCharCount"></div>
|
|
2873
3135
|
<div class="chatQuotaBar" id="chatQuotaBar">
|
|
2874
3136
|
<div class="chatQuotaTrack"><div class="chatQuotaFill" id="chatQuotaFill"></div></div>
|
|
@@ -2937,6 +3199,18 @@
|
|
|
2937
3199
|
return originalToBlob.apply(this, arguments);
|
|
2938
3200
|
};
|
|
2939
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
|
+
|
|
2940
3214
|
pdfjsLib.GlobalWorkerOptions.workerSrc = '';
|
|
2941
3215
|
|
|
2942
3216
|
// State - now private, not accessible from console
|
|
@@ -3499,38 +3773,9 @@
|
|
|
3499
3773
|
}
|
|
3500
3774
|
|
|
3501
3775
|
try {
|
|
3502
|
-
// ============================================
|
|
3503
|
-
// SPA CACHE - Check if parent has cached buffer
|
|
3504
|
-
// ============================================
|
|
3505
3776
|
let pdfBuffer = null;
|
|
3506
3777
|
|
|
3507
|
-
|
|
3508
|
-
// Request cached buffer from parent
|
|
3509
|
-
const cachePromise = new Promise((resolve) => {
|
|
3510
|
-
const handler = (event) => {
|
|
3511
|
-
if (event.data && event.data.type === 'pdf-secure-cache-response' && event.data.filename === config.filename) {
|
|
3512
|
-
window.removeEventListener('message', handler);
|
|
3513
|
-
resolve(event.data.buffer);
|
|
3514
|
-
}
|
|
3515
|
-
};
|
|
3516
|
-
window.addEventListener('message', handler);
|
|
3517
|
-
|
|
3518
|
-
// Timeout after 100ms
|
|
3519
|
-
setTimeout(() => {
|
|
3520
|
-
window.removeEventListener('message', handler);
|
|
3521
|
-
resolve(null);
|
|
3522
|
-
}, 100);
|
|
3523
|
-
|
|
3524
|
-
window.parent.postMessage({ type: 'pdf-secure-cache-request', filename: config.filename }, window.location.origin);
|
|
3525
|
-
});
|
|
3526
|
-
|
|
3527
|
-
pdfBuffer = await cachePromise;
|
|
3528
|
-
if (pdfBuffer) {
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
|
|
3532
|
-
// If no cache, fetch from server
|
|
3533
|
-
if (!pdfBuffer) {
|
|
3778
|
+
{
|
|
3534
3779
|
// Nonce and key are embedded in HTML config (not fetched from API)
|
|
3535
3780
|
const nonce = config.nonce;
|
|
3536
3781
|
const decryptKey = config.dk;
|
|
@@ -3546,23 +3791,12 @@
|
|
|
3546
3791
|
|
|
3547
3792
|
const encodedBuffer = await pdfRes.arrayBuffer();
|
|
3548
3793
|
|
|
3549
|
-
// Decrypt AES-256-GCM encrypted data
|
|
3550
|
-
if (decryptKey
|
|
3551
|
-
|
|
3552
|
-
} else {
|
|
3553
|
-
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');
|
|
3554
3797
|
}
|
|
3798
|
+
pdfBuffer = await aesGcmDecode(encodedBuffer, decryptKey, decryptIv);
|
|
3555
3799
|
|
|
3556
|
-
// Send buffer to parent for caching (premium only - non-premium must not leak decoded buffer)
|
|
3557
|
-
if ((_cfg.isPremium !== false || _cfg.isLite) && window.parent && window.parent !== window) {
|
|
3558
|
-
// Clone buffer for parent (we keep original)
|
|
3559
|
-
const bufferCopy = pdfBuffer.slice(0);
|
|
3560
|
-
window.parent.postMessage({
|
|
3561
|
-
type: 'pdf-secure-buffer',
|
|
3562
|
-
filename: config.filename,
|
|
3563
|
-
buffer: bufferCopy
|
|
3564
|
-
}, window.location.origin, [bufferCopy]); // Transferable
|
|
3565
|
-
}
|
|
3566
3800
|
}
|
|
3567
3801
|
|
|
3568
3802
|
|
|
@@ -3606,6 +3840,18 @@
|
|
|
3606
3840
|
};
|
|
3607
3841
|
}
|
|
3608
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
|
+
|
|
3609
3855
|
|
|
3610
3856
|
} catch (err) {
|
|
3611
3857
|
|
|
@@ -3810,6 +4056,17 @@
|
|
|
3810
4056
|
const chatIsPremium = _cfg && _cfg.isPremium;
|
|
3811
4057
|
const chatIsVip = _cfg && _cfg.isVip;
|
|
3812
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
|
+
|
|
3813
4070
|
// Show chat button if chatEnabled (visible to all users, not just premium)
|
|
3814
4071
|
if (_cfg && _cfg.chatEnabled) {
|
|
3815
4072
|
chatBtnEl.style.display = '';
|
|
@@ -3901,6 +4158,37 @@
|
|
|
3901
4158
|
|
|
3902
4159
|
chatBtnEl.onclick = toggleChat;
|
|
3903
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
|
+
|
|
4173
|
+
// VIP tip chips — click to populate chat input
|
|
4174
|
+
var vipTipChips = document.querySelectorAll('.vipTip');
|
|
4175
|
+
var vipTipPrompts = {
|
|
4176
|
+
'detayli aciklama': 'Bu konuyu detayli aciklar misin?',
|
|
4177
|
+
'ornek ver': 'Bununla ilgili somut bir ornek verir misin?',
|
|
4178
|
+
'sinav sorusu': 'Bu konudan cikmis veya cikabilecek sinav sorulari neler?',
|
|
4179
|
+
'karsilastirma tablosu': 'Bu konudaki kavramlari karsilastirma tablosu ile goster',
|
|
4180
|
+
'ezber teknigi': 'Bu konuyu kolayca ezberlemek icin teknik onerir misin?',
|
|
4181
|
+
};
|
|
4182
|
+
vipTipChips.forEach(function (chip) {
|
|
4183
|
+
chip.onclick = function () {
|
|
4184
|
+
var prompt = vipTipPrompts[chip.textContent.trim()];
|
|
4185
|
+
if (prompt && chatInputEl) {
|
|
4186
|
+
chatInputEl.value = prompt;
|
|
4187
|
+
sendChatMessage();
|
|
4188
|
+
}
|
|
4189
|
+
};
|
|
4190
|
+
});
|
|
4191
|
+
|
|
3904
4192
|
closeChatBtn.onclick = () => {
|
|
3905
4193
|
chatSidebarEl.classList.remove('open');
|
|
3906
4194
|
chatBtnEl.classList.remove('active');
|
|
@@ -3917,12 +4205,19 @@
|
|
|
3917
4205
|
|
|
3918
4206
|
var frag = document.createDocumentFragment();
|
|
3919
4207
|
|
|
3920
|
-
// Split into code blocks and normal text
|
|
3921
|
-
var parts = text.split(/(```[\s\S]
|
|
4208
|
+
// Split into code blocks, LaTeX blocks ($$...$$), and normal text
|
|
4209
|
+
var parts = text.split(/(```[\s\S]*?```|\$\$[\s\S]*?\$\$)/g);
|
|
3922
4210
|
for (var pi = 0; pi < parts.length; pi++) {
|
|
3923
4211
|
var part = parts[pi];
|
|
3924
4212
|
if (!part) continue;
|
|
3925
4213
|
|
|
4214
|
+
// LaTeX block: $$...$$
|
|
4215
|
+
if (part.startsWith('$$') && part.endsWith('$$') && part.length > 4) {
|
|
4216
|
+
var latex = part.slice(2, -2).trim();
|
|
4217
|
+
frag.appendChild(renderMath(latex, true));
|
|
4218
|
+
continue;
|
|
4219
|
+
}
|
|
4220
|
+
|
|
3926
4221
|
// Code block
|
|
3927
4222
|
if (part.startsWith('```') && part.endsWith('```')) {
|
|
3928
4223
|
var codeContent = part.slice(3, -3);
|
|
@@ -3935,31 +4230,267 @@
|
|
|
3935
4230
|
var code = document.createElement('code');
|
|
3936
4231
|
code.textContent = codeContent.trim();
|
|
3937
4232
|
pre.appendChild(code);
|
|
4233
|
+
pre.appendChild(createCopyBtn(codeContent.trim()));
|
|
3938
4234
|
frag.appendChild(pre);
|
|
3939
4235
|
continue;
|
|
3940
4236
|
}
|
|
3941
4237
|
|
|
3942
|
-
// Normal text — process inline formatting
|
|
3943
|
-
|
|
4238
|
+
// Normal text — process block and inline formatting
|
|
4239
|
+
renderBlocks(part, frag);
|
|
3944
4240
|
}
|
|
3945
4241
|
return frag;
|
|
3946
4242
|
}
|
|
3947
4243
|
|
|
3948
|
-
|
|
3949
|
-
|
|
4244
|
+
// Block-level markdown: tables, lists, headers, HR
|
|
4245
|
+
// Falls back to inline formatting for regular text
|
|
4246
|
+
function renderBlocks(text, parent) {
|
|
3950
4247
|
var lines = text.split('\n');
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
var
|
|
3959
|
-
|
|
3960
|
-
|
|
4248
|
+
var i = 0;
|
|
4249
|
+
var paraBuf = [];
|
|
4250
|
+
var hasOutput = false; // track if we've rendered anything yet
|
|
4251
|
+
|
|
4252
|
+
function flushPara() {
|
|
4253
|
+
if (paraBuf.length === 0) return;
|
|
4254
|
+
if (hasOutput) parent.appendChild(document.createElement('br'));
|
|
4255
|
+
for (var pi = 0; pi < paraBuf.length; pi++) {
|
|
4256
|
+
if (pi > 0) parent.appendChild(document.createElement('br'));
|
|
4257
|
+
var tokens = tokenizeInline(paraBuf[pi]);
|
|
4258
|
+
for (var t = 0; t < tokens.length; t++) {
|
|
4259
|
+
parent.appendChild(tokens[t]);
|
|
4260
|
+
}
|
|
3961
4261
|
}
|
|
4262
|
+
paraBuf = [];
|
|
4263
|
+
hasOutput = true;
|
|
3962
4264
|
}
|
|
4265
|
+
|
|
4266
|
+
while (i < lines.length) {
|
|
4267
|
+
var trimmed = lines[i].trim();
|
|
4268
|
+
|
|
4269
|
+
// Empty line — flush paragraph buffer
|
|
4270
|
+
if (!trimmed) { flushPara(); i++; continue; }
|
|
4271
|
+
|
|
4272
|
+
// Horizontal rule: --- or *** or ___
|
|
4273
|
+
if (/^[-*_]{3,}\s*$/.test(trimmed) && !/\S/.test(trimmed.replace(/[-*_]/g, ''))) {
|
|
4274
|
+
flushPara();
|
|
4275
|
+
parent.appendChild(document.createElement('hr'));
|
|
4276
|
+
hasOutput = true;
|
|
4277
|
+
i++; continue;
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
// Header: # ## ### ####
|
|
4281
|
+
var hMatch = trimmed.match(/^(#{1,4})\s+(.+)/);
|
|
4282
|
+
if (hMatch) {
|
|
4283
|
+
flushPara();
|
|
4284
|
+
var hEl = document.createElement('h' + Math.min(hMatch[1].length + 2, 5));
|
|
4285
|
+
var hTokens = tokenizeInline(hMatch[2]);
|
|
4286
|
+
for (var t = 0; t < hTokens.length; t++) hEl.appendChild(hTokens[t]);
|
|
4287
|
+
parent.appendChild(hEl);
|
|
4288
|
+
hasOutput = true;
|
|
4289
|
+
i++; continue;
|
|
4290
|
+
}
|
|
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
|
+
|
|
4303
|
+
// Table: lines containing | with a separator row
|
|
4304
|
+
if (trimmed.includes('|') && i + 1 < lines.length) {
|
|
4305
|
+
var tLines = [];
|
|
4306
|
+
var j = i;
|
|
4307
|
+
while (j < lines.length && lines[j].trim().includes('|')) {
|
|
4308
|
+
tLines.push(lines[j].trim());
|
|
4309
|
+
j++;
|
|
4310
|
+
}
|
|
4311
|
+
if (tLines.length >= 2 && /^\|?[\s:|-]+\|?$/.test(tLines[1]) && tLines[1].includes('-')) {
|
|
4312
|
+
flushPara();
|
|
4313
|
+
var table = renderTable(tLines);
|
|
4314
|
+
if (table) {
|
|
4315
|
+
var wrap = document.createElement('div');
|
|
4316
|
+
wrap.className = 'tableWrap';
|
|
4317
|
+
wrap.appendChild(table);
|
|
4318
|
+
wrap.appendChild(createCopyBtn(tableToText(table)));
|
|
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
|
+
});
|
|
4328
|
+
hasOutput = true;
|
|
4329
|
+
i = j; continue;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// Unordered list: - item or * item (but not ---)
|
|
4335
|
+
if (/^[-*]\s+/.test(trimmed)) {
|
|
4336
|
+
flushPara();
|
|
4337
|
+
var ul = document.createElement('ul');
|
|
4338
|
+
while (i < lines.length) {
|
|
4339
|
+
var lt = lines[i].trim();
|
|
4340
|
+
var lm = lt.match(/^[-*]\s+(.*)/);
|
|
4341
|
+
if (lm) {
|
|
4342
|
+
var li2 = document.createElement('li');
|
|
4343
|
+
var liTokens = tokenizeInline(lm[1]);
|
|
4344
|
+
for (var t = 0; t < liTokens.length; t++) li2.appendChild(liTokens[t]);
|
|
4345
|
+
ul.appendChild(li2);
|
|
4346
|
+
i++;
|
|
4347
|
+
} else if (!lt) {
|
|
4348
|
+
i++;
|
|
4349
|
+
if (i < lines.length && /^[-*]\s+/.test(lines[i].trim())) continue;
|
|
4350
|
+
break;
|
|
4351
|
+
} else { break; }
|
|
4352
|
+
}
|
|
4353
|
+
parent.appendChild(ul);
|
|
4354
|
+
hasOutput = true;
|
|
4355
|
+
continue;
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
// Ordered list: 1. item or 1) item
|
|
4359
|
+
if (/^\d+[.)]\s+/.test(trimmed)) {
|
|
4360
|
+
flushPara();
|
|
4361
|
+
var ol = document.createElement('ol');
|
|
4362
|
+
while (i < lines.length) {
|
|
4363
|
+
var lt = lines[i].trim();
|
|
4364
|
+
var om = lt.match(/^\d+[.)]\s+(.*)/);
|
|
4365
|
+
if (om) {
|
|
4366
|
+
var li3 = document.createElement('li');
|
|
4367
|
+
var oTokens = tokenizeInline(om[1]);
|
|
4368
|
+
for (var t = 0; t < oTokens.length; t++) li3.appendChild(oTokens[t]);
|
|
4369
|
+
ol.appendChild(li3);
|
|
4370
|
+
i++;
|
|
4371
|
+
} else if (!lt) {
|
|
4372
|
+
i++;
|
|
4373
|
+
if (i < lines.length && /^\d+[.)]\s+/.test(lines[i].trim())) continue;
|
|
4374
|
+
break;
|
|
4375
|
+
} else { break; }
|
|
4376
|
+
}
|
|
4377
|
+
parent.appendChild(ol);
|
|
4378
|
+
hasOutput = true;
|
|
4379
|
+
continue;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
// Regular text line — buffer for paragraph
|
|
4383
|
+
paraBuf.push(trimmed);
|
|
4384
|
+
i++;
|
|
4385
|
+
}
|
|
4386
|
+
flushPara();
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4389
|
+
function renderTable(tLines) {
|
|
4390
|
+
var headerCells = parseTableRow(tLines[0]);
|
|
4391
|
+
if (!headerCells || headerCells.length === 0) return null;
|
|
4392
|
+
|
|
4393
|
+
// Parse alignments from separator row
|
|
4394
|
+
var sepCells = parseTableRow(tLines[1]);
|
|
4395
|
+
var aligns = sepCells ? sepCells.map(function (c) {
|
|
4396
|
+
c = c.trim();
|
|
4397
|
+
if (c.charAt(0) === ':' && c.charAt(c.length - 1) === ':') return 'center';
|
|
4398
|
+
if (c.charAt(c.length - 1) === ':') return 'right';
|
|
4399
|
+
return '';
|
|
4400
|
+
}) : [];
|
|
4401
|
+
|
|
4402
|
+
var table = document.createElement('table');
|
|
4403
|
+
var thead = document.createElement('thead');
|
|
4404
|
+
var hRow = document.createElement('tr');
|
|
4405
|
+
for (var c = 0; c < headerCells.length; c++) {
|
|
4406
|
+
var th = document.createElement('th');
|
|
4407
|
+
if (aligns[c]) th.style.textAlign = aligns[c];
|
|
4408
|
+
var tokens = tokenizeInline(headerCells[c].trim());
|
|
4409
|
+
for (var t = 0; t < tokens.length; t++) th.appendChild(tokens[t]);
|
|
4410
|
+
hRow.appendChild(th);
|
|
4411
|
+
}
|
|
4412
|
+
thead.appendChild(hRow);
|
|
4413
|
+
table.appendChild(thead);
|
|
4414
|
+
|
|
4415
|
+
var tbody = document.createElement('tbody');
|
|
4416
|
+
for (var r = 2; r < tLines.length; r++) {
|
|
4417
|
+
var cells = parseTableRow(tLines[r]);
|
|
4418
|
+
if (!cells) continue;
|
|
4419
|
+
var tr = document.createElement('tr');
|
|
4420
|
+
for (var c = 0; c < cells.length; c++) {
|
|
4421
|
+
var td = document.createElement('td');
|
|
4422
|
+
if (aligns[c]) td.style.textAlign = aligns[c];
|
|
4423
|
+
var tokens = tokenizeInline((cells[c] || '').trim());
|
|
4424
|
+
for (var t = 0; t < tokens.length; t++) td.appendChild(tokens[t]);
|
|
4425
|
+
tr.appendChild(td);
|
|
4426
|
+
}
|
|
4427
|
+
tbody.appendChild(tr);
|
|
4428
|
+
}
|
|
4429
|
+
table.appendChild(tbody);
|
|
4430
|
+
return table;
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
function parseTableRow(line) {
|
|
4434
|
+
line = line.trim();
|
|
4435
|
+
if (line.charAt(0) === '|') line = line.slice(1);
|
|
4436
|
+
if (line.charAt(line.length - 1) === '|') line = line.slice(0, -1);
|
|
4437
|
+
if (!line) return null;
|
|
4438
|
+
return line.split('|');
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
// Copy button helper (used for code blocks and tables)
|
|
4442
|
+
function createCopyBtn(textToCopy) {
|
|
4443
|
+
var btn = document.createElement('button');
|
|
4444
|
+
btn.className = 'chatCopyBtn';
|
|
4445
|
+
btn.textContent = 'Kopyala';
|
|
4446
|
+
btn.onclick = function (e) {
|
|
4447
|
+
e.stopPropagation();
|
|
4448
|
+
navigator.clipboard.writeText(textToCopy).then(function () {
|
|
4449
|
+
btn.textContent = 'Kopyalandi!';
|
|
4450
|
+
btn.classList.add('copied');
|
|
4451
|
+
setTimeout(function () {
|
|
4452
|
+
btn.textContent = 'Kopyala';
|
|
4453
|
+
btn.classList.remove('copied');
|
|
4454
|
+
}, 1500);
|
|
4455
|
+
}).catch(function () {
|
|
4456
|
+
btn.textContent = 'Hata!';
|
|
4457
|
+
setTimeout(function () { btn.textContent = 'Kopyala'; }, 1500);
|
|
4458
|
+
});
|
|
4459
|
+
};
|
|
4460
|
+
return btn;
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
// Convert table element to tab-separated text for clipboard
|
|
4464
|
+
function tableToText(table) {
|
|
4465
|
+
var lines = [];
|
|
4466
|
+
var rows = table.querySelectorAll('tr');
|
|
4467
|
+
for (var r = 0; r < rows.length; r++) {
|
|
4468
|
+
var cells = rows[r].querySelectorAll('th, td');
|
|
4469
|
+
var line = [];
|
|
4470
|
+
for (var c = 0; c < cells.length; c++) {
|
|
4471
|
+
line.push(cells[c].textContent.trim());
|
|
4472
|
+
}
|
|
4473
|
+
lines.push(line.join('\t'));
|
|
4474
|
+
}
|
|
4475
|
+
return lines.join('\n');
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
// Render LaTeX math expression (inline or block)
|
|
4479
|
+
function renderMath(latex, displayMode) {
|
|
4480
|
+
if (typeof katex === 'undefined') {
|
|
4481
|
+
// KaTeX not loaded, show raw LaTeX
|
|
4482
|
+
var fallback = document.createElement('code');
|
|
4483
|
+
fallback.textContent = (displayMode ? '$$' : '$') + latex + (displayMode ? '$$' : '$');
|
|
4484
|
+
return fallback;
|
|
4485
|
+
}
|
|
4486
|
+
var container = document.createElement('span');
|
|
4487
|
+
container.className = displayMode ? 'katex-block' : 'katex-inline';
|
|
4488
|
+
try {
|
|
4489
|
+
katex.render(latex, container, { displayMode: displayMode, throwOnError: false });
|
|
4490
|
+
} catch (e) {
|
|
4491
|
+
container.textContent = latex;
|
|
4492
|
+
}
|
|
4493
|
+
return container;
|
|
3963
4494
|
}
|
|
3964
4495
|
|
|
3965
4496
|
function tokenizeInline(line) {
|
|
@@ -4000,6 +4531,26 @@
|
|
|
4000
4531
|
}
|
|
4001
4532
|
|
|
4002
4533
|
while (i < line.length) {
|
|
4534
|
+
// Block math: $$...$$
|
|
4535
|
+
if (line[i] === '$' && line[i + 1] === '$') {
|
|
4536
|
+
var endM = line.indexOf('$$', i + 2);
|
|
4537
|
+
if (endM !== -1) {
|
|
4538
|
+
flushBuf();
|
|
4539
|
+
nodes.push(renderMath(line.slice(i + 2, endM), true));
|
|
4540
|
+
i = endM + 2;
|
|
4541
|
+
continue;
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
// Inline math: $...$ (but not $$)
|
|
4545
|
+
if (line[i] === '$' && line[i + 1] !== '$') {
|
|
4546
|
+
var endM2 = line.indexOf('$', i + 1);
|
|
4547
|
+
if (endM2 !== -1 && endM2 > i + 1) {
|
|
4548
|
+
flushBuf();
|
|
4549
|
+
nodes.push(renderMath(line.slice(i + 1, endM2), false));
|
|
4550
|
+
i = endM2 + 1;
|
|
4551
|
+
continue;
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4003
4554
|
// Inline code: `...`
|
|
4004
4555
|
if (line[i] === '`') {
|
|
4005
4556
|
var end = line.indexOf('`', i + 1);
|
|
@@ -4036,6 +4587,18 @@
|
|
|
4036
4587
|
continue;
|
|
4037
4588
|
}
|
|
4038
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
|
+
}
|
|
4039
4602
|
buf += line[i];
|
|
4040
4603
|
i++;
|
|
4041
4604
|
}
|
|
@@ -4057,6 +4620,11 @@
|
|
|
4057
4620
|
svg.appendChild(path);
|
|
4058
4621
|
badge.appendChild(svg);
|
|
4059
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);
|
|
4060
4628
|
return badge;
|
|
4061
4629
|
}
|
|
4062
4630
|
|
|
@@ -4072,6 +4640,32 @@
|
|
|
4072
4640
|
}
|
|
4073
4641
|
msg.appendChild(createAiBadge());
|
|
4074
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);
|
|
4075
4669
|
} else if (role === 'user') {
|
|
4076
4670
|
var now = new Date();
|
|
4077
4671
|
var timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
|
|
@@ -4089,18 +4683,34 @@
|
|
|
4089
4683
|
}
|
|
4090
4684
|
|
|
4091
4685
|
function showChatLoading() {
|
|
4092
|
-
|
|
4686
|
+
var loadText = chatDetailMode ? 'Detayl\u0131 analiz haz\u0131rlan\u0131yor' : 'D\u00fc\u015f\u00fcn\u00fcyor';
|
|
4687
|
+
var msg = document.createElement('div');
|
|
4093
4688
|
msg.className = 'chatMsg ai chatLoading';
|
|
4094
4689
|
msg.id = 'chatLoadingMsg';
|
|
4095
|
-
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);
|
|
4096
4703
|
chatMessagesEl.appendChild(msg);
|
|
4097
4704
|
chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;
|
|
4098
4705
|
return msg;
|
|
4099
4706
|
}
|
|
4100
4707
|
|
|
4101
4708
|
function removeChatLoading() {
|
|
4102
|
-
|
|
4103
|
-
if (el)
|
|
4709
|
+
var el = document.getElementById('chatLoadingMsg');
|
|
4710
|
+
if (el) {
|
|
4711
|
+
if (el._timer) clearInterval(el._timer);
|
|
4712
|
+
el.remove();
|
|
4713
|
+
}
|
|
4104
4714
|
}
|
|
4105
4715
|
|
|
4106
4716
|
// Quota usage bar
|
|
@@ -4172,7 +4782,7 @@
|
|
|
4172
4782
|
var existingChips = suggestions.querySelectorAll('.chatSuggestionChip');
|
|
4173
4783
|
existingChips.forEach(function (c) { c.classList.add('loading'); });
|
|
4174
4784
|
|
|
4175
|
-
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), {
|
|
4176
4786
|
headers: { 'x-csrf-token': _cfg.csrfToken || '' },
|
|
4177
4787
|
})
|
|
4178
4788
|
.then(function (r) { return r.json(); })
|
|
@@ -4211,9 +4821,14 @@
|
|
|
4211
4821
|
suggestions.appendChild(chip);
|
|
4212
4822
|
});
|
|
4213
4823
|
|
|
4214
|
-
//
|
|
4215
|
-
|
|
4216
|
-
|
|
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
|
+
}
|
|
4217
4832
|
|
|
4218
4833
|
chatMessagesEl.appendChild(suggestions);
|
|
4219
4834
|
chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;
|
|
@@ -4398,8 +5013,10 @@
|
|
|
4398
5013
|
},
|
|
4399
5014
|
body: JSON.stringify({
|
|
4400
5015
|
filename: _cfg.filename,
|
|
5016
|
+
tid: _cfg.tid || 0,
|
|
4401
5017
|
question: question,
|
|
4402
5018
|
history: chatHistory,
|
|
5019
|
+
detailMode: chatDetailMode,
|
|
4403
5020
|
}),
|
|
4404
5021
|
});
|
|
4405
5022
|
|
|
@@ -4424,7 +5041,16 @@
|
|
|
4424
5041
|
chatHistory = chatHistory.slice(-50);
|
|
4425
5042
|
}
|
|
4426
5043
|
} else {
|
|
4427
|
-
addChatMessage('error', data.error || 'Bir hata oluştu. Tekrar deneyin.');
|
|
5044
|
+
var errorMsg = addChatMessage('error', data.error || 'Bir hata oluştu. Tekrar deneyin.');
|
|
5045
|
+
// Show upgrade button for file size limit errors (Premium -> VIP)
|
|
5046
|
+
if (data.showUpgrade && errorMsg) {
|
|
5047
|
+
var uid = (_cfg && _cfg.uid) || 0;
|
|
5048
|
+
var upgradeBtn = document.createElement('button');
|
|
5049
|
+
upgradeBtn.style.cssText = 'margin-top:8px;padding:4px 12px;background:linear-gradient(135deg,#ffd700,#ffaa00);color:#1a1a1a;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;';
|
|
5050
|
+
upgradeBtn.textContent = 'VIP\'e Y\u00fckselt';
|
|
5051
|
+
upgradeBtn.addEventListener('click', function () { window.open('https://forum.ieu.app/pay/checkout?uid=' + uid, '_blank'); });
|
|
5052
|
+
errorMsg.appendChild(upgradeBtn);
|
|
5053
|
+
}
|
|
4428
5054
|
}
|
|
4429
5055
|
} catch (err) {
|
|
4430
5056
|
removeChatLoading();
|