Skip to content

Commit

Permalink
Merge pull request #159 from dcSpark/fix/create-new-icons
Browse files Browse the repository at this point in the history
Using BF API to create banner and icons.
  • Loading branch information
guillevalin authored Feb 5, 2025
2 parents 2c3eedf + c0726fc commit 5173224
Show file tree
Hide file tree
Showing 78 changed files with 326 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/run_tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
SHINKAI_STORE_ADDR: ${{ vars.SHINKAI_STORE_ADDR }}
SHINKAI_STORE_TOKEN: ${{ secrets.SHINKAI_STORE_TOKEN }}
USE_DOCKER: true
SHINKAI_NODE_IMAGE: "dcsparkdevops/shinkai-node:${{ inputs.node_version || 'debug-latest' }}"
SHINKAI_NODE_IMAGE: "dcsparkdevops/shinkai-node:${{ inputs.node_version || 'release-latest' }}"
run: |
./scripts/run_node.sh &
timeout 60 bash -c 'until curl -s --location "$SHINKAI_NODE_ADDR/v2/health_check" | jq -e ".status == \"ok\"" > /dev/null; do sleep 1; done'
Expand Down
Binary file added scripts/Lato-Bold.ttf
Binary file not shown.
103 changes: 103 additions & 0 deletions scripts/add_text_to_image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Image, TextLayout } from "https://deno.land/x/imagescript@1.2.15/mod.ts";
import { join, dirname, fromFileUrl } from "https://deno.land/std@0.210.0/path/mod.ts";
import { existsSync } from "https://deno.land/std@0.210.0/fs/mod.ts";

const __dirname = dirname(fromFileUrl(import.meta.url));
const toolsDir = join(__dirname, '..', 'tools');
const fontPath = join(__dirname, 'Lato-Bold.ttf');

async function addTextToImage(inputPath: string, outputPath: string, toolName: string): Promise<void> {
try {
// Load the background image
const image = await Image.decode(await Deno.readFile(inputPath));
const width = image.width;
const height = image.height;

// Calculate font size (start with 15% of height)
const fontSize = Math.floor(height * 0.15);
console.log(`Image dimensions: ${width}x${height}, fontSize: ${fontSize}`);

// Load font
const font = await Deno.readFile(fontPath);

// Create text layout
const layout = new TextLayout({
maxWidth: Math.floor(width * 0.9),
maxHeight: Math.floor(height * 0.9),
wrapStyle: "word",
align: "center"
});

// Create shadow text
const shadowText = Image.renderText(font, fontSize, toolName, 0x000000FF, layout);

// Calculate bottom right position with padding
const padding = Math.floor(height * 0.05); // 5% of height as padding
let x = Math.floor(width - shadowText.width - padding);
let y = Math.floor(height - shadowText.height - padding);
console.log(`Text position: (${x},${y})`);

// Ensure we don't go beyond image boundaries
if (x < 0) x = padding;
if (y < 0) y = padding;

// Draw shadow with multiple offsets for thickness
for (let offsetX = 1; offsetX <= 4; offsetX++) {
for (let offsetY = 1; offsetY <= 4; offsetY++) {
await image.composite(shadowText, x + offsetX, y + offsetY);
}
}

// Create and draw main text
const mainText = Image.renderText(font, fontSize, toolName, 0xFFFFFFFF, layout);
await image.composite(mainText, x, y);

// Save the result
await Deno.writeFile(outputPath, await image.encode());
} catch (error) {
console.error(`Error processing ${inputPath}:`, error);
throw error;
}
}

async function readToolMetadata(toolPath: string) {
const metadataPath = join(toolPath, 'metadata.json');
const metadata = JSON.parse(await Deno.readTextFile(metadataPath));
return metadata;
}

async function main() {
// Get all tool directories
const tools = Array.from(Deno.readDirSync(toolsDir))
.filter(item => item.isDirectory)
.map(item => item.name);

for (const tool of tools) {
const toolPath = join(toolsDir, tool);
const backgroundPath = join(toolPath, 'banner_background.png');
const bannerPath = join(toolPath, 'banner.png');

// Skip if background doesn't exist
if (!existsSync(backgroundPath)) {
console.log(`Skipping ${tool}: no banner_background.png found`);
continue;
}

try {
console.log(`Processing ${tool}...`);
const metadata = await readToolMetadata(toolPath);
await addTextToImage(backgroundPath, bannerPath, metadata.name);
console.log(`Successfully generated banner.png for ${tool}`);
} catch (error) {
console.error(`Error processing tool ${tool}:`, error);
}
}
}

// Run if called directly
if (import.meta.main) {
main().catch(error => {
console.error('Fatal error:', error);
Deno.exit(1);
});
}
222 changes: 222 additions & 0 deletions scripts/generate_images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { config as dotenvConfig } from "https://deno.land/x/dotenv@v3.2.2/mod.ts";
import { join, dirname, fromFileUrl } from "https://deno.land/std@0.210.0/path/mod.ts";
import { existsSync } from "https://deno.land/std@0.210.0/fs/mod.ts";
import { createCanvas, loadImage } from "https://deno.land/x/canvas/mod.ts";
import axios from "npm:axios@1.6.2";

