improved google gemini

This commit is contained in:
Mic 2025-08-28 21:04:00 +02:00
parent 4ef246afc3
commit 5a3ea0b380
3 changed files with 78 additions and 63 deletions

View file

@ -345,7 +345,7 @@ class MessagesArea extends HTMLElement {
} }
// Check for newlines in the batch // Check for newlines in the batch
if (tokens.includes('\n')) { if (tokens.endsWith('\n')) {
this.flushAccumulatingMessage(); this.flushAccumulatingMessage();
} }
} }

View file

@ -72,9 +72,10 @@ export class GoogleGemini {
fetchResponse = async (messages) => { fetchResponse = async (messages) => {
// Smart streaming: disabilita streaming per risposte piccole // Smart streaming: disabilita streaming per risposte piccole
const messageLength = messages.map(m => m.parts?.map(p => p.text).join('') || '').join(''); const messageLength = messages.map(m => m.parts?.map(p => p.text).join('') || '').join('').length;
const shouldStream = false; ///this.stream && (messageLength > 500 || !this.adaptiveStreaming); console.log(">>>>>>>>>> Google Gemini messageLength: " + messageLength);
//console.log(">>>>>>>>>> Google Gemini shouldStream: " + shouldStream); const shouldStream = this.stream && (messageLength > 200 || !this.adaptiveStreaming);
console.log(">>>>>>>>>> Google Gemini shouldStream: " + shouldStream);
try { try {
let google_gemini_body = { let google_gemini_body = {
contents:messages contents:messages

View file

@ -42,16 +42,15 @@ let tokenBatch = '';
let batchTimer = null; let batchTimer = null;
let timeoutTimer = null; let timeoutTimer = null;
let lastBatchTime = 0; let lastBatchTime = 0;
let batchStartTime = 0;
// Function to send batched tokens // Function to send batched tokens
function sendTokenBatch(force = false, reason = 'unknown') { function sendTokenBatch(force = false, reason = 'unknown') {
if (tokenBatch && (force || tokenBatch.length >= TOKEN_BATCH_SIZE || performance.now() - lastBatchTime >= TOKEN_BATCH_DELAY)) { if (tokenBatch && (force || tokenBatch.length >= TOKEN_BATCH_SIZE || performance.now() - lastBatchTime >= TOKEN_BATCH_DELAY)) {
console.log(`>>>>>>>>>>>>> Sending token batch (reason: ${reason}):`, tokenBatch);
postMessage({ type: 'tokenBatch', payload: { tokens: tokenBatch } }); postMessage({ type: 'tokenBatch', payload: { tokens: tokenBatch } });
// Reset batch state // Reset batch state
tokenBatch = ''; tokenBatch = '';
lastBatchTime = performance.now(); lastBatchTime = performance.now();
batchStartTime = 0;
// Clear all timers // Clear all timers
if (batchTimer) { if (batchTimer) {
clearTimeout(batchTimer); clearTimeout(batchTimer);
@ -67,10 +66,6 @@ function sendTokenBatch(force = false, reason = 'unknown') {
// Function to add token to batch // Function to add token to batch
function addTokenToBatch(token) { function addTokenToBatch(token) {
tokenBatch += token; tokenBatch += token;
// Set batch start time for the first token
if (tokenBatch.length === token.length) {
batchStartTime = performance.now();
}
// Send immediately if batch is full // Send immediately if batch is full
if (tokenBatch.length >= TOKEN_BATCH_SIZE) { if (tokenBatch.length >= TOKEN_BATCH_SIZE) {
sendTokenBatch(true, 'size-limit'); sendTokenBatch(true, 'size-limit');
@ -119,60 +114,79 @@ self.onmessage = async function(event) {
throw new Error("[ThunderAI] Google Gemini API request failed: " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail); throw new Error("[ThunderAI] Google Gemini API request failed: " + response.status + " " + response.statusText + ", Detail: " + error_message + " " + errorDetail);
} }
const reader = response.body.getReader(); // Check if the response is streaming (SSE/chunks)
const decoder = new TextDecoder("utf-8"); const contentType = response.headers.get('content-type') || '';
let buffer = ''; const isStreaming = contentType.includes('text/event-stream') || contentType.includes('application/x-ndjson');
while (true) {
if (stopStreaming) { if (isStreaming) {
stopStreaming = false; const reader = response.body.getReader();
reader.cancel(); const decoder = new TextDecoder("utf-8");
// Send any remaining tokens in the batch let buffer = '';
sendTokenBatch(true, 'stream-stop'); while (true) {
conversationHistory.push({ role: 'model', parts: [{"text": assistantResponseAccumulator}] }); if (stopStreaming) {
assistantResponseAccumulator = ''; stopStreaming = false;
postMessage({ type: 'tokensDone' }); reader.cancel();
break; // Send any remaining tokens in the batch
} sendTokenBatch(true, 'stream-stop');
const { done, value } = await reader.read(); conversationHistory.push({ role: 'model', parts: [{"text": assistantResponseAccumulator}] });
if (done) { assistantResponseAccumulator = '';
// Send any remaining tokens in the batch postMessage({ type: 'tokensDone' });
sendTokenBatch(true, 'stream-stop'); break;
conversationHistory.push({ role: 'model', parts: [{"text": assistantResponseAccumulator}] });
assistantResponseAccumulator = '';
postMessage({ type: 'tokensDone' });
break;
}
// lots of low-level Google Gemini response parsing stuff
const chunk = decoder.decode(value);
buffer += chunk;
taLog.log("buffer " + buffer);
const lines = buffer.split("\n");
buffer = lines.pop();
let parsedLines = [];
//console.log(">>>>>>>>>>>>>>> lines: " + JSON.stringify(lines));
try{
parsedLines = lines
.map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix
.filter((line) => line !== "" ) // Remove empty lines
// .map((line) => JSON.parse(line)); // Parse the JSON string
.map((line) => {
taLog.log("line: " + JSON.stringify(line));
return JSON.parse(line);
});
}catch(e){
taLog.error("Error parsing lines: " + e);
}
for (const parsedLine of parsedLines) {
const { candidates } = parsedLine;
const { content } = candidates[0];
const { parts } = content;
const { text } = parts[0];
// Update the UI with the new content
if (text) {
assistantResponseAccumulator += text;
// Add to batch instead of sending immediately
addTokenToBatch(text);
} }
const { done, value } = await reader.read();
if (done) {
// Send any remaining tokens in the batch
sendTokenBatch(true, 'stream-stop');
conversationHistory.push({ role: 'model', parts: [{"text": assistantResponseAccumulator}] });
assistantResponseAccumulator = '';
postMessage({ type: 'tokensDone' });
break;
}
// lots of low-level Google Gemini response parsing stuff
const chunk = decoder.decode(value);
buffer += chunk;
taLog.log("buffer " + buffer);
const lines = buffer.split("\n");
buffer = lines.pop();
let parsedLines = [];
//console.log(">>>>>>>>>>>>>>> lines: " + JSON.stringify(lines));
try{
parsedLines = lines
.map((line) => line.replace(/^data: /, "").trim()) // Remove the "data: " prefix
.filter((line) => line !== "" ) // Remove empty lines
// .map((line) => JSON.parse(line)); // Parse the JSON string
.map((line) => {
taLog.log("line: " + JSON.stringify(line));
return JSON.parse(line);
});
}catch(e){
taLog.error("Error parsing lines: " + e);
}
for (const parsedLine of parsedLines) {
const { candidates } = parsedLine;
const { content } = candidates[0];
const { parts } = content;
const { text } = parts[0];
// Update the UI with the new content
if (text) {
assistantResponseAccumulator += text;
// Add to batch instead of sending immediately
addTokenToBatch(text);
}
}
}
} else {
// Non-streaming: send the entire text in a single batch
try {
const responseJson = await response.json();
const text = responseJson.candidates?.[0]?.content?.parts?.[0]?.text || '';
assistantResponseAccumulator = text;
postMessage({ type: 'tokenBatch', payload: { tokens: text } });
postMessage({ type: 'tokensDone' });
conversationHistory.push({ role: 'model', parts: [{ "text": assistantResponseAccumulator }] });
assistantResponseAccumulator = '';
} catch (e) {
taLog.error("Error parsing non-streaming response: " + e);
} }
} }
} else if (event.data.type === 'stop') { } else if (event.data.type === 'stop') {