6 changed files with 268 additions and 4 deletions
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
'use strict'; |
||||
// ISOLATED world: инжект MAIN-world chat-logger и передача конфига через CustomEvent
|
||||
(function () { |
||||
var TOKEN_KEY = 'dshmem-bridge-token' |
||||
var ENABLED_KEY = 'dshmem-log-enabled' |
||||
var HOOK_ID = 'dshmem-hook' |
||||
|
||||
function pushConfig() { |
||||
try { |
||||
chrome.storage.local.get([TOKEN_KEY, ENABLED_KEY], function (v) { |
||||
var detail = { |
||||
token: v && v[TOKEN_KEY] ? String(v[TOKEN_KEY]) : '', |
||||
enabled: !(v && v[ENABLED_KEY] === false) |
||||
} |
||||
try { |
||||
window.dispatchEvent(new CustomEvent('dshmem:log-config', { detail: JSON.stringify(detail) })) |
||||
} catch (e) { } |
||||
}) |
||||
} catch (e) { } |
||||
} |
||||
|
||||
function injectHook() { |
||||
if (document.getElementById(HOOK_ID)) return |
||||
var script = document.createElement('script') |
||||
script.id = HOOK_ID |
||||
script.src = chrome.runtime.getURL('injected/chat-logger.js') |
||||
script.async = false |
||||
script.onload = function () { script.remove() } |
||||
;(document.head || document.documentElement).appendChild(script) |
||||
} |
||||
|
||||
window.addEventListener('dshmem:log-request', pushConfig) |
||||
|
||||
try { |
||||
chrome.storage.onChanged.addListener(function (changes, area) { |
||||
if (area !== 'local') return |
||||
if (changes[TOKEN_KEY] || changes[ENABLED_KEY]) pushConfig() |
||||
}) |
||||
} catch (e) { } |
||||
|
||||
injectHook() |
||||
console.log('[chat-log] injected') |
||||
})() |
||||
@ -0,0 +1,183 @@
@@ -0,0 +1,183 @@
|
||||
'use strict'; |
||||
(function () { |
||||
var TOKEN_KEY = 'dshmem-bridge-token' |
||||
var ENABLED_KEY = 'dshmem-log-enabled' |
||||
var BASE = 'http' + ':' + '//' + '127.0.0.1' + ':' + '8766' |
||||
var CHUNK = 700 |
||||
var seen = Object.create(null) |
||||
var cfg = null |
||||
var SEP = String.fromCharCode(10) + String.fromCharCode(10) |
||||
|
||||
function hash(s) { |
||||
var h1 = 5381 |
||||
var h2 = 52711 |
||||
for (var i = 0; i !== s.length; i++) { |
||||
var c = s.charCodeAt(i) |
||||
h1 = (((h1 * 33) ^ c) >>> 0) |
||||
h2 = (((h2 * 31) ^ c) >>> 0) |
||||
} |
||||
return h1.toString(36) + h2.toString(36) |
||||
} |
||||
|
||||
function today() { |
||||
var d = new Date() |
||||
var m = String(d.getMonth() + 1) |
||||
var dd = String(d.getDate()) |
||||
if (m.length === 1) m = '0' + m |
||||
if (dd.length === 1) dd = '0' + dd |
||||
return d.getFullYear() + '-' + m + '-' + dd |
||||
} |
||||
|
||||
function chunk(text) { |
||||
var out = [] |
||||
var n = Math.ceil(text.length / CHUNK) || 1 |
||||
for (var i = 0; i !== n; i++) out.push(text.slice(i * CHUNK, (i + 1) * CHUNK)) |
||||
return out |
||||
} |
||||
|
||||
function isInj(text) { |
||||
if (text.indexOf('MEMORY_SYSTEM') !== -1) return true |
||||
if (text.indexOf('DSM:memory_calls') !== -1) return true |
||||
if (text.indexOf('DSM:memory_write') !== -1) return true |
||||
if (text.indexOf('dsmemory') !== -1) return true |
||||
return false |
||||
} |
||||
|
||||
function linesFor(mid, role, text, ts) { |
||||
var cs = chunk(text) |
||||
var arr = [] |
||||
for (var i = 0; i !== cs.length; i++) { |
||||
var obj = { |
||||
mid: mid, |
||||
part: i, |
||||
total: cs.length, |
||||
role: role, |
||||
ts: ts, |
||||
inj: isInj(text), |
||||
text: cs[i] |
||||
} |
||||
arr.push(JSON.stringify(obj)) |
||||
} |
||||
return arr |
||||
} |
||||
|
||||
function requestConfig() { |
||||
return new Promise(function (resolve) { |
||||
var done = false |
||||
function onCfg(e) { |
||||
if (done) return |
||||
done = true |
||||
try { |
||||
cfg = JSON.parse(e.detail) |
||||
} catch (err) { |
||||
cfg = null |
||||
} |
||||
window.removeEventListener('dshmem:log-config', onCfg) |
||||
resolve(cfg) |
||||
} |
||||
window.addEventListener('dshmem:log-config', onCfg) |
||||
try { |
||||
window.dispatchEvent(new CustomEvent('dshmem:log-request')) |
||||
} catch (err) { |
||||
done = true |
||||
resolve(null) |
||||
} |
||||
setTimeout(function () { |
||||
if (done) return |
||||
done = true |
||||
window.removeEventListener('dshmem:log-config', onCfg) |
||||
resolve(null) |
||||
}, 2000) |
||||
}) |
||||
} |
||||
|
||||
function sendLines(file, lines) { |
||||
if (!cfg || !cfg.token) return Promise.resolve(false) |
||||
var headers = { 'Content-Type': 'application/json' } |
||||
headers.Authorization = 'Bearer ' + cfg.token |
||||
return fetch(BASE + '/log/append', { |
||||
method: 'POST', |
||||
headers: headers, |
||||
body: JSON.stringify({ file: file, lines: lines }) |
||||
}).then(function (r) { return r.ok }).catch(function () { return false }) |
||||
} |
||||
|
||||
function extractAssistant(sse) { |
||||
if (!sse) return '' |
||||
var out = [] |
||||
var blocks = sse.split(SEP) |
||||
for (var i = 0; i !== blocks.length; i++) { |
||||
var block = blocks[i] |
||||
var idx = block.indexOf('data: ') |
||||
if (idx === -1) continue |
||||
var json = block.slice(idx + 6).trim() |
||||
if (!json) continue |
||||
var o = null |
||||
try { o = JSON.parse(json) } catch (e) { continue } |
||||
if (!o || typeof o !== 'object') continue |
||||
if (o.p) continue |
||||
if (typeof o.v !== 'string') continue |
||||
if (Object.keys(o).length !== 1) continue |
||||
out.push(o.v) |
||||
} |
||||
return out.join('') |
||||
} |
||||
|
||||
function process(entry) { |
||||
if (!entry || !entry.url) return |
||||
if (entry.url.indexOf('chat/completion') === -1) return |
||||
if (!cfg) { |
||||
requestConfig().then(function () { process(entry) }) |
||||
return |
||||
} |
||||
if (!cfg.enabled) return |
||||
var messages = [] |
||||
try { |
||||
var req = JSON.parse(entry.body) |
||||
if (req && Array.isArray(req.messages)) messages = req.messages |
||||
} catch (e) { return } |
||||
var assistant = '' |
||||
try { assistant = extractAssistant(entry.resp || '') } catch (e) { } |
||||
var all = messages.slice() |
||||
if (assistant) all.push({ role: 'assistant', content: assistant }) |
||||
var file = today() + '.jsonl' |
||||
var lines = [] |
||||
for (var i = 0; i !== all.length; i++) { |
||||
var msg = all[i] |
||||
if (!msg || typeof msg.content !== 'string') continue |
||||
var text = msg.content |
||||
if (!text) continue |
||||
var role = msg.role || 'user' |
||||
var h = hash(role + ':' + text) |
||||
if (seen[h]) continue |
||||
seen[h] = true |
||||
var ls = linesFor(role + '-' + h, role, text, Date.now()) |
||||
for (var j = 0; j !== ls.length; j++) lines.push(ls[j]) |
||||
} |
||||
if (!lines.length) return |
||||
sendLines(file, lines).then(function (ok) { |
||||
if (ok) console.log('[chat-log] saved', lines.length) |
||||
}) |
||||
} |
||||
|
||||
var origOpen = XMLHttpRequest.prototype.open |
||||
var origSend = XMLHttpRequest.prototype.send |
||||
XMLHttpRequest.prototype.open = function (m, u) { |
||||
this.__chatUrl = String(u || '') |
||||
return origOpen.apply(this, arguments) |
||||
} |
||||
XMLHttpRequest.prototype.send = function (body) { |
||||
var self = this |
||||
var bodyStr = typeof body === 'string' ? body : '' |
||||
this.addEventListener('load', function () { |
||||
try { |
||||
process({ url: self.__chatUrl, body: bodyStr, resp: self.responseText || '' }) |
||||
} catch (e) { |
||||
console.warn('[chat-log] error', String(e)) |
||||
} |
||||
}) |
||||
return origSend.apply(this, arguments) |
||||
} |
||||
|
||||
requestConfig() |
||||
})() |
||||
Loading…
Reference in new issue