first commit

This commit is contained in:
tcsenpai 2024-10-11 13:28:02 +02:00
commit 72aa542080
14 changed files with 532 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
extension
pack_extension.sh
SpaceLLama.zip

14
LICENSE.md Normal file
View File

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2024 tcsenpai <dev@tcsenpai.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

56
README.md Normal file
View File

@ -0,0 +1,56 @@
# SpaceLLama
SpaceLLama is a powerful browser extension that leverages OLLAMA to provide quick and efficient web page summarization. It offers a seamless way to distill the essence of any web content, saving you time and enhancing your browsing experience.
## Features
- **One-Click Summarization**: Quickly summarize any web page with a single click.
- **Sidebar Integration**: View summaries in a convenient sidebar without leaving the current page.
- **Customizable OLLAMA Settings**: Easily configure the OLLAMA endpoint and model through the options page.
- **Markdown Rendering**: Summaries are rendered in Markdown for better readability and formatting.
- **Error Handling**: Robust error handling with informative messages for troubleshooting.
## How It Works
1. Click the SpaceLLama icon in your browser toolbar to open the sidebar.
2. Navigate to the web page you want to summarize.
3. Click the "Summarize" button in the sidebar.
4. Wait for a few seconds as SpaceLLama processes the page content.
5. Read the concise summary presented in the sidebar.
## Configuration
You can customize SpaceLLama's behavior through the options page:
1. Click the "Open Settings" button in the sidebar.
2. Set your preferred OLLAMA endpoint (default is `http://localhost:11434`).
3. Choose the OLLAMA model you want to use (default is `llama2`).
4. Save your settings.
## Technical Details
SpaceLLama is built using standard web technologies and the WebExtensions API. It consists of:
- A background script for handling API requests to OLLAMA.
- A content script for extracting page content.
- A sidebar interface for user interaction and displaying summaries.
- An options page for customizing settings.
The extension uses the `marked` library to render Markdown content in the summary view.
## Privacy and Security
SpaceLLama processes web page content locally through your configured OLLAMA endpoint. No data is sent to external servers beyond what you configure. Always ensure you're using a trusted OLLAMA setup, especially if using a remote endpoint.
## Contributing
Contributions to SpaceLLama are welcome! Please feel free to submit issues, feature requests, or pull requests to help improve the extension.
## License
Licensed under the [Do What The Fuck You Want To Public License](LICENSE.md).
See [LICENSE.md](LICENSE.md) for more details.
---
SpaceLLama: Bringing the power of OLLAMA to your browser for effortless web page summarization.

55
background.js Normal file
View File

@ -0,0 +1,55 @@
console.log("Background script loaded");
browser.browserAction.onClicked.addListener(() => {
browser.sidebarAction.toggle();
});
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "summarize") {
summarizeContent(request.content)
.then(summary => {
sendResponse({ summary });
})
.catch(error => {
console.error('Error in summarizeContent:', error);
sendResponse({ error: error.toString(), details: error.details });
});
return true; // Indicates that we will send a response asynchronously
}
});
async function summarizeContent(content) {
const settings = await browser.storage.local.get(['ollamaEndpoint', 'ollamaModel']);
const endpoint = `${settings.ollamaEndpoint || 'http://localhost:11434'}/api/generate`;
const model = settings.ollamaModel || 'llama2';
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: `Summarize the following text:\n\n${content}`,
model: model,
stream: false
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
}
const data = await response.json();
return data.response;
} catch (error) {
console.error('Error details:', error);
error.details = {
endpoint: endpoint,
model: model,
message: error.message
};
throw error;
}
}

View File

@ -0,0 +1,16 @@
function getPageContent() {
console.log("getPageContent called");
return document.body.innerText;
}
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log("Content script received message:", request);
if (request.action === "getContent") {
const content = getPageContent();
console.log("Sending content (first 100 chars):", content.substring(0, 100));
sendResponse({ content: content });
}
return true; // Indicate that we will send a response asynchronously
});
console.log("Content script loaded");

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

38
manifest.json Normal file
View File

@ -0,0 +1,38 @@
{
"manifest_version": 2,
"name": "SpaceLLama",
"version": "1.0",
"description": "Summarize web pages using OLLAMA",
"permissions": [
"activeTab",
"storage",
"<all_urls>",
"tabs"
],
"browser_action": {
"default_title": "SpaceLLama",
"default_icon": "icon.png"
},
"sidebar_action": {
"default_title": "SpaceLLama",
"default_panel": "sidebar/sidebar.html",
"default_icon": "icon.png"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_scripts/content.js"]
}
],
"options_ui": {
"page": "options/options.html",
"open_in_tab": true
},
"web_accessible_resources": [
"sidebar/marked.min.js"
]
}

90
options/options.css Normal file
View File

