180 lines
5.1 KiB
JavaScript
180 lines
5.1 KiB
JavaScript
var Search = (function() {
|
|
var defaultApiUrl = 'https://llama-instruct.reeselink.com/v1';
|
|
var defaultModel = 'instruct';
|
|
|
|
function getConfig() {
|
|
return {
|
|
apiUrl: localStorage.getItem('apiUrl') || defaultApiUrl,
|
|
token: localStorage.getItem('apiToken') || '',
|
|
model: localStorage.getItem('modelName') || defaultModel
|
|
};
|
|
}
|
|
|
|
function callAPI(messages, onChunk, onComplete, onError) {
|
|
var config = getConfig();
|
|
var apiUrl = config.apiUrl.replace(/\/+$/, '');
|
|
var headers = { 'Content-Type': 'application/json' };
|
|
if (config.token) {
|
|
headers['Authorization'] = 'Bearer ' + config.token;
|
|
}
|
|
|
|
fetch(apiUrl + '/chat/completions', {
|
|
method: 'POST',
|
|
headers: headers,
|
|
body: JSON.stringify({
|
|
messages: messages,
|
|
model: config.model,
|
|
stream: true
|
|
})
|
|
})
|
|
.then(function(response) {
|
|
if (!response.ok) {
|
|
throw new Error('API error: ' + response.status + ' ' + response.statusText);
|
|
}
|
|
var reader = response.body.getReader();
|
|
var decoder = new TextDecoder();
|
|
var buffer = '';
|
|
|
|
function read() {
|
|
return reader.read().then(function(result) {
|
|
var done = result.done;
|
|
var value = result.value;
|
|
if (done) {
|
|
onComplete();
|
|
return;
|
|
}
|
|
buffer += decoder.decode(value, { stream: true });
|
|
var lines = buffer.split('\n');
|
|
buffer = lines.pop();
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (line.startsWith('data: ')) {
|
|
var data = line.slice(6);
|
|
if (data === '[DONE]') {
|
|
onComplete();
|
|
return;
|
|
}
|
|
try {
|
|
var json = JSON.parse(data);
|
|
var delta = json.choices && json.choices[0] && json.choices[0].delta;
|
|
if (delta && delta.content) {
|
|
onChunk(delta.content);
|
|
}
|
|
} catch (e) {
|
|
// skip malformed JSON
|
|
}
|
|
}
|
|
}
|
|
return read();
|
|
});
|
|
}
|
|
return read();
|
|
})
|
|
.catch(function(err) {
|
|
onError(err.message);
|
|
});
|
|
}
|
|
|
|
function chat(elementId, systemPrompt, userMessage) {
|
|
var messages = [];
|
|
if (systemPrompt) {
|
|
messages.push({ role: 'system', content: systemPrompt });
|
|
}
|
|
messages.push({ role: 'user', content: userMessage });
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
var outputEl = document.getElementById(elementId);
|
|
if (!outputEl) { reject(new Error('Output element not found')); return; }
|
|
|
|
outputEl.innerHTML = '<span class="llm-loading">Thinking...</span>';
|
|
|
|
var fullText = '';
|
|
var history = systemPrompt ? messages : [];
|
|
|
|
callAPI(
|
|
history,
|
|
function(chunk) {
|
|
fullText += chunk;
|
|
outputEl.innerHTML = formatMarkdown(fullText);
|
|
outputEl.style.whiteSpace = 'pre-wrap';
|
|
},
|
|
function() {
|
|
resolve(fullText);
|
|
},
|
|
function(err) {
|
|
outputEl.innerHTML = '<span class="llm-error">Error: ' + escapeHTML(err) + '</span>';
|
|
reject(err);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
function chatWithHistory(elementId, history, onDone) {
|
|
return new Promise(function(resolve, reject) {
|
|
var outputEl = document.getElementById(elementId);
|
|
if (!outputEl) { reject(new Error('Output element not found')); return; }
|
|
|
|
outputEl.innerHTML = '<span class="llm-loading">Thinking...</span>';
|
|
|
|
var fullText = '';
|
|
|
|
callAPI(
|
|
history,
|
|
function(chunk) {
|
|
fullText += chunk;
|
|
outputEl.innerHTML = formatMarkdown(fullText);
|
|
outputEl.style.whiteSpace = 'pre-wrap';
|
|
},
|
|
function() {
|
|
if (onDone) onDone();
|
|
resolve(fullText);
|
|
},
|
|
function(err) {
|
|
outputEl.innerHTML = '<span class="llm-error">Error: ' + escapeHTML(err) + '</span>';
|
|
reject(err);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
function escapeHTML(str) {
|
|
var div = document.createElement('div');
|
|
div.textContent = str;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function formatMarkdown(text) {
|
|
// Extract code blocks first to protect them from escaping
|
|
var codeBlocks = [];
|
|
text = text.replace(/```([\s\S]*?)```/g, function(match, code) {
|
|
var placeholder = '%%CODEBLOCK' + codeBlocks.length + '%%';
|
|
codeBlocks.push('<pre class="llm-code-block"><code>' + escapeHTML(code) + '</code></pre>');
|
|
return placeholder;
|
|
});
|
|
|
|
// Escape remaining HTML
|
|
text = escapeHTML(text);
|
|
|
|
// Restore code blocks
|
|
for (var i = 0; i < codeBlocks.length; i++) {
|
|
text = text.replace('%%CODEBLOCK' + i + '%%', codeBlocks[i]);
|
|
}
|
|
|
|
// Inline code
|
|
text = text.replace(/`([^`]+)`/g, '<code class="llm-inline-code">$1</code>');
|
|
// Bold
|
|
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
// Line breaks
|
|
text = text.replace(/\n/g, '<br>');
|
|
return text;
|
|
}
|
|
|
|
return {
|
|
getConfig: getConfig,
|
|
callAPI: callAPI,
|
|
chat: chat,
|
|
chatWithHistory: chatWithHistory
|
|
};
|
|
})();
|