-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackground.js
More file actions
206 lines (177 loc) · 6.59 KB
/
Copy pathbackground.js
File metadata and controls
206 lines (177 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// background.js (service worker)
const OPENAI_API_KEY = "...";
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', () => self.clients.claim());
const NI = (...a) => console.log('[NI][bg]', ...a);
const QUEUE = [];
let ACTIVE = 0;
const QUEUE_CONCURRENCY = 1;
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg?.action === 'inject_page_script') {
// инжектим page_inject.js через scripting.executeScript (обходит часть CSP)
const tabId = sender?.tab?.id;
if (!tabId) { sendResponse({ ok:false, error: 'no_tab_id' }); return false; }
chrome.scripting.executeScript(
{ target: { tabId }, files: ['page_inject.js'] },
() => {
if (chrome.runtime.lastError) {
NI('scripting.executeScript error:', chrome.runtime.lastError.message);
sendResponse({ ok:false, error: chrome.runtime.lastError.message });
} else {
NI('scripting.executeScript OK for tab', tabId);
sendResponse({ ok:true });
}
}
);
return true;
}
if (msg?.action === 'invert' && msg?.text) {
enqueue(async () => {
NI('enqueue invert; len=', msg.text.length);
try {
const out = await invertTextWithCache(msg.text);
sendResponse({ ok: true, text: out });
} catch (err) {
NI('invert error:', err);
sendResponse({ ok: false, error: String(err) });
}
});
return true; // async
}
if (msg?.ping) { sendResponse({ pong: true }); return; }
return false;
});
function enqueue(fn) { QUEUE.push(fn); runQueue(); }
function runQueue() {
while (ACTIVE < QUEUE_CONCURRENCY && QUEUE.length) {
const job = QUEUE.shift();
ACTIVE++;
job().finally(() => { ACTIVE--; runQueue(); });
}
}
// ---- cache ----
async function getCache(key) { return new Promise(r => chrome.storage.local.get([key], o => r(o[key] || null))); }
async function setCache(key, val) { return new Promise(r => chrome.storage.local.set({ [key]: val }, r)); }
function hashDJB2(str) { let h=5381; for (let i=0;i<str.length;i++) h=((h<<5)+h)+str.charCodeAt(i); return 'h'+(h>>>0).toString(16); }
function normalizeStr(s) {
return (s || '').replace(/\s+/g, ' ').trim();
}
function tooSimilar(a, b) {
const A = normalizeStr(a).toLowerCase();
const B = normalizeStr(b).toLowerCase();
if (!A || !B) return false;
if (A === B) return true;
const aw = A.split(/\s+/);
const bw = B.split(/\s+/);
const n = Math.min(aw.length, bw.length);
let same = 0;
for (let i = 0; i < n; i++) if (aw[i] === bw[i]) same++;
const similarity = same / n;
return similarity > 0.60;
}
async function invertTextWithCache(text) {
const key = hashDJB2(text);
const cached = await getCache(key);
if (cached) { NI('cache hit', key); return cached; }
const out = await invertTextWithOpenAI(text);
if (out && typeof out === 'string') await setCache(key, out);
return out;
}
async function invertTextWithOpenAI(text) {
if (!OPENAI_API_KEY || OPENAI_API_KEY === "PUT_YOUR_OPENAI_API_KEY_HERE") {
throw new Error("OpenAI API key not set in background.js");
}
const systemPrompt = `
You invert the meaning of news text while preserving style and structure.
Rules:
- Reverse the stance/claims to their logical opposite.
- Keep names, places, dates, quotes attribution, and numerals exactly as written.
- Maintain tone/voice and roughly similar length.
- For headlines and subheadings (short text), keep output length ≤ original length (or at most +10%).
- Do NOT add any prefixes, labels, or meta text (no "NOT:", no explanations).
- Do NOT repeat the original wording; rephrase so most tokens differ while meaning is flipped.
- Output only the rewritten text, in the same language as the input.
`.trim();
const isShort = text.length <= 220; // типичный заголовок
const approxTokens = Math.ceil(text.length / 4);
const maxTokens = Math.min(600, Math.ceil(approxTokens * (isShort ? 1.05 : 1.25)));
const primaryBody = {
model: "gpt-4o-mini",
temperature: 0.7,
frequency_penalty: 0.3,
presence_penalty: 0.1,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: "Rewrite the following so its meaning is the exact opposite, while keeping entities and numbers intact:\n\n" + text }
],
max_tokens: 400 //maxToken
};
async function callOpenAI(body) {
let attempt = 0;
const maxAttempts = 5;
while (true) {
attempt++;
const resp = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${OPENAI_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
if (resp.ok) {
const j = await resp.json();
const out = j?.choices?.[0]?.message?.content;
if (!out) throw new Error("No content from OpenAI");
return out;
}
const t = await resp.text();
if (resp.status === 400 && /context_length_exceeded/i.test(t)) {
return text;
}
if (resp.status === 429) {
let waitMs = 250;
const retryAfter = resp.headers.get('retry-after');
if (retryAfter) {
const parsed = parseFloat(retryAfter);
if (!Number.isNaN(parsed)) waitMs = parsed > 10 ? parsed * 1000 : parsed;
} else {
const m = t.match(/try again in (\d+)ms/i);
if (m) waitMs = parseInt(m[1], 10);
}
const backoff = waitMs * Math.pow(2, attempt - 1);
const jitter = Math.floor(Math.random() * 120);
await new Promise(r => setTimeout(r, backoff + jitter));
if (attempt < maxAttempts) continue;
}
throw new Error(`OpenAI ${resp.status}: ${t}`);
}
}
let out = await callOpenAI(primaryBody);
if (tooSimilar(text, out)) {
const forceBody = {
...primaryBody,
temperature: 0.9,
messages: [
{ role: "system", content: systemPrompt },
{
role: "user",
content:
"FORCE INVERSION: Rewrite so the claims are the *negation* and at least 60% of tokens differ; " +
"keep names/dates/numbers unchanged. No labels, no explanations. Output only the rewritten text.\n\n" +
text
}
]
};
try {
const out2 = await callOpenAI(forceBody);
if (out2 && !tooSimilar(text, out2)) {
out = out2; // taking the second version
}
} catch (e) {
// keep the first version
console.warn('[NI][bg] force pass failed:', e);
}
}
return out;
}