// Load environment variables
dotenvConfig();

const BANNER_WIDTH = 1280;
const BANNER_HEIGHT = 640;
const ICON_SIZE = 256;
const BFL_API_URL = 'https://api.us1.bfl.ai/v1';
const BFL_API_KEY = Deno.env.get("BFL_API_KEY");

interface ToolMetadata {
name: string;
description: string;
}

interface GenerateImageOptions {
prompt: string;
negative_prompt?: string;
width?: number;
height?: number;
num_outputs?: number;
guidance_scale?: number;
seed?: number;
}

interface BFLResponse {
id: string;
status: string;
result?: {
sample: string;
};
}

async function readToolMetadata(toolPath: string): Promise<ToolMetadata> {
const metadataPath = join(toolPath, 'metadata.json');
const metadata = JSON.parse(await Deno.readTextFile(metadataPath));
return metadata;
}

async function waitForImageGeneration(requestId: string): Promise<string> {
while (true) {
const response = await axios.get<BFLResponse>(`${BFL_API_URL}/get_result?id=${requestId}`, {
headers: {
'accept': 'application/json',
'x-key': BFL_API_KEY
}
});

if (response.data.status === 'Ready' && response.data.result) {
return response.data.result.sample;
}

if (response.data.status === 'Failed') {
throw new Error('Image generation failed');
}

// Wait before polling again
await new Promise(resolve => setTimeout(resolve, 500));
}
}

async function generateImage(options: GenerateImageOptions): Promise<string> {
const defaultOptions = {
width: 1024,
height: 1024,
// prompt_upsampling: true,
};

if (!options.prompt) {
throw new Error('Prompt is required');
}

const mergedOptions = { ...defaultOptions, ...options };

try {
// Initial request to start generation
const response = await axios.post<BFLResponse>(
`${BFL_API_URL}/flux-pro-1.1`,
{
prompt: mergedOptions.prompt,
width: mergedOptions.width,
height: mergedOptions.height
},
{
headers: {
'accept': 'application/json',
'x-key': BFL_API_KEY,
'Content-Type': 'application/json'
}
}
);

// Poll for results
return await waitForImageGeneration(response.data.id);
} catch (error) {
console.error('Error generating image:', error);
throw error;
}
}

async function copyPlaceholderImages(scriptDir: string, toolPath: string, missingFiles: string[]): Promise<void> {
console.log("Using placeholder images for:", missingFiles.join(", "));
for (const file of missingFiles) {
const sourcePath = join(scriptDir, file);
const destPath = join(toolPath, file);
if (existsSync(sourcePath)) {
await Deno.copyFile(sourcePath, destPath);
}
}
}

async function generateToolImages(toolPath: string, metadata: ToolMetadata): Promise<void> {
const bannerPath = join(toolPath, 'banner_background.png');
const iconPath = join(toolPath, 'icon.png');
const missingFiles: string[] = [];

if (!existsSync(bannerPath)) missingFiles.push('banner_background.png');
if (!existsSync(iconPath)) missingFiles.push('icon.png');

if (!missingFiles.length) {
console.log("All images already exist, skipping generation");
return;
}

console.log(`Generating missing images: ${missingFiles.join(', ')}`);

try {
if (missingFiles.includes('banner_background.png')) {
const bannerPrompt =
`Create a professional banner image for a software tool named '${metadata.name}'. ` +
`The tool description is: ${metadata.description}. Use a modern, minimalist style with subtle tech elements. ` +
"Make it suitable for a developer tool interface. Use a cohesive color scheme, dark colors, allowing for white text to be readable on the image. " +
"and include some abstract geometric elements that represent the tool's function." +
"Do not include any text, just create a beautiful image. And I repeat, do not include any text.";

console.log(`Generating banner for ${metadata.name}...`);
const bannerUrl = await generateImage({
prompt: bannerPrompt,
width: 1280,
height: 736,
num_outputs: 1
});

const response = await axios.get(bannerUrl, { responseType: 'arraybuffer' });
await Deno.writeFile(join(toolPath, 'banner_background.png'), new Uint8Array(response.data));
}

if (missingFiles.includes('icon.png')) {
const iconPrompt =
`Create a simple, memorable icon for '${metadata.name}'. The icon should represent ` +
`${metadata.description}. Use a minimal design with clear shapes and limited colors. ` +
"Make it recognizable at small sizes and suitable for a developer tool. " +
"The icon should work well as an app icon or menu item.";

console.log(`Generating icon for ${metadata.name}...`);
const iconUrl = await generateImage({
prompt: iconPrompt,
width: 256,
height: 256,
num_outputs: 1
});

const response = await axios.get(iconUrl, { responseType: 'arraybuffer' });
await Deno.writeFile(join(toolPath, 'icon.png'), new Uint8Array(response.data));
}

console.log("Successfully generated missing images");
} catch (error) {
console.error('Error generating images:', error);
throw error;
}
}

