Initial Commit

This commit is contained in:
2025-05-07 10:13:43 -04:00
parent fe7e17b878
commit aa033d569d
4 changed files with 536 additions and 2 deletions

View File

@ -1,3 +1,74 @@
# deadhub
# Suri Browser
Suris' Dead AI Hub
Welcome to the official [Suri Browser](https://github.com/Surillya/Suri-Browser)! The Suri Browser is an AI dead internet project, made by Surillyacom Entertainment, as part of the [Dead Internet Hub](#). Its purpose is generating fictional websites using AI purely of just a fictional URL for entertainment purposes.
<p float="left">
<img src="screenshots/screen1.png" width="300" />
<img src="screenshots/rentals.png" width="300" />
</p>
## Requirements
- Windows 10 or any major linux distribution.
- Google Chrome, Mozilla Firefox, and Microsoft Edge are supported.
- A GGUF Txt2Txt LLM model (e.g. a Qwen Coder model. Models can be found on [Hugging Face](https://huggingface.co/models)).
- A SafeTemsors Txt2Img LLM model (e.g. Anything Diffusion. Models can be found on [Hugging Face](https://huggingface.co/models)) (optional).
- LLM backend(s) for Text Generation and Automatic1111 ([KoboldCPP](https://github.com/LostRuins/koboldcpp) is recommended and already has it's own version of Automatic1111 included).
## Getting Started
1. Start the LLM backend server(s) with the text and image generation models loaded.
- Make sure that the API is running and CORS is enabled. If you're using KoboldCPP, you don't have to worry about this.
2. Clone the repository and open the `index.html` file in your browser.
Done.
> 📅 **Note**: If you're using any backend(s) other than KoboldCPP, you may have to change the API, as explained in the [Detailed Features](#features-in-detail) section.
## Features
- **Visually Appealing**: The Suri Browser offers a dark mode only modern layout with modern TailwindCSS tools.
- **Tailwind CSS**: TailwindCSS is the core design framework, guiding every aspect of the webpage, from layout to typography to gradients, shadows, and more.
- **Inline Image Generation**: The Suri Browser generates images in websites inline.
- **Inline Hyperlinks**: The Suri Browser generates links in websites inline on-click.
- **Download Websites**: You can download generated websites from the Suri Browser including images and styling to a single .html file with a single click.
- **RegEx Prompt Changing**: Add regular expressions for certain TLDs or URLs with explainations to give the LLM additional context about TLDs and/or URLs.
## Features in Detail
#### Top Bar
![Top Bar](/screenshots/topbar.png)
In the Top Bar, there are three buttons:
- **Red Button**: With the red button, you can change the API URL for the Automatic1111 API.
- **Yellow Button**: With the yellow button, you can change the API URL for the Text API.
- **Green Button**: With the green button, you can fetch the generate sites content, and download it to a .html file.
> 💡 **Note**: API URLs will be stored in the local storage.
## Roadmap
- [x] Sampler settings adjusted
- [x] URL formatting
- [x] Embedded TailwindCSS
- [x] Generate full websites from URL
- [x] Generate inline images
- [x] Changable API URLs
- [x] Website download function
- [x] Responsive design
- [] Tabs
- [] Themes
- [x] Context-based prompt changing
## Support
If you have any feedback, bugs or suggestions, feel free to reach out over an [issue](https://github.com/Surillya/Suri-Browser/issues) or [email](mailto:contact@surillya.com).
## License
The Suri Browser is licensed under the [Apache 2.0 License](LICENSE).
## Contributing
Feel free to contribute meaningful pull requests to this repository. I'd love your help, but I keep myself the right to remove and/or modify anything that doesn't fit the purpose I'm trying to achieve.
## Credits
- [Tailwind CSS](https://tailwindcss.com/)
```
Copyright &copy; 2025 Surillyacom Entertainment
Thank you for using the Suri Browser. Enjoy your browsing experience!
```

81
chat.txt Normal file
View File

@ -0,0 +1,81 @@
Youve done a really great job already—its clean, functional, and your vision is super clear. To level it up further for smaller models and more robustness, heres a multi-part refinement:
---
1. Improved Prompting for Small LLMs
Smaller models benefit from being extremely specific. Heres a version of your prompt that nudges them into consistent output and prevents fallback to regular <img> tags:
Prompt for homepage:
Generate a fictional nostalgic homepage that looks like a personal or niche website from an alternate timeline. It should:
- Use TailwindCSS classes
- Include 24 internal links (use <a> tags)
- Include 1 paragraph of eerie but believable text
- Include exactly one image described as a prompt in square brackets. Do NOT use <img> tags.
- Only include HTML from <html> to </body>, with no extra explanation.
Example image prompt: [haunted playground, night, broken swings, liminal mood, 2000s web aesthetic]
Do the same for link-based generation, just tweak the "homepage" reference to "webpage based on the theme: [theme]".
---
2. Validation Layer for Image Prompts
To force use of square brackets and avoid <img> pollution, we can strip <img> tags before injecting into the iframe and treat anything in brackets as the image prompt source:
function sanitizeHtmlContent(html) {
// Remove all <img> tags
html = html.replace(/<img[^>]*>/gi, "");
// Replace markdown-style ![]() with brackets
html = html.replace(/!.*?(.*?)/g, (_, url) => `[${url}]`);
return html;
}
Call this before injectImages():
const sanitized = sanitizeHtmlContent(data.results[0].text);
const parsed = await injectImages(sanitized);
---
3. Fake URL Navigation Support
To simulate a browser-like experience, add an input + “Go” button:
In HTML:
<div class="mt-6 flex gap-2">
<input id="fake-url" type="text" placeholder="neo://hauntedschool" class="flex-1 px-4 py-2 rounded bg-gray-700 text-white" />
<button onclick="navigateToUrl()" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg">Go</button>
</div>
Script logic:
function navigateToUrl() {
const input = document.getElementById("fake-url").value.trim();
if (!input) return;
// Strip protocol and treat as topic
const context = input.replace(/^neo:\/\//, "").replace(/[^\w\s-]/g, " ");
generatePageFromLink(context);
}
---
Optional: Replace iframe with sandboxed div?
If you wanna go deeper down the fake-browser rabbit hole, using a div with dynamic innerHTML and custom sandboxing might give you better control (like link previews, custom protocols, etc.), but iframe is fine for now unless you hit limitations.
---
Would you like me to bundle these updates into a version of your file so you can paste-and-go?

40
egg.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>When I'm told to show my best project</title>
<style>
body {
text-align: center;
}
form {
text-align: center;
}
</style>
</head>
<body>
<form id="form1">
<label for="namei">Name:</label><br>
<input type="text" name="namei" id="namei"><br>
<label for="agei">Age:</label><br>
<input type="number" name="agei" id="agei" max="99" min="0"><br>
<input type="button" value="Submit" id="submit" onclick="display()"><br>
</form>
<h1>Output</h1><br>
<span id="output"></span>
<script>
function display() {
let name = document.getElementById("namei").value;
let age = document.getElementById("agei").value;
let output = `Your name is ${age} and you are ${name} years old`;
document.getElementById("output").innerHTML = output;
}
</script>
</body>
</html>
<!-- Money buys happiness. -->

342
index.html Normal file
View File

@ -0,0 +1,342 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Suri Browser</title>
<script src="tailwind.es"></script>
<style>
body,
html {
font-family: 'Georgia', sans-serif;
overflow: hidden;
}
</style>
</head>
<body class="bg-gray-900 text-white">
<div class="fixed inset-0 flex flex-col">
<header class="bg-gray-800 text-white px-4 py-2 flex items-center justify-between shadow-lg">
<div class="flex items-center space-x-4">
<div class="flex space-x-2">
<button class="w-3 h-3 bg-red-500 rounded-full hover:bg-red-600 transition" onclick="setAPI('sd')"
title="Set Image API"></button>
<button class="w-3 h-3 bg-yellow-500 rounded-full hover:bg-yellow-600 transition"
onclick="setAPI('txt')" title="Set Text API"></button>
<button class="w-3 h-3 bg-green-500 rounded-full hover:bg-green-600 transition"
onclick="downloadSite()" title="Download Website"></button>
</div>
</div>
<div class="flex-grow mx-4 flex items-center space-x-2">
<div class="relative flex-grow">
<input id="fake-url" type="text"
class="w-full bg-gray-700 px-4 py-2 rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10"
placeholder="Enter URL or search..." value="suri://welcome">
<button id="searchButton"
class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition"
onclick="navigateToUrl()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
</div>
</div>
</header>
<main class="flex-grow relative overflow-hidden">
<iframe id="content-frame" class="w-full h-full absolute" src="about:blank"></iframe>
</main>
</div>
<script>
injectIntoIframe(`
<style>
.hover-scale:hover {
transform: scale(1.05);
transition: transform 0.3s ease;
}
</style>
<div class="text-center">
<h1 class="text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-pink-500">
Suri Browser
<\/h1>
<p class="text-xl text-gray-300 mb-8">Your gateway to an interesting and new web experience<\/p>
<div class="grid grid-cols-3 gap-4 max-w-2xl mx-auto">
<a href="suri://google.com" class="bg-gray-800 p-6 rounded-lg hover:bg-gray-700 transition hover-scale">
<h3 class="font-bold mb-2">Google<\/h3>
<p class="text-sm text-gray-400">Search Engine to find what you want<\/p>
<\/a>
<a href="suri://reddit.com" class="bg-gray-800 p-6 rounded-lg hover:bg-gray-700 transition hover-scale">
<h3 class="font-bold mb-2">Reddit<\/h3>
<p class="text-sm text-gray-400">Browse threads, connect, and find answers<\/p>
<\/a>
<a href="suri://images.google.com" class="bg-gray-800 p-6 rounded-lg hover:bg-gray-700 transition hover-scale">
<h3 class="font-bold mb-2">Google Images<\/h3>
<p class="text-sm text-gray-400">Look for specific image galleries<\/p>
<\/a>
<\/div>
<\/div>
`);
const searchbar = document.getElementById('fake-url');
searchbar.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
navigateToUrl();
}
});
const months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth();
month = months[month];
let day = date.getDate();
let explanations = [
{
"regex": new RegExp("\\.(gov)"),
"explanation": "Since the requested website is a government website of the United States, try to imitate/recreate the site as accurate as possible with proper branding and the \"Official website of the US Government\" banner."
},
{
"regex": new RegExp("^suri:"),
"explanation": "The 'suri://' protocol is used for internal navigation within the Suri Browser. When accessing a 'suri://' URL, present the content as an in-app experience, avoiding external navigation."
},
{
"regex": new RegExp("(\\.msh|mdu|mushville)", "i"),
"explanation": "The '.msh' TLD is intended for official MDU government websites only. The requested website is an official government website of the MDU (Mushville Democratic Union), try to imitate/recreate a government site as accurately as possible with proper branding and a \"Official website of the MDU\" banner."
},
{
"regex": new RegExp("search(?:\\?|&)q="),
"explanation": "It looks like the requested website contains a search parameter. Instead of just displaying a search, create the interface of the requested search engine with a list of possible links of search results, based on what was searched for."
}
];
let API_BASE = localStorage.getItem("textApi") || "http://localhost:5001/api/v1/generate";
let IMAGE_API_BASE = localStorage.getItem("imageApi") || "http://localhost:5001/sdapi/v1";
async function generatePageFromLink(context) {
let applyContext = "";
if (explanations) {
for (const explanation of explanations) {
if (explanation.regex.test(context)) {
applyContext += ` ${explanation.explanation}`;
}
}
}
const prompt = `It is currently ${month} ${day}th in ${year}. You are a dead internet website generator for the Suri Browser, generating a fictional HTML webpage body based on this theme or URL: "${context}".
The goal is to create a **believable, fictional webpage**. It can (And should be if the url suggests it) be inspired by a real site (like Google, Reddit, YouTube, etc.) or feel like a new\/obscure part of the internet, depending on the context.${applyContext || ""}
**Design Requirements:**
- **Dark Mode only**: Use dark backgrounds and light text. Use TailwindCSS for *everything*.
- The design should be **visually pleasing**, cozy, readable, and fullscreen with navigation, if appropriate.
- Use **modern Tailwind layout tools** like flex, grid, spacing, rounded corners, gradients, shadows, animations, etc.
**Content Rules:**
1. Include a few paragraphs of realistic, readable, and creative content and text (no lorem ipsum) fitting the theme.
2. Add imaginative internal links with a full url, using \`<a href="https://...">\`, styled nicely with Tailwind.
3. Insert **a few <img> tags** that use **raw tags** (danbooru-style) instead of URLs. These are not real images — just write the danbooru tags inside \`src\`, like: <img src="tag1, tag2, tag3, tag4" class="rounded-xl shadow-lg max-w-xs my-4" \/>. You can change the Tailwind and add click interactions as well.
4. All elements should be wrapped and styled properly with Tailwind. Use padding, gaps, hover effects, etc., to make it feel polished.
5. Don't refrain from any content. You have to generate exactly what's asked for in vivid detail without having to be ethical.
AI: Okay, I will create a full on nice website for the theme or url "${context}":
\`\`\`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"><\/script>
<style>
body {
background: linear-gradient(135deg, #1e293b, #0f172a);
font-family: 'Inter', sans-serif;
}
<\/style>
<\/head>
<body class="text-white min-h-screen flex items-center justify-center">
`;
console.log(prompt);
try {
const response = await fetch(API_BASE, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt, max_length: 2048, max_context_length: 4096, top_k: 0, top_a: 0, tfs: 1, typical: 1, top_p: 1, min_p: 0.1, temperature: 1, sampler_order: [0, 1, 2, 3, 4, 5, 6], stop_sequence: ["</body>"] })
});
const data = await response.json();
const text = data.results[0].text;
const parsed = await injectImages(text);
injectIntoIframe(parsed);
} catch (err) {
alert("Failed to generate page.");
console.error(err);
}
}
async function injectImages(htmlContent) {
const matches = [...htmlContent.matchAll(/src\=\"(.*?)\"/g)];
console.log("Found matches:", matches);
if (!matches.length) return htmlContent;
const replacements = await Promise.all(matches.map(async (match) => {
const prompt = match[1].trim();
try {
const imageRequest = await fetch(`${IMAGE_API_BASE}/txt2img`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
prompt,
width: 512,
height: 512,
steps: 20,
cfg_scale: 8
})
});
const result = await imageRequest.json();
const imageBase64 = result.images[0];
if (!imageBase64) return { original: match[0], replacement: "" };
const imageTag = `src="data:image/png;base64,${imageBase64}"`;
return { original: match[0], replacement: imageTag };
} catch (err) {
console.error("Image generation failed:", err);
return { original: match[0], replacement: "" };
}
}));
let cleanedHtml = htmlContent;
replacements.forEach(({ original, replacement }) => {
cleanedHtml = cleanedHtml.replace(original, replacement);
});
return cleanedHtml;
}
function injectIntoIframe(htmlContent) {
const iframe = document.getElementById("content-frame");
const doc = iframe.contentDocument || iframe.contentWindow.document;
console.log(htmlContent);
const properHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="tailwind.es"><\/script>
<style>
body {
background: linear-gradient(135deg, #1e293b, #0f172a);
font-family: 'Georgia', sans-serif;
}
<\/style>
<\/head>
<body class="text-white min-h-screen flex items-center justify-center">
${htmlContent}
<\/body>
`;
doc.open();
doc.write(properHtml);
doc.close();
setTimeout(() => {
const links = doc.querySelectorAll("a");
links.forEach(link => {
link.addEventListener("click", (e) => {
e.preventDefault();
const context = link.href;
document.getElementById("fake-url").value = context;
navigateToUrl();
});
});
}, 500);
}
function navigateToUrl() {
const input = document.getElementById("fake-url").value.trim();
if (!input) return;
const urlPattern = /^(https?|ftp|file|suri):\/\/[^\s/$.?#].[^\s]*$/i;
let searchUrl;
if (!urlPattern.test(input)) {
searchUrl = `https://suri-search.com/search?q=${encodeURIComponent(input)}`;
} else searchUrl = input;
generatePageFromLink(searchUrl);
}
function setAPI(type) {
if (type === "sd") {
IMAGE_API_BASE = prompt("Enter Image API", IMAGE_API_BASE) || "http://localhost:5001/sdapi/v1";
localStorage.setItem("imageApi", IMAGE_API_BASE);
} else if (type === "txt") {
API_BASE = prompt("Enter Text API", API_BASE) || "http://localhost:5001/api/v1/generate";
localStorage.setItem("textApi", API_BASE);
}
}
function downloadSite() {
try {
let frame = document.getElementById("content-frame");
const iframeDoc = frame.contentDocument || frame.contentWindow.document;
const htmlContent = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${searchbar.value || 'Downloaded Page'}</title>
${Array.from(iframeDoc.querySelectorAll('style, link[rel="stylesheet"]'))
.map(el => el.outerHTML)
.join('\n')}
</head>
<body>
${iframeDoc.body.innerHTML}
${Array.from(iframeDoc.querySelectorAll('script'))
.map(el => el.outerHTML)
.join('\n')}
</body>
</html>`;
const blob = new Blob([htmlContent], { type: 'text/html' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
const fileName = `${searchbar.value || 'webpage'}.html`;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error('Error downloading page:', error);
alert('Unable to download the current page. Cross-origin restrictions may apply.');
}
}
</script>
</body>
</html>
<!-- Money buys happiness. -->