lovelyYellowXerinaemain.tsx2 matches
20'header', 'main', 'article', 'section', 'page', 'skip', 'jump', 'return',
21// Web-specific terms
22'image', 'photo', 'photograph', 'picture', 'fullscreen', 'share', 'reuse', 'features', 'topics', 'observer',
23'getty', 'rex', 'shutterstock', 'ap', 'afp', 'reuters', 'epa', 'pa', 'alamy', 'getty images', 'associated press',
24'agence france-presse', 'press association', 'copyright', 'rights reserved', 'all rights reserved',
25'click', 'tap', 'swipe', 'scroll', 'zoom', 'enlarge', 'expand', 'collapse', 'hide', 'show',
740gradientStart,
741gradientEnd,
742logoImageSrc,
743textColor,
744logoColor,
763764// Create a cache key based on the input parameters
765const cacheKey = `og:${link}:${title}:${subtitle}:${gradientStart}:${gradientEnd}:${logoImageSrc}:${textColor}:${logoColor}:${titleFontSize}:${subtitleFontSize}:${urlFontSize}:${showLogoName}`;
766767// Try to get the cached image from Val Town blob
768if (typeof nocache == 'undefined' || nocache !== 'true') {
769try {
770const cachedImage = await blob.get(cacheKey);
771if (cachedImage) {
772// Check if cachedImage is a Response object
773if (cachedImage instanceof Response) {
774// Ensure the response is OK
775if (cachedImage.ok) {
776const imageBuffer = await cachedImage.arrayBuffer();
777c.header("Content-Type", "image/png");
778c.header("X-Cache", "HIT");
779console.log("Cache hit for", cacheKey);
780return c.body(new Uint8Array(imageBuffer));
781}
782} else {
783// If it's already a Uint8Array, use it directly
784c.header("Content-Type", "image/png");
785c.header("X-Cache", "HIT");
786console.log("Cache hit for", cacheKey);
787return c.body(cachedImage);
788}
789} else {
791}
792} catch (error) {
793console.error("Error fetching image from Val Town blob:", error);
794}
795}
796// If we reach here, either there was no cache hit or nocache was true
797// Continue with generating a new image...
798799// Use provided title and subtitle, or fallback to defaults
800const imageTitle = title || "";
801const imageSubtitle = subtitle || "";
802// Define colors
803// gray: 161413
825826
827// const logoImageSrc = 'https://labspace.ai/ls2-circle-md4x.png';
828logoImageSrc = logoImageSrc || 'https://labspace.ai/lsc-blue-transparent4x.png';
829830const svg = await satori({
887WebkitBoxOrient: 'vertical',
888},
889children: imageTitle,
890},
891},
902marginBottom: '0px',
903},
904children: imageSubtitle,
905},
906},
979// type: 'img',
980// props: {
981// src: logoImageSrc,
982// alt: 'Labspace Logo',
983// style: {
984// width: '72px',
985// objectFit: 'contain',
986// marginBottom: '0px', // Add space between image and text
987// marginRight: '0px',
988// },
1146const pngBuffer = await render(svg);
11471148// Cache the generated image in Val Town blob
1149try {
1150// Check if the pngBuffer size is less than 128KB (Val Town blob limit)
1153c.header("X-Cache", "MISS");
1154} else {
1155console.warn("Image too large to cache in Val Town blob");
1156c.header("X-Cache", "SKIP");
1157}
1158} catch (error) {
1159console.error("Error caching image in Val Town blob:", error);
1160c.header("X-Cache", "ERROR");
1161}
11621163c.header("Content-Type", "image/png");
1164return c.body(pngBuffer);
1165});
1174gradientStart,
1175gradientEnd,
1176logoImageSrc,
1177textColor,
1178logoColor,
12021203// Create a cache key based on the input parameters
1204const cacheKey = `og:${link}:${title}:${subtitle}:${attachment}:${objectFit}:${objectPosition}:${panelWidth}:${width}:${height}:${gradientStart}:${gradientEnd}:${logoImageSrc}:${textColor}:${logoColor}:${titleFontSize}:${subtitleFontSize}:${urlFontSize}:${showLogoName}`;
12051206// Try to get the cached image from Val Town blob
1207if (typeof nocache == 'undefined' || nocache !== 'true') {
1208try {
1209const cachedImage = await blob.get(cacheKey);
1210if (cachedImage) {
1211// Check if cachedImage is a Response object
1212if (cachedImage instanceof Response) {
1213// Ensure the response is OK
1214if (cachedImage.ok) {
1215const imageBuffer = await cachedImage.arrayBuffer();
1216c.header("Content-Type", "image/png");
1217c.header("X-Cache", "HIT");
1218console.log("Cache hit for", cacheKey);
1219return c.body(new Uint8Array(imageBuffer));
1220}
1221} else {
1222// If it's already a Uint8Array, use it directly
1223c.header("Content-Type", "image/png");
1224c.header("X-Cache", "HIT");
1225console.log("Cache hit for", cacheKey);
1226return c.body(cachedImage);
1227}
1228} else {
1230}
1231} catch (error) {
1232console.error("Error fetching image from Val Town blob:", error);
1233}
1234}
1235// If we reach here, either there was no cache hit or nocache was true
1236// Continue with generating a new image...
12371238// Use provided title and subtitle, or fallback to defaults
1239const imageTitle = title || "";
1240const imageSubtitle = subtitle || "";
12411242// Define colors
1258// }
1259
1260// const logoImageSrc = 'https://labspace.ai/ls2-circle-md4x.png';
1261logoImageSrc = logoImageSrc || 'https://labspace.ai/lsc-blue-transparent4x.png';
12621263const svg = await satori({
1294flexDirection: 'column',
1295maxWidth: panelWidth, // panelWidth, // Control width of text section
1296paddingRight: '48px', // Space between text and image
12971298paddingTop: '14px',
1330WebkitBoxOrient: 'vertical',
1331},
1332children: imageTitle,
1333},
1334},
1345marginBottom: '0px',
1346},
1347children: imageSubtitle,
1348},
1349},
1355},
1356attachment && {
1357// Right panel with image
1358type: 'div',
1359props: {
1457// type: 'img',
1458// props: {
1459// src: logoImageSrc,
1460// alt: 'Labspace Logo',
1461// style: {
1462// width: '72px',
1463// objectFit: 'contain',
1464// marginBottom: '0px', // Add space between image and text
1465// marginRight: '0px',
1466// },
1624const pngBuffer = await render(svg);
16251626// Cache the generated image in Val Town blob
1627try {
1628// Check if the pngBuffer size is less than 128KB (Val Town blob limit)
1631c.header("X-Cache", "MISS");
1632} else {
1633console.warn("Image too large to cache in Val Town blob");
1634c.header("X-Cache", "SKIP");
1635}
1636} catch (error) {
1637console.error("Error caching image in Val Town blob:", error);
1638c.header("X-Cache", "ERROR");
1639}
16401641c.header("Content-Type", "image/png");
1642return c.body(pngBuffer);
1643});
1656<meta charset="UTF-8">
1657<meta name="viewport" content="width=device-width, initial-scale=1.0">
1658<link rel="icon" type="image/png" href="https://labspace.ai/ls2-circle.png" />
1659<title>OG Image Generator</title>
1660<meta property="og:title" content="OG Image Generator" />
1661<meta property="og:description" content="Generate custom Open Graph images for your web pages." />
1662<meta property="og:image" content="https://yawnxyz-og.web.val.run/img2?link=https://yawnxyz-og.web.val.run&title=OG+Image+Generator&subtitle=Generate+custom+Open+Graph+images+for+your+web+pages.&attachment=https://f2.phage.directory/blogalog/og-labspace-ai.png" />
1663<meta property="og:url" content="https://yawnxyz-og.web.val.run/" />
1664<meta property="og:type" content="website" />
1665<meta name="twitter:card" content="summary_large_image" />
1666<meta name="twitter:title" content="OG Image Generator" />
1667<meta name="twitter:description" content="Generate custom Open Graph images for your web pages." />
1668<meta name="twitter:image" content="https://yawnxyz-og.web.val.run/img2?link=https://yawnxyz-og.web.val.run&title=OG+Image+Generator&subtitle=Generate+custom+Open+Graph+images+for+your+web+pages.&attachment=https://f2.phage.directory/blogalog/og-labspace-ai.png" />
1669<script src="https://cdn.tailwindcss.com"></script>
1670<script src="https://unpkg.com/dexie@3.2.2/dist/dexie.js"></script>
1734<div class="AppContainer">
1735<div class="App">
1736<h1>OG Image Generator</h1>
1737<p class="pb-4">Generate custom Open Graph images for your web pages.</p>
1738<form id="ogForm" class="mb-4">
1739<!-- <label class="text-xs text-gray-500" for="title">Title</label> -->
1743<!-- <label class="text-xs text-gray-500" for="link">Page URL</label> -->
1744<input type="text" id="link" placeholder="Enter page URL" class="mb-2" value="${link||''}">
1745<!-- <label class="text-xs text-gray-500" for="attachment">Image Attachment URL (optional)</label> -->
1746<input type="text" id="attachment" placeholder="Enter image attachment URL (optional)" class="mb-2" value="${attachment||''}">
1747<div id="attachmentOptions" class="hidden">
1748<label class="text-xs text-gray-500" for="objectFit">Object Fit</label>
1758<label class="text-xs text-gray-500" for="panelWidth">Panel Width</label>
1759<input type="text" id="panelWidth" placeholder="Panel Width (e.g., 30%)" class="mb-2">
1760<label class="text-xs text-gray-500" for="width">Image Width</label>
1761<input type="text" id="width" placeholder="Image Width (e.g., 1200)" class="mb-2">
1762<label class="text-xs text-gray-500" for="height">Image Height</label>
1763<input type="text" id="height" placeholder="Image Height (e.g., 630)" class="mb-2">
1764</div>
1765<button type="submit" class="bg-button text-white px-4 py-2 rounded hover:bg-button-hover transition-colors">
1766Generate OG Image
1767</button>
1768<span id="timerDisplay" class="ml-2 text-sm"></span>
1769</form>
1770<div id="imageList" class="mb-4 space-y-2"></div>
1771<div class="relative">
1772<div id="loadingPlaceholder" class="hidden w-full h-64 rounded-md mb-4 bg-accent border border-app-accent flex items-center justify-center">
1773<div class="animate-spin rounded-full h-12 w-12 border-4 border-button border-t-transparent"></div>
1774</div>
1775<img id="ogImage" src="" alt="Generated OG Image" class="w-full rounded-md mb-4 hidden">
1776<button id="downloadButton"
1777class="absolute top-2 right-2 bg-button text-white px-2 py-1 rounded text-sm hover:bg-button-hover transition-colors hidden">
1779</button>
1780</div>
1781<div id="imageUrlContainer" class="hidden relative mb-4 break-all bg-accent border border-app-accent rounded-md p-2">
1782<span id="imageUrl" class="text-sm"></span>
1783<button id="copyButton"
1784class="absolute top-[6px] right-1 bg-button text-white px-2 py-1 rounded text-sm hover:bg-button-hover transition-colors hidden">
1789<div class="flex flex-col items-left">
1790<div class="text-sm">Width</div>
1791<span id="imageWidth" class="counter">0</span>
1792</div>
1793<div class="flex flex-col items-left">
1794<div class="text-sm">Height</div>
1795<span id="imageHeight" class="counter">0</span>
1796</div>
1797<div class="flex flex-col items-left">
1798<div class="text-sm">Size</div>
1799<span id="imageSize" class="counter">0 KB</span>
1800</div>
1801</div>
1813<script>
1814// Initialize Dexie
1815const db = new Dexie('OGImageGenerator');
1816db.version(1).stores({
1817images: '++id, link, title, subtitle, imageUrl'
1818});
18191820const form = document.getElementById('ogForm');
1821const ogImage = document.getElementById('ogImage');
1822const downloadButton = document.getElementById('downloadButton');
1823const imageWidth = document.getElementById('imageWidth');
1824const imageHeight = document.getElementById('imageHeight');
1825const imageSize = document.getElementById('imageSize');
1826const imageUrl = document.getElementById('imageUrl');
1827const imageUrlContainer = document.getElementById('imageUrlContainer');
1828const copyButton = document.getElementById('copyButton');
1829const imageList = document.getElementById('imageList');
1830const timerDisplay = document.getElementById('timerDisplay');
1831const attachmentInput = document.getElementById('attachment');
1852}
18531854async function updateImageList() {
1855const images = await db.images.reverse().limit(50).toArray();
1856imageList.innerHTML = images.map(image =>
1857'<div class="image-item flex flex-row justify-between items-center p-2 bg-accent border border-app-accent rounded-md cursor-pointer hover:bg-app-bg-alt transition-colors" data-id="' + image.id + '">' +
1858'<span>' + image.title + '</span>' +
1859'<button class="delete-button self-end bg-button text-white px-2 py-1 rounded text-sm hover:bg-button-hover transition-colors">' +
1860'Delete' +
1863).join('');
1864}
1865imageList.addEventListener('click', async (e) => {
1866if (e.target.classList.contains('image-item') || e.target.tagName === 'SPAN') {
1867const imageId = parseInt(e.target.closest('.image-item').dataset.id);
1868const image = await db.images.get(imageId);
1869// Remove bg-white from all items
1870document.querySelectorAll('.image-item').forEach(item => {
1871item.classList.remove('bg-white');
1872});
1873// Add bg-white to selected item
1874e.target.closest('.image-item').classList.add('bg-white');
18751876if (image) {
1877displayImageData(image);
1878}
1879} else if (e.target.classList.contains('delete-button')) {
1880e.stopPropagation();
1881const imageId = parseInt(e.target.closest('.image-item').dataset.id);
1882if (confirm('Are you sure you want to delete this image?')) {
1883await db.images.delete(imageId);
1884await updateImageList();
1885}
1886}
1887});
18881889function displayImageData(image) {
1890document.getElementById('link').value = image.link;
1891document.getElementById('title').value = image.title;
1892document.getElementById('subtitle').value = image.subtitle;
1893document.getElementById('attachment').value = image.attachment || '';
1894
1895// Show loading placeholder and hide image
1896loadingPlaceholder.classList.remove('hidden');
1897ogImage.classList.add('hidden');
1898
1899// Set up the image load sequence
1900ogImage.onload = () => {
1901// Only hide placeholder after image is fully loaded and rendered
1902loadingPlaceholder.classList.add('hidden');
1903ogImage.classList.remove('hidden');
1904downloadButton.classList.remove('hidden');
1905};
1906
1907// Start loading the image
1908ogImage.src = image.imageUrl;
1909
1910imageUrl.textContent = image.imageUrl;
1911imageUrlContainer.classList.remove('hidden');
1912copyButton.classList.remove('hidden');
1913updateImageStats(image.imageUrl);
1914}
19151916async function updateImageStats(url) {
1917const response = await fetch(url);
1918const blob = await response.blob();
1919const img = new Image();
1920img.onload = function() {
1921imageWidth.textContent = this.width;
1922imageHeight.textContent = this.height;
1923imageSize.textContent = (blob.size / 1024).toFixed(2) + ' KB';
1924}
1925img.src = URL.createObjectURL(blob);
1950params.append('objectPosition', document.getElementById('objectPosition').value);
1951}
1952const imageUrlValue = attachment ? '/img2?' + params.toString() : '/img?' + params.toString();
19531954startTimer();
1955timerDisplay.classList.remove('text-green-600');
19561957// Show loading placeholder and hide image/button
1958loadingPlaceholder.classList.remove('hidden');
1959ogImage.classList.add('hidden');
1960downloadButton.classList.add('hidden');
19611962try {
1963const response = await fetch(imageUrlValue + '&nocache=true');
1964const blob = await response.blob();
1965const url = URL.createObjectURL(blob);
19661967// Set up the image load sequence
1968ogImage.onload = () => {
1969// Only hide placeholder after image is fully loaded and rendered
1970loadingPlaceholder.classList.add('hidden');
1971ogImage.classList.remove('hidden');
1972downloadButton.classList.remove('hidden');
1973};
1974
1975// Start loading the image
1976ogImage.src = url;
19771978imageUrl.textContent = window.location.origin + imageUrlValue;
1979imageUrlContainer.classList.remove('hidden');
1980copyButton.classList.remove('hidden');
19811982updateImageStats(url);
19831984// Save to Dexie
1985await db.images.add({
1986link,
1987title,
1992height: document.getElementById('height').value,
1993objectPosition: document.getElementById('objectPosition').value,
1994imageUrl: window.location.origin + imageUrlValue
1995});
19961997// Update image list
1998await updateImageList();
1999} catch (error) {
2000loadingPlaceholder.classList.add('hidden');
2001console.error('Error generating image:', error);
2002alert('Failed to generate image. Please try again.');
2003} finally {
2004stopTimer();
2008downloadButton.addEventListener('click', () => {
2009const link = document.createElement('a');
2010link.href = ogImage.src;
2011link.download = 'og-image.png';
2012link.click();
2013});
20142015copyButton.addEventListener('click', () => {
2016const urlText = imageUrl.textContent;
2017navigator.clipboard.writeText(urlText).then(() => {
2018// alert('URL copied to clipboard!');
2028document.getElementById('objectPosition').value = 'center';
20292030// Initial load of image list
2031updateImageList();
2032</script>
2033</body>
sqlite_adminREADME.md1 match
3This is a lightweight SQLite Admin interface to view and debug your SQLite data.
45
67It's currently super limited (no pagination, editing data, data-type specific viewers), and is just a couple dozens lines of code over a couple different vals. Forks encouraged! Just comment on the val if you add any features that you want to share.
sqliteExplorerAppREADME.md1 match
3View and interact with your Val Town SQLite data. It's based off Steve's excellent [SQLite Admin](https://www.val.town/v/stevekrouse/sqlite_admin?v=46) val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by [LibSQL Studio](https://github.com/invisal/libsql-studio) by [invisal](https://github.com/invisal). This is now more an SPA, with tables, queries and results showing up on the same page.
45
67## Install
generateQRmain.tsx2 matches
3export default async function server(req: Request): Promise<Response> {
4let url: URL;
5const base64Image = await qrcode(
6`https://loading-file.web.val.run/?peer=${new URL(req.url).searchParams.get("peer")}`,
7{
9},
10);
11return Response.json({ base64Image });
12}
13// Forked from @ramkarthik.GenerateQR
6function App() {
7const [prompt, setPrompt] = useState("");
8const [imageUrl, setImageUrl] = useState("");
9const [count, setCount] = useState(0);
1011const generateImage = useCallback(
12debounce(async (input) => {
13if (input.trim() === "") {
14setImageUrl("");
15return;
16}
17const encodedPrompt = encodeURIComponent(input);
18const url = `https://maxm-imggenurl.web.val.run/${encodedPrompt}`;
19setImageUrl(url);
20}, 300),
21[],
3536useEffect(() => {
37generateImage(prompt);
38}, [prompt, generateImage]);
3940const handleDownload = async () => {
41if (!imageUrl) return;
4243try {
44const response = await fetch(imageUrl);
45const blob = await response.blob();
46const url = window.URL.createObjectURL(blob);
48a.style.display = "none";
49a.href = url;
50a.download = `generated-image-${Date.now()}.png`;
51document.body.appendChild(a);
52a.click();
53window.URL.revokeObjectURL(url);
54} catch (error) {
55console.error("Error downloading image:", error);
56}
57};
60<div className="container">
61<div>Visitor Count: {count}</div>
62<h1>Real-time Image Generator</h1>
63<input
64type="text"
68className="prompt-input"
69/>
70<div className="image-container">
71{imageUrl
72? (
73<>
74<img src={imageUrl} alt={prompt} className="generated-image" />
75<button onClick={handleDownload} className="download-button">
76Download Image
77</button>
78</>
79)
80: <div className="placeholder">Image will appear here</div>}
81</div>
82<div>
133export default async function server(request: Request): Promise<Response> {
134const { sqlite } = await import("https://esm.town/v/stevekrouse/sqlite");
135const KEY = "imageGen";
136137// Create table if it doesn't exist
167<html>
168<head>
169<title>Real-time Image Generator</title>
170<meta name="viewport" content="width=device-width, initial-scale=1">
171<style>${css}</style>
222color: #888;
223}
224.image-container {
225background-color: #1e1e1e;
226border-radius: 8px;
233border: 1px solid #444;
234}
235.generated-image {
236max-width: 100%;
237max-height: 500px;
10- Request: `https://sammeltassen-rijks.web.val.run/200738982`
1112The val first requests a machine readable representation of the object's metadata. For more information, please refer to the [Rijksmuseum Data Services](https://data.rijksmuseum.nl/docs/http/). The `edm` profile contains a pointer to the IIIF Image API Enpoint hosted at [Micrio](https://micr.io/). For debugging you can request the raw `edm` response (converted to json) as follows:
1314- EDM response: `https://sammeltassen-rijks.web.val.run/200738982?format=edm`
27- [Album met de kaart van de stad Rotterdam](https://sammeltassen-rijks.web.val.run/200646411)
2829NB: Unfortunately, not all objects include pointers to the IIIF image. This is for example the case for the The Night Watch (id: `200107928`). In such cases, a manifest is returned without canvasses.
3031Todo:
12export default function handler(req) {
3return new Response(`"\n <!DOCTYPE html>\n <html>\n <head>\n <title>Claude Chat Conversation</title>\n <meta charset=\"UTF-8\">\n <style>\n body {\n font-family: system-ui, -apple-system, sans-serif;\n line-height: 1.5;\n max-width: 800px;\n margin: 0 auto;\n padding: 20px;\n background: #f9fafb;\n }\n .message {\n margin: 20px 0;\n padding: 15px;\n border-radius: 8px;\n }\n .human {\n background: #e5e7eb;\n }\n .assistant {\n background: #dbeafe;\n }\n .role {\n font-weight: bold;\n margin-bottom: 8px;\n }\n </style>\n </head>\n <body>\n \n <div class=\"message human\">\n <div class=\"role\">Human</div>\n <div class=\"content\">I want to build a Chrome extension that automatically cleans up my Chrome tabs in the background. Is it possible with the Chrome extension API? Can it look for all the tabs? Can it combine them? Can it close them? Can it do things like these?</div>\n </div>\n \n <div class=\"message assistant\">\n <div class=\"role\">Claude</div>\n <div class=\"content\">Yes, it's absolutely possible to build a Chrome extension that manages your tabs! The Chrome Extensions API provides robust capabilities for tab management. Let me break down what's possible and show you a basic extension structure.Tab Manager Chrome ExtensionClick to open codeI've created a basic Chrome extension that demonstrates the key tab management capabilities you asked about. Here's what the extension can do:\n\nLook for all tabs: Yes, using chrome.tabs.query({}), you can get all open tabs across all windows.\nCombine/close tabs: Yes, you can:\n\nClose tabs using chrome.tabs.remove(tabId)\nGroup similar tabs by domain\nClose duplicate tabs\nClose inactive tabs\n\n\nBackground operations: Yes, using a service worker (background.js), the extension can:\n\nRun periodic cleanup tasks using chrome.alarms\nMonitor tab activity\nPerform automated management\n\n\n\nThe example extension I've provided includes:\n\nmanifest.json: Declares the necessary permissions and structure\nbackground.js: Handles background tab management\n\nRuns every 30 minutes\nGroups tabs by domain\nCloses duplicates\nCloses inactive tabs (older than 7 days)\n\n\npopup.html/js: Provides a simple user interface\n\nTo make this extension more powerful, you could add features like:\n\nCustom rules for tab management\nWhitelist for tabs to never close\nTab grouping by topic using URL patterns\nAuto-bookmarking before closing\nTab suspension for inactive tabs\nTab search and organization\n\nWould you like me to expand on any of these features or show you how to implement a specific functionality?</div>\n </div>\n \n <div class=\"message human\">\n <div class=\"role\">Human</div>\n <div class=\"content\">I want to have a way to check these tabs - what information do I get about a tab? I want to essentially build an OpenAI thingy on top, like an AI-based thing where I like every few seconds I send a API call to AI and ask it to do something.\nSo I want to know what information do I have about these tabs? Do I know how much I read them? Do I know the description? The web page? I don't know what I do know.</div>\n </div>\n \n <div class=\"message assistant\">\n <div class=\"role\">Claude</div>\n <div class=\"content\">I'll help you understand what tab information is available and then show you how we could integrate this with OpenAI's API for intelligent tab management.Tab Information Analyzer with AI IntegrationClick to open codeHere's what information you can get about tabs and how you can integrate it with AI:\n\nBasic Tab Information (Available directly from chrome.tabs API):\n\n\nid: Unique identifier\nurl: Complete URL\ntitle: Page title\nfavIconUrl: Favicon URL\nstatus: Loading status\nactive: Whether it's the current active tab\npinned: If the tab is pinned\nindex: Position in the window\nwindowId: Which window it belongs to\nopenerTabId: Which tab opened this one\nlastAccessed: When it was last accessed\n\n\nPage Content (Available through content scripts):\n\n\nMeta description\nMain content text\nHeadings\nLinks\nImages\nForms\nAny DOM element\n\n\nUser Interaction Data (Can be tracked):\n\n\nTime spent on the tab\nScroll position\nClick interactions\nKeyboard activity\nForm inputs\nCopy/paste actions\nMouse movements\n\n\nCustom Tracking (You can implement):\n\n\nReading progress (scroll depth)\nForm completion status\nVideo watching progress\nComment/interaction status\nCustom tags or categories\n\nFor AI integration, you can:\n\nPeriodic Analysis:\n\n\nSend tab data to OpenAI every few minutes\nGet recommendations for tab management\nAnalyze content relationships between tabs\nIdentify priority or stale tabs\n\n\nSmart Features:\n\n\nGroup related tabs based on content similarity\nIdentify duplicate or similar content\nSummarize tab content\nPrioritize tabs based on content and usage\nGenerate tab descriptions\nCreate smart bookmarks with AI-generated tags\n\n\nUser Behavior Analysis:\n\n\nTrack reading patterns\nIdentify important tabs based on interaction\nPredict which tabs might be needed soon\nSuggest tab cleanup based on usage patterns\n\nTo implement this:\n\nSet up a background service worker that:\n\n\nCollects tab data periodically\nSends it to OpenAI\nProcesses recommendations\nTakes automated actions\n\n\nUse content scripts to:\n\n\nTrack detailed user interaction\nExtract relevant page content\nMonitor page changes\n\n\nStore historical data in:\n\n\nchrome.storage.local for persistence\nIndex patterns for quick lookup\nUsage statistics for better recommendations\n\nWould you like me to:\n\nShow how to implement any specific feature?\nDemonstrate a particular AI integration pattern?\nExplain how to track specific user behaviors?\nShow how to handle the OpenAI API responses?</div>\n </div>\n \n <div class=\"message human\">\n <div class=\"role\">Human</div>\n <div class=\"content\">So basically you're saying content script is something that's loaded on every page when it's there, and it's like a lightweight analytics script that runs in source data in local storage. Is there a way to do this in a really lightweight way? I don't want to make this a big deal.\nAlso, is it possible to like Chrome also has tab grouping. Is that also possible by API?</div>\n </div>\n \n <div class=\"message assistant\">\n <div class=\"role\">Claude</div>\n <div class=\"content\">Yes, exactly! You can make this really lightweight, and Chrome's tab grouping API is available too. Let me show you a minimal version:Lightweight Tab Manager with GroupsClick to open codeThis lightweight version shows several key features:\n\nSuper Lightweight Content Script:\n\n\nOnly tracks visibility state changes\nMinimal memory footprint\nNo heavy DOM monitoring\nSimple message passing to background script\n\n\nTab Grouping API:\n\n\nchrome.tabs.group(): Creates tab groups\nchrome.tabGroups.update(): Customizes groups (title, color)\nCan automatically group by domain\nCan create, modify, and remove groups programmatically\n\n\nKey Tab Group Features:\n\njavascriptCopy// Create a group\nconst groupId = await chrome.tabs.group({ tabIds: [tab1Id, tab2Id] });\n\n// Update group appearance\nawait chrome.tabGroups.update(groupId, {\n collapsed: true, // Collapse the group\n title: \"My Group\",\n color: \"red\" // Various colors available\n});\n\n// Add tabs to existing group\nawait chrome.tabs.group({ tabIds: [tab3Id], groupId });\n\n// Ungroup tabs\nawait chrome.tabs.ungroup(tabId);\n\nSimple Data Storage:\n\n\nUses a Map for in-memory storage\nCould easily switch to chrome.storage.local for persistence\nMinimal data structure\n\nThe tab grouping API is quite powerful and lets you:\n\nCreate/delete groups\nColor-code groups\nCollapse/expand groups\nMove groups between windows\nAdd/remove tabs from groups\nGet group information\n\nWould you like me to:\n\nShow more tab grouping features?\nMake the tracking even lighter?\nAdd specific AI integration patterns?\nShow how to handle group collapse/expand events?</div>\n </div>\n \n </body>\n </html>\n"`, {
4headers: {
5"Content-Type": "text/html",
sqliteExplorerAppREADME.md1 match
3View and interact with your Val Town SQLite data. It's based off Steve's excellent [SQLite Admin](https://www.val.town/v/stevekrouse/sqlite_admin?v=46) val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by [LibSQL Studio](https://github.com/invisal/libsql-studio) by [invisal](https://github.com/invisal). This is now more an SPA, with tables, queries and results showing up on the same page.
45
67## Install
TaylorSwiftMomentsmain.tsx70 matches
7{
8name: "Debut",
9image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/1.jpg",
10logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361917469-1.png",
11themeColor1: "#A9CBAA",
14{
15name: "Fearless",
16image: "https://assets.dmi.umgapps.com/assets/taylor-swift/landing-images/1678544276252-2-v2.jpg",
17logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361934058-2.png",
18themeColor1: "#FDDAA6",
21{
22name: "Speak Now",
23image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/3.jpg",
24logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361938785-3.png",
25themeColor1: "#D6BADC",
28{
29name: "Red",
30image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/4.jpg",
31logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361944562-4.png",
32themeColor1: "#72333C",
35{
36name: "1989",
37image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/5.jpg",
38logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361948489-5.png",
39themeColor1: "#034A62",
42{
43name: "Reputation",
44image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/6.jpg",
45logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361951894-6.png",
46themeColor1: "#2A2628",
49{
50name: "Lover",
51image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/7.jpg",
52logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361917469-1.png",
53themeColor1: "#FBB3D1",
56{
57name: "Folklore",
58image: "https://assets.dmi.umgapps.com/assets/taylor-swift/tour-site/landing-images/8.jpg",
59logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361955371-7.png",
60themeColor1: "#D5D5D5",
63{
64name: "Evermore",
65image: "https://assets.dmi.umgapps.com/assets/taylor-swift/landing-images/1678544281122-9-v2.jpg",
66logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361958514-8.png",
67themeColor1: "#E0C9AF",
70{
71name: "Midnights",
72image: "https://assets.dmi.umgapps.com/assets/ts/ttpd/1715195830303-new-midnights.jpg",
73logoUrl: "https://assets.dmi.umgapps.com/assets/taylor-swift/logosv2/1682361961823-9.png",
74themeColor1: "#E0EDFD",
77{
78name: "TTPD",
79image: "https://assets.dmi.umgapps.com/assets/ts/ttpd/1715168073245-ttpd.jpg",
80logoUrl: "https://assets.dmi.umgapps.com/assets/ts/ttpd/1715168828695-10.png",
81themeColor1: "#9E958B",
111className="absolute inset-0 w-full"
112style={{
113backgroundImage: `url(${era.image})`,
114backgroundPosition: "center top",
115backgroundSize: "cover",
130}
131132function ImageGallery({ images, activeEra, onLike, onDelete }) {
133const downloadImage = async (url, filename) => {
134try {
135const response = await fetch(url);
144window.URL.revokeObjectURL(blobUrl);
145} catch (error) {
146console.error("Error downloading image:", error);
147}
148};
150return (
151<div className="mt-12">
152<h2 className="text-2xl font-bold mb-4">Generated Images Gallery</h2>
153<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
154{images.map((image) => (
155<div key={image.id} className="bg-white bg-opacity-80 rounded-lg overflow-hidden shadow-lg group relative">
156<img src={image.url} alt={image.original_prompt} className="w-full h-48 object-cover" />
157<div className="p-4">
158<p className="text-sm text-gray-600">
159{truncatePrompt(image.original_prompt)}
160</p>
161<div className="flex justify-between items-center mt-2">
162<p className="text-xs text-gray-500">
163Likes: {image.likes}
164</p>
165<div>
166<button
167onClick={() =>
168onLike(image.id)}
169className="px-3 py-1 text-sm rounded-full transition-colors duration-200 mr-2"
170style={{
181className="absolute top-0 left-0 right-0 bottom-10 bg-black bg-opacity-75 text-white p-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 overflow-hidden cursor-pointer flex flex-col justify-center items-center"
182onClick={() =>
183downloadImage(image.url, `taylor-swift-${image.era}-${image.id}.jpg`)}
184>
185<p className="text-xs text-center mb-2">{truncatePrompt(image.original_prompt, 100)}</p>
186<p className="text-sm font-bold">Click to Download</p>
187</div>
195function App() {
196const [prompt, setPrompt] = useState("");
197const [imageUrl, setImageUrl] = useState("");
198const [loading, setLoading] = useState(false);
199const [activeEra, setActiveEra] = useState(swiftEras[10]);
200const [galleryImages, setGalleryImages] = useState([]);
201const [enhancedPrompt, setEnhancedPrompt] = useState("");
202203useEffect(() => {
204fetchImages();
205}, []);
206207const fetchImages = async () => {
208try {
209const response = await fetch("/api/images");
210const images = await response.json();
211setGalleryImages(images);
212} catch (error) {
213console.error("Error fetching images:", error);
214}
215};
216217const generateImage = async (e?: React.FormEvent) => {
218e?.preventDefault();
219setLoading(true);
238input: {
239prompt: enhancedPrompt,
240image_size: "landscape_4_3",
241num_inference_steps: 4,
242num_images: 1,
243enable_safety_checker: true,
244sync_mode: true,
245},
246});
247const generatedImageUrl = result.data.images[0].url;
248setImageUrl(generatedImageUrl);
249} catch (error) {
250console.error("Error generating image:", error);
251} finally {
252setLoading(false);
258};
259260const likeImage = async (imageId) => {
261try {
262await fetch(`/api/images/${imageId}/like`, { method: "POST" });
263fetchImages(); // Refresh the gallery after liking
264} catch (error) {
265console.error("Error liking image:", error);
266}
267};
268269const deleteImage = async (imageId) => {
270try {
271await fetch(`/api/images/${imageId}`, { method: "DELETE" });
272fetchImages(); // Refresh the gallery after deleting
273} catch (error) {
274console.error("Error deleting image:", error);
275}
276};
278const addToGallery = async () => {
279try {
280await fetch("/api/images", {
281method: "POST",
282headers: { "Content-Type": "application/json" },
283body: JSON.stringify({
284url: imageUrl,
285original_prompt: prompt,
286enhanced_prompt: enhancedPrompt,
288}),
289});
290fetchImages(); // Refresh the gallery after adding
291setImageUrl(""); // Clear the generated image
292} catch (error) {
293console.error("Error adding image to feed:", error);
294}
295};
300className="absolute inset-0 z-0"
301style={{
302backgroundImage:
303`url("data:image/png;base64,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")`,
304opacity: 0.05,
305}}
312>
313<div className="max-w-3xl mx-auto">
314<form className="mb-8" onSubmit={generateImage}>
315<h2 className="text-2xl font-bold mb-4">
316Your Favorite {activeEra.name} Era Moment
343}}
344>
345{loading ? "Generating..." : "Generate Image"}
346</button>
347</div>
348</form>
349{imageUrl && (
350<div className="bg-white bg-opacity-80 rounded-lg overflow-hidden shadow-lg mb-8">
351<img src={imageUrl} alt="Generated Taylor Swift era image" className="w-full h-auto" />
352<div className="p-4">
353<button
364</div>
365)}
366<ImageGallery images={galleryImages} activeEra={activeEra} onLike={likeImage} onDelete={deleteImage} />
367</div>
368</main>
397398await sqlite.execute(`
399CREATE TABLE IF NOT EXISTS ${KEY}_images_${SCHEMA_VERSION} (
400id INTEGER PRIMARY KEY AUTOINCREMENT,
401url TEXT NOT NULL,
418<meta charset="UTF-8">
419<meta name="viewport" content="width=device-width, initial-scale=1.0">
420<title>Taylor Swift Eras Image Generator</title>
421<script src="https://cdn.tailwindcss.com"></script>
422<style>
452role: "system",
453content:
454"You are an expert in Taylor Swift's music and eras. Your task is to enhance the given prompt to create a more detailed and vivid description for image generation. Focus 80% on enhancing the user's input and 20% on incorporating elements from the specified era's aesthetic, themes, and iconic elements. Fulfill the user's prompt as best as possible, ensuring that any additional details like who Taylor is with or where she is are accounted for in the enhanced prompt. Make sure the user's prompt stays on topic, and act as a moderator if the user prompts something inappropriate.",
455},
456{
457role: "user",
458content:
459`Enhance this prompt for image generation, focusing mainly on the user's input with a slight touch of Taylor Swift's ${era} era: ${prompt}`,
460},
461],
471}
472473if (url.pathname === "/api/images") {
474if (req.method === "GET") {
475const images = await sqlite.execute(`
476SELECT id, url, original_prompt, enhanced_prompt, era, likes, created_at FROM ${KEY}_images_${SCHEMA_VERSION}
477ORDER BY likes DESC, created_at DESC
478`);
479return new Response(JSON.stringify(images.rows), {
480headers: { "Content-Type": "application/json" },
481});
484await sqlite.execute(
485`
486INSERT INTO ${KEY}_images_${SCHEMA_VERSION} (url, original_prompt, enhanced_prompt, era)
487VALUES (?, ?, ?, ?)
488`,
495}
496497if (url.pathname.startsWith("/api/images/")) {
498const id = url.pathname.split("/")[3];
499if (url.pathname.endsWith("/like") && req.method === "POST") {
500await sqlite.execute(
501`
502UPDATE ${KEY}_images_${SCHEMA_VERSION}
503SET likes = likes + 1
504WHERE id = ?
512await sqlite.execute(
513`
514DELETE FROM ${KEY}_images_${SCHEMA_VERSION}
515WHERE id = ?
516`,