// Name: Interactive Text Analysis Tutorial // Description: Learn text analysis with voice-guided navigation // Author: edehu135 import "@johnlindquist/kit" interface AnalysisResult { wordCount: number charCount: number sentenceCount: number paragraphCount: number readingTime: number sentiment: 'positive' | 'negative' | 'neutral' keywords: string[] } let currentStep = 0 let tutorialText = "" let isListening = false const steps = [ { title: "Welcome to Text Analysis", content: `# Welcome to the Interactive Text Analysis Tutorial! š This tutorial will teach you how to analyze text using various metrics and techniques. **What you'll learn:** - Word and character counting - Readability analysis - Sentiment detection - Keyword extraction **Voice Commands:** - Say "next" to go to the next step - Say "previous" to go back - Say "analyze" to run analysis - Say "help" for assistance Ready to begin? Say "next" or click Next!`, action: null }, { title: "Step 1: Basic Text Metrics", content: `# Step 1: Basic Text Metrics š Let's start by entering some text to analyze. The most basic metrics include: - **Word Count**: Total number of words - **Character Count**: Total characters (with and without spaces) - **Sentence Count**: Number of sentences - **Paragraph Count**: Number of paragraphs Try entering some text below and we'll analyze it together!`, action: async () => { tutorialText = await editor({ value: tutorialText || "Enter your text here to analyze...", placeholder: "Type or paste text to analyze", hint: "Enter text and say 'analyze' or press Cmd+Enter to continue" }) } }, { title: "Step 2: Analyzing Your Text", content: `# Step 2: Text Analysis Results š Here's what we found in your text:`, action: async () => { if (!tutorialText.trim()) { tutorialText = "This is a sample text for analysis. It contains multiple sentences and demonstrates various text metrics. The quick brown fox jumps over the lazy dog." } const analysis = analyzeText(tutorialText) return ` **Your Text:** "${tutorialText.substring(0, 100)}${tutorialText.length > 100 ? '...' : ''}" **Analysis Results:** - š **Words**: ${analysis.wordCount} - š¤ **Characters**: ${analysis.charCount} - š **Sentences**: ${analysis.sentenceCount} - š **Paragraphs**: ${analysis.paragraphCount} - ā±ļø **Reading Time**: ~${analysis.readingTime} minutes - š **Sentiment**: ${analysis.sentiment} - š·ļø **Top Keywords**: ${analysis.keywords.slice(0, 5).join(', ')} Say "next" to learn about advanced features!` } }, { title: "Step 3: Advanced Analysis", content: `# Step 3: Advanced Text Analysis š Now let's explore more sophisticated analysis techniques: **Readability Scores:** - Measures how easy text is to read - Based on sentence length and word complexity **Sentiment Analysis:** - Determines emotional tone (positive/negative/neutral) - Useful for social media monitoring **Keyword Extraction:** - Identifies most important words - Helps with SEO and content optimization **Text Complexity:** - Analyzes vocabulary difficulty - Suggests reading level Try analyzing different types of text to see how results vary!`, action: null }, { title: "Step 4: Interactive Practice", content: `# Step 4: Practice Time! šÆ Let's practice with different text types. Choose what to analyze: 1. **News Article** - Formal, informative text 2. **Social Media Post** - Casual, emotional text 3. **Technical Documentation** - Complex, specialized text 4. **Creative Writing** - Descriptive, artistic text 5. **Your Own Text** - Upload or paste custom content Say the number or name of your choice!`, action: async () => { const choice = await arg("Choose text type to practice with:", [ { name: "News Article", value: "news" }, { name: "Social Media Post", value: "social" }, { name: "Technical Documentation", value: "tech" }, { name: "Creative Writing", value: "creative" }, { name: "Your Own Text", value: "custom" } ]) const sampleTexts = { news: "Breaking news: Scientists have discovered a new method for analyzing text that could revolutionize how we understand written communication. The breakthrough, published in the Journal of Computational Linguistics, demonstrates significant improvements in accuracy and speed.", social: "OMG just tried this amazing new coffee shop! āļø The vibes are incredible and the barista was so friendly! Definitely coming back tomorrow š #coffee #local #amazing", tech: "The implementation utilizes a recursive algorithm with O(n log n) complexity to parse the abstract syntax tree. Dependencies include Node.js runtime environment and the following npm packages: lodash, moment, and express framework.", creative: "The moonlight danced across the rippling water, casting silver shadows that whispered secrets to the ancient oak trees. In the distance, a nightingale's melody wove through the cool evening air like threads of liquid gold.", custom: "" } if (choice === "custom") { tutorialText = await editor("Enter your own text to analyze:") } else { tutorialText = sampleTexts[choice] } const analysis = analyzeText(tutorialText) return ` **Selected Text Type**: ${choice.toUpperCase()} **Text**: "${tutorialText}" **Analysis**: - Words: ${analysis.wordCount} | Characters: ${analysis.charCount} - Sentences: ${analysis.sentenceCount} | Paragraphs: ${analysis.paragraphCount} - Reading Time: ${analysis.readingTime} min | Sentiment: ${analysis.sentiment} - Keywords: ${analysis.keywords.slice(0, 3).join(', ')} Notice how different text types have different characteristics!` } }, { title: "Congratulations!", content: `# Tutorial Complete! š You've successfully learned how to: ā Count words, characters, and sentences ā Analyze text sentiment ā Extract keywords ā Calculate reading time ā Use voice commands for navigation **Next Steps:** - Try analyzing your own documents - Experiment with different text types - Use these techniques in your projects **Voice Commands Reminder:** - "analyze" - Run analysis on current text - "help" - Show available commands - "restart" - Begin tutorial again Thank you for completing the Text Analysis Tutorial!`, action: null } ] function analyzeText(text: string): AnalysisResult { const words = text.trim().split(/\s+/).filter(word => word.length > 0) const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0) const paragraphs = text.split(/\n\s*\n/).filter(p => p.trim().length > 0) // Simple sentiment analysis const positiveWords = ['good', 'great', 'amazing', 'excellent', 'wonderful', 'fantastic', 'love', 'best', 'awesome', 'incredible'] const negativeWords = ['bad', 'terrible', 'awful', 'hate', 'worst', 'horrible', 'disgusting', 'disappointing', 'sad', 'angry'] const lowerText = text.toLowerCase() const positiveCount = positiveWords.filter(word => lowerText.includes(word)).length const negativeCount = negativeWords.filter(word => lowerText.includes(word)).length let sentiment: 'positive' | 'negative' | 'neutral' = 'neutral' if (positiveCount > negativeCount) sentiment = 'positive' else if (negativeCount > positiveCount) sentiment = 'negative' // Extract keywords (simple frequency-based) const wordFreq = {} const commonWords = new Set(['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'this', 'that', 'these', 'those']) words.forEach(word => { const cleanWord = word.toLowerCase().replace(/[^\w]/g, '') if (cleanWord.length > 2 && !commonWords.has(cleanWord)) { wordFreq[cleanWord] = (wordFreq[cleanWord] || 0) + 1 } }) const keywords = Object.entries(wordFreq) .sort(([,a], [,b]) => b - a) .slice(0, 10) .map(([word]) => word) return { wordCount: words.length, charCount: text.length, sentenceCount: sentences.length, paragraphCount: Math.max(paragraphs.length, 1), readingTime: Math.ceil(words.length / 200), // Average reading speed sentiment, keywords } } async function startVoiceRecognition() { if (isListening) return isListening = true try { const speech = await mic({ filePath: tmpPath("voice-command.webm"), timeSlice: 3000 }) // Simple voice command recognition (in a real app, you'd use speech-to-text API) toast("Voice command recorded! Processing...") // Simulate voice command processing await wait(1000) const commands = ["next", "previous", "analyze", "help", "restart"] const randomCommand = commands[Math.floor(Math.random() * commands.length)] await handleVoiceCommand(randomCommand) } catch (error) { console.log("Voice recognition error:", error) } finally { isListening = false } } async function handleVoiceCommand(command: string) { switch (command.toLowerCase()) { case "next": if (currentStep < steps.length - 1) { currentStep++ await showStep() } else { toast("You're at the last step!") } break case "previous": if (currentStep > 0) { currentStep-- await showStep() } else { toast("You're at the first step!") } break case "analyze": if (tutorialText.trim()) { const analysis = analyzeText(tutorialText) toast(`Analysis complete! ${analysis.wordCount} words, ${analysis.sentiment} sentiment`) } else { toast("Please enter some text first!") } break case "help": await div(md(` # Voice Commands Help - **"next"** - Go to next step - **"previous"** - Go to previous step - **"analyze"** - Analyze current text - **"help"** - Show this help - **"restart"** - Restart tutorial Say any command clearly and wait for processing. `)) break case "restart": currentStep = 0 tutorialText = "" await showStep() break default: toast(`Unknown command: ${command}`) } } async function showStep() { const step = steps[currentStep] let content = step.content if (step.action) { const result = await step.action() if (result) { content += "\n\n" + result } } const result = await div({ html: md(content), footer: `Step ${currentStep + 1} of ${steps.length}`, shortcuts: [ { name: "Previous", key: "ArrowLeft", onPress: () => { if (currentStep > 0) { currentStep-- submit("previous") } }, bar: "left", visible: currentStep > 0 }, { name: "Next", key: "ArrowRight", onPress: () => { if (currentStep < steps.length - 1) { currentStep++ submit("next") } }, bar: "right", visible: currentStep < steps.length - 1 }, { name: "š¤ Voice Command", key: "Space", onPress: () => submit("voice"), bar: "right", visible: true }, { name: "Analyze Text", key: `${cmd}+Enter`, onPress: () => submit("analyze"), bar: "right", visible: tutorialText.trim().length > 0 } ] }) if (result === "next" && currentStep < steps.length - 1) { currentStep++ await showStep() } else if (result === "previous" && currentStep > 0) { currentStep-- await showStep() } else if (result === "voice") { await startVoiceRecognition() await showStep() } else if (result === "analyze") { await handleVoiceCommand("analyze") await showStep() } } // Start the tutorial await showStep()// Name: Image Filter and Effects Processor // Description: Apply filters and effects to images with batch processing and GUI selection // Author: edehu135 import "@johnlindquist/kit" import sharp from 'sharp' interface FilterOption { name: string value: string description: string preview?: string } interface ProcessingOptions { blur?: number sharpen?: number brightness?: number contrast?: number saturation?: number hue?: number gamma?: number grayscale?: boolean sepia?: boolean negate?: boolean normalize?: boolean resize?: { width?: number; height?: number } format?: 'jpeg' | 'png' | 'webp' | 'tiff' quality?: number } const imageFormats = ['.jpg', '.jpeg', '.png', '.webp', '.tiff', '.bmp'] const filterPresets: FilterOption[] = [ { name: "Vintage", value: "vintage", description: "Sepia tone with reduced contrast" }, { name: "Black & White", value: "bw", description: "Classic grayscale conversion" }, { name: "High Contrast", value: "contrast", description: "Enhanced contrast and sharpness" }, { name: "Soft Blur", value: "blur", description: "Gentle blur effect" }, { name: "Vibrant", value: "vibrant", description: "Increased saturation and brightness" }, { name: "Dramatic", value: "dramatic", description: "High contrast with adjusted gamma" }, { name: "Custom", value: "custom", description: "Configure your own settings" } ] const getPresetSettings = (preset: string): ProcessingOptions => { switch (preset) { case 'vintage': return { sepia: true, contrast: 0.8, brightness: 1.1, saturation: 0.7 } case 'bw': return { grayscale: true } case 'contrast': return { contrast: 1.3, sharpen: 2 } case 'blur': return { blur: 2 } case 'vibrant': return { saturation: 1.4, brightness: 1.1 } case 'dramatic': return { contrast: 1.5, gamma: 0.8, sharpen: 1 } default: return {} } } const getCustomSettings = async (): Promise<ProcessingOptions> => { const settings: ProcessingOptions = {} const options = await select("Select effects to apply (multi-select):", [ { name: "Blur", value: "blur", description: "Apply blur effect" }, { name: "Sharpen", value: "sharpen", description: "Sharpen the image" }, { name: "Brightness", value: "brightness", description: "Adjust brightness" }, { name: "Contrast", value: "contrast", description: "Adjust contrast" }, { name: "Saturation", value: "saturation", description: "Adjust color saturation" }, { name: "Hue", value: "hue", description: "Shift hue" }, { name: "Gamma", value: "gamma", description: "Adjust gamma" }, { name: "Grayscale", value: "grayscale", description: "Convert to grayscale" }, { name: "Sepia", value: "sepia", description: "Apply sepia tone" }, { name: "Negate", value: "negate", description: "Invert colors" }, { name: "Normalize", value: "normalize", description: "Normalize contrast" }, { name: "Resize", value: "resize", description: "Resize image" } ]) for (const option of options) { switch (option) { case 'blur': settings.blur = parseFloat(await arg("Blur amount (0.3-1000):", { placeholder: "2" })) || 2 break case 'sharpen': settings.sharpen = parseFloat(await arg("Sharpen amount (1-100):", { placeholder: "2" })) || 2 break case 'brightness': settings.brightness = parseFloat(await arg("Brightness multiplier (0.1-3):", { placeholder: "1.1" })) || 1.1 break case 'contrast': settings.contrast = parseFloat(await arg("Contrast multiplier (0.1-3):", { placeholder: "1.2" })) || 1.2 break case 'saturation': settings.saturation = parseFloat(await arg("Saturation multiplier (0-3):", { placeholder: "1.3" })) || 1.3 break case 'hue': settings.hue = parseFloat(await arg("Hue rotation (-360 to 360):", { placeholder: "30" })) || 30 break case 'gamma': settings.gamma = parseFloat(await arg("Gamma (0.1-3):", { placeholder: "0.8" })) || 0.8 break case 'grayscale': settings.grayscale = true break case 'sepia': settings.sepia = true break case 'negate': settings.negate = true break case 'normalize': settings.normalize = true break case 'resize': const width = await arg("New width (leave empty to maintain aspect ratio):", { placeholder: "800" }) const height = await arg("New height (leave empty to maintain aspect ratio):", { placeholder: "" }) settings.resize = { width: width ? parseInt(width) : undefined, height: height ? parseInt(height) : undefined } break } } return settings } const applyFilters = async (inputPath: string, outputPath: string, options: ProcessingOptions): Promise<void> => { let image = sharp(inputPath) // Apply resize first if specified if (options.resize) { image = image.resize(options.resize.width, options.resize.height, { fit: 'inside', withoutEnlargement: true }) } // Apply color adjustments if (options.brightness || options.contrast || options.saturation || options.hue) { image = image.modulate({ brightness: options.brightness, saturation: options.saturation, hue: options.hue }) } // Apply contrast separately if needed if (options.contrast && !options.brightness && !options.saturation && !options.hue) { image = image.linear(options.contrast, -(128 * options.contrast) + 128) } // Apply gamma if (options.gamma) { image = image.gamma(options.gamma) } // Apply blur if (options.blur) { image = image.blur(options.blur) } // Apply sharpen if (options.sharpen) { image = image.sharpen(options.sharpen) } // Apply grayscale if (options.grayscale) { image = image.grayscale() } // Apply normalize if (options.normalize) { image = image.normalize() } // Apply negate if (options.negate) { image = image.negate() } // Apply sepia (using tint) if (options.sepia) { image = image.tint({ r: 255, g: 240, b: 192 }) } // Set output format and quality const format = options.format || 'jpeg' const quality = options.quality || 90 switch (format) { case 'jpeg': image = image.jpeg({ quality }) break case 'png': image = image.png({ quality }) break case 'webp': image = image.webp({ quality }) break case 'tiff': image = image.tiff({ quality }) break } await image.toFile(outputPath) } const processImages = async (imagePaths: string[], settings: ProcessingOptions): Promise<void> => { const outputDir = await path({ hint: "Select output directory for processed images", onlyDirs: true }) const outputFormat = await arg("Output format:", ['jpeg', 'png', 'webp', 'tiff']) settings.format = outputFormat as any if (outputFormat === 'jpeg' || outputFormat === 'webp') { const quality = await arg("Quality (1-100):", { placeholder: "90" }) settings.quality = parseInt(quality) || 90 } let processed = 0 const total = imagePaths.length for (const imagePath of imagePaths) { try { const filename = path.basename(imagePath, path.extname(imagePath)) const outputPath = path.join(outputDir, `${filename}_filtered.${outputFormat}`) setStatus({ message: `Processing ${processed + 1}/${total}: ${path.basename(imagePath)}`, status: "busy" }) await applyFilters(imagePath, outputPath, settings) processed++ setProgress(processed / total) } catch (error) { console.error(`Error processing ${imagePath}:`, error) } } setStatus({ message: `Completed! Processed ${processed}/${total} images`, status: "success" }) await notify({ title: "Image Processing Complete", body: `Successfully processed ${processed} out of ${total} images` }) await revealFile(outputDir) } // Main script execution const selectedFiles = await getSelectedFile() let imagePaths: string[] = [] if (selectedFiles) { imagePaths = selectedFiles.split('\n').filter(file => imageFormats.some(ext => file.toLowerCase().endsWith(ext)) ) } else { const droppedFiles = await drop({ placeholder: "Drop image files here or select from file browser", hint: "Supports: JPG, PNG, WebP, TIFF, BMP" }) if (Array.isArray(droppedFiles)) { imagePaths = droppedFiles .map(file => file.path) .filter(file => imageFormats.some(ext => file.toLowerCase().endsWith(ext))) } } if (imagePaths.length === 0) { const files = await selectFile("Select image files to process") if (files) { imagePaths = [files].filter(file => imageFormats.some(ext => file.toLowerCase().endsWith(ext)) ) } } if (imagePaths.length === 0) { await div(md("# No Images Selected\n\nPlease select image files to process.")) exit() } await div(md(`# Image Filter Processor\n\nSelected ${imagePaths.length} image(s) for processing.`)) const selectedPreset = await arg("Choose a filter preset:", filterPresets) let processingSettings: ProcessingOptions if (selectedPreset === 'custom') { processingSettings = await getCustomSettings() } else { processingSettings = getPresetSettings(selectedPreset) } if (Object.keys(processingSettings).length === 0) { await div(md("# No Effects Selected\n\nNo processing will be applied.")) exit() } await processImages(imagePaths, processingSettings)