import { strictFormat } from '../utils/text.js';
import { log, logVision } from '../../logger.js';
export class Local {
constructor(model_name, url, params) {
this.model_name = model_name;
this.params = params;
this.url = url || 'http://127.0.0.1:11434';
this.chat_endpoint = '/api/chat';
this.embedding_endpoint = '/api/embeddings';
// Note: Actual multimodal support depends on the specific Ollama model (e.g., LLaVA, BakLLaVA)
this.supportsRawImageInput = true;
}
async sendRequest(turns, systemMessage, imageData = null) {
let model = this.model_name || 'sweaterdog/andy-4:latest'; // Changed to Andy-4
let messages = strictFormat(turns);
messages.unshift({ role: 'system', content: systemMessage });
if (imageData) {
console.warn(`[Ollama] imageData provided. Ensure the configured Ollama model ('${model}') is multimodal (e.g., llava, bakllava) to process images.`);
let lastUserMessageIndex = -1;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === 'user') {
lastUserMessageIndex = i;
break;
}
}
if (lastUserMessageIndex !== -1) {
if (!messages[lastUserMessageIndex].images) {
messages[lastUserMessageIndex].images = [];
}
messages[lastUserMessageIndex].images.push(imageData.toString('base64'));
} else {
console.warn('[Ollama] imageData provided, but no user message found to attach it to. Image not sent.');
// Or, could create a new user message:
// messages.push({ role: 'user', content: "Image attached.", images: [imageData.toString('base64')] });
}
}
const maxAttempts = 5;
let attempt = 0;
let finalRes = null;
while (attempt < maxAttempts) {
attempt++;
console.log(`Awaiting local response... (model: ${model}, attempt: ${attempt})`);
let res = null;
try {
let apiResponse = await this.send(this.chat_endpoint, {
model: model,
messages: messages,
stream: false,
...(this.params || {})
});
if (apiResponse) {
res = apiResponse['message']['content'];
} else {
res = 'No response data.';
}
} catch (err) {
if (err.message.toLowerCase().includes('context length') && turns.length > 1) {
console.log('Context length exceeded, trying again with shorter context.');
return await this.sendRequest(turns.slice(1), systemMessage);
} else {
console.log(err);
res = 'My brain disconnected, try again.';
}
}
const hasOpenTag = res.includes("");
const hasCloseTag = res.includes("");
if ((hasOpenTag && !hasCloseTag)) {
console.warn("Partial block detected. Re-generating...");
if (attempt < maxAttempts) continue;
}
if (hasCloseTag && !hasOpenTag) {
res = '' + res;
}
if (hasOpenTag && hasCloseTag) {
res = res.replace(/[\s\S]*?<\/think>/g, '').trim();
}
finalRes = res;
break;
}
if (finalRes == null) {
console.warn("Could not get a valid response after max attempts.");
finalRes = 'I thought too hard, sorry, try again.';
}
if (typeof finalRes === 'string') {
finalRes = finalRes.replace(//g, '').replace(/<\/thinking>/g, '');
}
if (imageData) { // If imageData was part of this sendRequest call
// `messages` here already includes the system prompt and image data
let visionPromptText = "";
if (messages.length > 0) {
const lastTurn = messages[messages.length -1];
// For Ollama, content is a string, images is a separate array.
if (lastTurn.role === 'user' && typeof lastTurn.content === 'string') {
visionPromptText = lastTurn.content;
}
}
logVision(messages, imageData, finalRes, visionPromptText);
} else {
// messages already includes system prompt if no imageData
log(JSON.stringify(messages), finalRes);
}
return finalRes;
}
async embed(text) {
let model = this.model_name || 'nomic-embed-text';
let body = { model: model, input: text };
let res = await this.send(this.embedding_endpoint, body);
return res['embedding'];
}
async send(endpoint, body) {
const url = new URL(endpoint, this.url);
let method = 'POST';
let headers = new Headers();
const request = new Request(url, { method, headers, body: JSON.stringify(body) });
let data = null;
try {
const res = await fetch(request);
if (res.ok) {
data = await res.json();
} else {
throw new Error(`Ollama Status: ${res.status}`);
}
} catch (err) {
console.error('Failed to send Ollama request.');
console.error(err);
}
return data;
}
}