Tom Kramer | Digital Projects

Coding Experience

A collection of projects, code samples, and technical explorations showcasing my programming journey.

Recent Projects from GitHub

Loading your GitHub projects...

Voice Assistant - AI-Powered Search Integration

One of my main projects is building a local voice assistant that combines speech recognition, natural language processing, and AI-powered search capabilities. The system integrates Google Custom Search API for web queries, Ollama for local AI-powered summarization, and voice processing libraries for hands-free interaction. The assistant can understand natural language commands, perform web searches, and provide intelligent summaries using a locally-running AI model (Gemma 270M). Key features include speech-to-text with Vosk, text-to-speech with pyttsx3, and intelligent query handling with confirmation prompts for ambiguous requests.

voice_assistant.py Python
import os
import requests
import pyttsx3
import datetime
from stt_engine import STTEngine

# Configuration
API_KEY = os.getenv("GOOGLE_API_KEY")
SEARCH_ENGINE_ID = os.getenv("GOOGLE_SEARCH_ENGINE_ID")
OLLAMA_URL = "http://localhost:11434/api/generate"
OLLAMA_MODEL = "gemma3:270m"
LOOKUP_TRIGGER = "look something up for me"

# Initialize Text-to-Speech engine
tts_engine = pyttsx3.init()

def speak(text):
    """Convert text to speech."""
    print(f"🔊 Speaking: {text}")
    tts_engine.say(text)
    tts_engine.runAndWait()

def summarize_with_ollama(results_text: str, query: str) -> str | None:
    """Summarize search results using local Ollama AI model."""
    prompt = f"""Summarize the following search results for: '{query}'

Search Results:
{results_text}

Provide a concise, natural-sounding summary in 2-3 sentences:"""

    try:
        print("🤖 Generating AI summary with Ollama...")
        payload = {
            "model": OLLAMA_MODEL,
            "prompt": prompt,
            "stream": False
        }
        resp = requests.post(OLLAMA_URL, json=payload, timeout=60)
        resp.raise_for_status()
        summary = resp.json().get("response", "").strip()
        return summary if summary else None
    except Exception as e:
        print(f"❌ Error summarizing: {e}")
        return None

def perform_search(query: str, use_voice: bool = True) -> None:
    """Perform Google Custom Search with AI summarization."""
    params = {
        "key": API_KEY,
        "cx": SEARCH_ENGINE_ID,
        "q": query,
        "num": 5
    }

    try:
        if use_voice:
            speak("Searching now, please wait.")

        resp = requests.get(
            "https://www.googleapis.com/customsearch/v1",
            params=params,
            timeout=10
        )
        resp.raise_for_status()
        items = resp.json().get("items", [])

        if items:
            # Build results text for AI summarization
            results_text = ""
            for i, item in enumerate(items, 1):
                title = item.get("title", "No title")
                snippet = item.get("snippet", "")
                results_text += f"{i}. {title}\n{snippet}\n\n"

            # Get AI-powered summary
            summary = summarize_with_ollama(results_text, query)

            if summary and use_voice:
                speak(f"Here's what I found: {summary}")
        else:
            if use_voice:
                speak("I couldn't find any results for that.")

    except Exception as e:
        print(f"❌ Error: {e}")
        if use_voice:
            speak("Sorry, something went wrong with the search.")

def voice_assistant_mode():
    """Run voice-controlled assistant with natural language processing."""
    model_path = r"vosk-models\vosk-model-small-en-us-0.15"
    stt_engine = STTEngine(model_path)

    try:
        print("🎤 Voice assistant activated. Listening...")
        speak("Voice assistant activated. I'm listening.")

        while True:
            recognized_text = stt_engine.listen()
            if not recognized_text:
                continue

            recognized_text = recognized_text.strip()

            # Exit commands
            if recognized_text in ("kill the script", "stop"):
                speak("Exiting now. Goodbye.")
                break

            # Time query
            elif "what is the time" in recognized_text or "time" in recognized_text:
                time = datetime.datetime.now().strftime("%H:%M")
                hour = int(time.split(":")[0])
                if hour < 12:
                    speak(f"It is {time} in the morning.")
                else:
                    speak(f"It is {time}.")

            # Direct lookup trigger
            elif recognized_text.lower().startswith(LOOKUP_TRIGGER):
                query = recognized_text[len(LOOKUP_TRIGGER):].strip()
                if query:
                    perform_search(query, use_voice=True)

            # General query - ask for confirmation
            else:
                speak("Should I look that up for you?")
                confirmation = stt_engine.listen()

                if confirmation and confirmation.strip().lower() in (
                    "yes", "yeah", "sure", "please", "go ahead"
                ):
                    perform_search(recognized_text, use_voice=True)
                else:
                    speak("Alright, not looking that up.")

    finally:
        stt_engine.cleanup()
        speak("Voice assistant deactivated.")

if __name__ == "__main__":
    voice_assistant_mode()

Web Development - Dynamic Content

I build responsive, interactive web pages using HTML, CSS, and JavaScript. This site is a good example of that approach, it uses modern layout tools like CSS Grid and Flexbox, and it loads content dynamically from external APIs. Below is a simple example that pulls my latest repositories directly from the GitHub API and renders them as project cards.

github-loader.js JavaScript
async function loadGitHubProjects(username) {
    try {
        const response = await fetch(
            `https://api.github.com/users/${username}/repos?sort=updated&per_page=6`
        );
        const projects = await response.json();

        const container = document.getElementById('githubProjects');
        container.innerHTML = '';

        projects.forEach(project => {
            const card = document.createElement('div');
            card.className = 'github-card';
            card.innerHTML = `
                <h3 class="github-repo-name">${project.name}</h3>
                <p class="github-repo-desc">${project.description || 'No description'}</p>
                <div class="github-repo-meta">
                    <span>⭐ ${project.stargazers_count}</span>
                    <span>🔀 ${project.forks_count}</span>
                </div>
                <a href="${project.html_url}" target="_blank" class="github-link">
                    View on GitHub →
                </a>
            `;
            container.appendChild(card);
        });
    } catch (error) {
        document.getElementById('githubProjects').innerHTML =
            '<div class="error-message">Failed to load projects</div>';
    }
}

// Load projects on page load
loadGitHubProjects('randumtom');

GitHub Contributions

Loading your GitHub contributions...