@ -0,0 +1,90 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 40px 20px;
}
h1 {
font-size: 28px;
margin-bottom: 30px;
color: #2c3e50;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #34495e;
}
.input-group {
display: flex;
align-items: center;
}
input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #bdc3c7;
border-radius: 5px;
transition: border-color 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: #3498db;
}
.status-indicator {
margin-left: 10px;
font-size: 20px;
}
.btn {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
}
.status-message {
margin-top: 20px;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
.status-message.success {
background-color: #2ecc71;
color: white;
}
.status-message.error {
background-color: #e74c3c;
color: white;
}

30
options/options.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OLLAMA Summarizer Settings</title>
<link rel="stylesheet" type="text/css" href="options.css">
</head>
<body>
<div class="container">
<h1>OLLAMA Summarizer Settings</h1>
<form id="settings-form">
<div class="form-group">
<label for="endpoint">OLLAMA Endpoint:</label>
<div class="input-group">
<input type="text" id="endpoint" placeholder="http://localhost:11434">
<span id="endpoint-status" class="status-indicator"></span>
</div>
</div>
<div class="form-group">
<label for="model">OLLAMA Model:</label>
<input type="text" id="model" placeholder="llama2">
</div>
<button type="submit" class="btn btn-primary">Save Settings</button>
</form>
<div id="status" class="status-message"></div>
</div>
<script src="options.js"></script>
</body>
</html>

60
options/options.js Normal file
View File

@ -0,0 +1,60 @@
async function validateEndpoint(endpoint) {
try {
const response = await fetch(`${endpoint}/api/tags`);
return response.ok;
} catch (error) {
console.error('Error validating endpoint:', error);
return false;
}
}
function updateEndpointStatus(isValid) {
const statusElement = document.getElementById('endpoint-status');
statusElement.textContent = isValid ? '✅' : '❌';
statusElement.title = isValid ? 'Endpoint is valid' : 'Endpoint is invalid';
}
async function saveOptions(e) {
e.preventDefault();
const endpoint = document.getElementById('endpoint').value;
const model = document.getElementById('model').value;
const status = document.getElementById('status');
// Ensure the endpoint doesn't end with /api/generate
const cleanEndpoint = endpoint.replace(/\/api\/generate\/?$/, '');
status.textContent = 'Validating endpoint...';
const isValid = await validateEndpoint(cleanEndpoint);
updateEndpointStatus(isValid);
if (isValid) {
browser.storage.local.set({
ollamaEndpoint: cleanEndpoint,
ollamaModel: model
}).then(() => {
status.textContent = 'Options saved and endpoint validated.';
setTimeout(() => {
status.textContent = '';
}, 2000);
});
} else {
status.textContent = 'Invalid endpoint. Please check the URL and try again.';
}
}
async function restoreOptions() {
const result = await browser.storage.local.get(['ollamaEndpoint', 'ollamaModel']);
const endpoint = result.ollamaEndpoint || 'http://localhost:11434';
document.getElementById('endpoint').value = endpoint;
document.getElementById('model').value = result.ollamaModel || 'llama2';
const isValid = await validateEndpoint(endpoint);
updateEndpointStatus(isValid);
}
document.addEventListener('DOMContentLoaded', restoreOptions);
document.getElementById('settings-form').addEventListener('submit', saveOptions);
document.getElementById('endpoint').addEventListener('blur', async (e) => {
const isValid = await validateEndpoint(e.target.value);
updateEndpointStatus(isValid);
});

6
sidebar/marked.min.js vendored Normal file

File diff suppressed because one or more lines are too long

90
sidebar/sidebar.css Normal file
View File

@ -0,0 +1,90 @@
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
color: #2c3e50;
}
.btn {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
}
.btn-secondary {
background-color: #95a5a6;
color: white;
}
.btn-secondary:hover {
background-color: #7f8c8d;
}
.summary-container {
margin-top: 20px;
padding: 15px;
background-color: white;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
#summary h1, #summary h2, #summary h3 {
margin-top: 15px;
margin-bottom: 10px;
color: #2c3e50;
}
#summary p {
margin-bottom: 10px;
line-height: 1.6;
}
#summary ul, #summary ol {
padding-left: 20px;
margin-bottom: 10px;
}
#summary code {
background-color: #f0f0f0;
padding: 2px 4px;
border-radius: 3px;
font-family: 'Courier New', Courier, monospace;
}
#summary pre {
background-color: #f0f0f0;
padding: 10px;
border-radius: 3px;
overflow-x: auto;
font-family: 'Courier New', Courier, monospace;
}
#open-options {
margin-top: 20px;
}

18
sidebar/sidebar.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="sidebar.css">
<script src="marked.min.js"></script>
</head>
<body>
<div class="container">
<h1>OLLAMA Summarizer</h1>
<button id="summarize" class="btn btn-primary">Summarize</button>
<div id="summary" class="summary-container"></div>
<button id="open-options" class="btn btn-secondary">Open Settings</button>
</div>
<script src="sidebar.js"></script>
</body>
</html>

56
sidebar/sidebar.js Normal file
View File

@ -0,0 +1,56 @@
document.addEventListener('DOMContentLoaded', () => {
const summarizeButton = document.getElementById('summarize');
const summaryDiv = document.getElementById('summary');
const openOptionsButton = document.getElementById('open-options');
summarizeButton.addEventListener('click', () => {
summaryDiv.innerHTML = '<p>Summarizing...</p>';
summarizeButton.disabled = true;
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
browser.tabs.sendMessage(tabs[0].id, { action: "getContent" }, (response) => {
if (browser.runtime.lastError) {
handleError('Error getting page content: ' + browser.runtime.lastError.message);
return;
}
if (response && response.content) {
browser.runtime.sendMessage(
{ action: "summarize", content: response.content },
(response) => {
if (browser.runtime.lastError) {
handleError('Error during summarization: ' + browser.runtime.lastError.message);
return;
}
if (response && response.summary) {
// Render the Markdown content
summaryDiv.innerHTML = marked.parse(response.summary);
} else if (response && response.error) {
handleError(response.error, response.details);
} else {
handleError("Unexpected response from summarization");
}
summarizeButton.disabled = false;
}
);
} else {
handleError('Error: Could not retrieve page content.');
}
});
});
});
openOptionsButton.addEventListener('click', () => {
browser.runtime.openOptionsPage();
});
function handleError(errorMessage, details = null) {
console.error("Error:", errorMessage, details);
summaryDiv.innerHTML = `<p>Error: ${errorMessage}</p>`;
if (details) {
summaryDiv.innerHTML += `<pre>${JSON.stringify(details, null, 2)}</pre>`;
}
summarizeButton.disabled = false;
}
});