async function main() {
if (!BFL_API_KEY) {
console.error('Please set BFL_API_KEY environment variable');
Deno.exit(1);
}

const __dirname = dirname(fromFileUrl(import.meta.url));
const toolsDir = join(__dirname, '..', 'tools');
const scriptDir = __dirname;

// Get all tool directories
const tools = Array.from(Deno.readDirSync(toolsDir))
.filter(item => item.isDirectory)
.map(item => item.name);

for (const tool of tools) {
const toolPath = join(toolsDir, tool);
console.log(`\nProcessing tool: ${tool}`);

try {
const metadata = await readToolMetadata(toolPath);
await generateToolImages(toolPath, metadata);
} catch (error) {
console.error(`Error processing tool ${tool}:`, error);
// Copy placeholder images if available
const missingFiles = [];
if (!existsSync(join(toolPath, 'banner_background.png'))) missingFiles.push('banner_background.png');
if (!existsSync(join(toolPath, 'icon.png'))) missingFiles.push('icon.png');
if (missingFiles.length) {
await copyPlaceholderImages(scriptDir, toolPath, missingFiles);
}
}
}
}

// Run if called directly
if (import.meta.main) {
main().catch(error => {
console.error('Fatal error:', error);
Deno.exit(1);
});
}

export { generateImage, GenerateImageOptions };
Binary file modified tools/article-scraper/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/arxiv-download/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/arxiv-search/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/chess-evaluate/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/chess-generate-image/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/chess-move/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coin-flip/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coinbase-call-faucet/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coinbase-create-wallet/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coinbase-get-balance/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coinbase-get-my-address/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coinbase-get-transactions/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coinbase-send-tx/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coingecko-get-coins/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/coingecko-get-historical-data/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/dev-airtable/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/dev-github/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/dev-gmail/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/dev-google-drive/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/dev-twitter/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/download-page/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/duckduckgo-search/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/elevenlabs-isolate-voice/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/elevenlabs-text-to-speech/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/email-imap-fetcher/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tools/email-responder/icon.png
Binary file modified tools/email-sender/icon.png
Binary file modified tools/file-read/icon.png
Binary file modified tools/file-update/icon.png
Binary file modified tools/file-write/icon.png
Binary file modified tools/game-crypto-2048/icon.png
Binary file modified tools/google-news-search/icon.png
Binary file modified tools/google-search/icon.png
Binary file modified tools/hacker-news/icon.png
Binary file modified tools/iterm-control/icon.png
Binary file modified tools/iterm-read/icon.png
Binary file modified tools/iterm-write/icon.png
Binary file modified tools/macos-calendar/icon.png
Binary file modified tools/macos-clipboard/icon.png
Binary file modified tools/macos-finder/icon.png
Binary file modified tools/macos-iterm/icon.png
Binary file modified tools/macos-notifications/icon.png
Binary file modified tools/macos-say-text-to-audio/icon.png
Binary file modified tools/macos-system/icon.png
Binary file modified tools/math-exp/icon.png
Binary file modified tools/meme-generator/icon.png
Binary file modified tools/memory/icon.png
Binary file modified tools/mermaid/icon.png
Binary file modified tools/news-aggregator/icon.png
Binary file modified tools/ntfy-push/icon.png
Binary file modified tools/pdf-summarize-to-audio/icon.png
Binary file modified tools/pdf-text-extractor/icon.png
Binary file modified tools/pdf-whitepaper-analyzer/icon.png
Binary file modified tools/perplexity-api/icon.png
Binary file modified tools/perplexity/icon.png
Binary file modified tools/podcast-to-podcast/icon.png
Binary file modified tools/pubmed-search/icon.png
Binary file modified tools/shinkai-question-learner/icon.png
Binary file modified tools/smart-search/icon.png
Binary file modified tools/stagehand-generic/icon.png
Binary file modified tools/stock-technical-analysis/icon.png
Binary file modified tools/system-hw-info/icon.png
Binary file modified tools/text-to-audio-kokoro/icon.png
Binary file modified tools/twitter-post/icon.png
Binary file modified tools/webcam-capture/icon.png
Binary file modified tools/wikimedia-featured-content/icon.png
Binary file modified tools/wikimedia-historical-events/icon.png
Binary file modified tools/wikimedia-page-content/icon.png
Binary file modified tools/wikimedia-search-titles/icon.png
Binary file modified tools/wikimedia-search/icon.png
Binary file modified tools/x-twitter-search/icon.png
Binary file modified tools/youtube-download-mp3/icon.png
Binary file modified tools/youtube-search/icon.png
Binary file modified tools/youtube-summary/icon.png

0 comments on commit 5173224

Please sign in to comment.