mirror of
https://github.com/tcsenpai/spacellama.git
synced 2025-06-07 03:35:31 +00:00
first commit
This commit is contained in:
commit
72aa542080
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
extension
|
||||
pack_extension.sh
|
||||
SpaceLLama.zip
|
14
LICENSE.md
Normal file
14
LICENSE.md
Normal 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
56
README.md
Normal 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
55
background.js
Normal 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;
|
||||
}
|
||||
}
|
16
content_scripts/content.js
Normal file
16
content_scripts/content.js
Normal 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");
|
38
manifest.json
Normal file
38
manifest.json
Normal 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
90
options/options.css
Normal 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
30
options/options.html
Normal 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
60
options/options.js
Normal 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
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
90
sidebar/sidebar.css
Normal 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
18
sidebar/sidebar.html
Normal 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
56
sidebar/sidebar.js
Normal 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;
|
||||
}
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user