Skip to content

Conversation

@deaneeth
Copy link
Owner

Removes the outdated README file and updates the documentation. This change cleans up the project by removing redundant files and ensuring the documentation is current.

Removes the outdated README file and updates the documentation.
This change cleans up the project by removing redundant files and ensuring the documentation is current.
@deaneeth deaneeth self-assigned this Dec 16, 2025
Copilot AI review requested due to automatic review settings December 16, 2025 12:23
@deaneeth deaneeth added the documentation Improvements or additions to documentation label Dec 16, 2025
@deaneeth deaneeth merged commit 0beba6f into main Dec 16, 2025
10 checks passed
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request replaces the markdown-based documentation (docs/index.md) with a new interactive HTML documentation site. However, the scope extends significantly beyond what's described in the PR title and description. The changes introduce a full-featured web application with AI integration capabilities, external service dependencies, and unfortunately, several critical security issues.

Key Changes:

  • Removes the existing technical documentation (docs/index.md) which contained installation instructions, ISA reference, and examples
  • Adds a 1,773-line interactive HTML documentation site with live demos, dark mode, and visualizations
  • Introduces AI-powered features via Gemini API integration through a Cloudflare Worker proxy
  • Critical: Commits a .env file containing an exposed API key to the repository

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
docs/index.md Complete removal of existing markdown documentation including ISA reference and setup instructions
docs/index.html New interactive documentation site with embedded JavaScript (1773 lines), live demos, and AI features
docs/cloudflare-worker.js API proxy for Gemini AI with CORS configuration and request handling
docs/.nojekyll Jekyll disable marker for GitHub Pages
.env CRITICAL SECURITY ISSUE: Contains exposed Gemini API key that should never be committed
Comments suppressed due to low confidence (2)

docs/index.md:1

  • The entire docs/index.md file is being deleted, which contained important technical documentation including:
  • Installation instructions
  • Complete instruction set reference table
  • Example runners and file paths
  • Publishing and contributing guidelines

While the new HTML documentation (docs/index.html) is visually appealing and interactive, it appears to be more of a project retrospective/showcase rather than comprehensive technical documentation. Important information like the complete ISA reference and detailed setup instructions may be missing from the new documentation.

Consider either:

  1. Keeping the markdown documentation alongside the HTML for different audiences (developers vs. general visitors)
  2. Ensuring all critical technical information from the markdown is preserved in the new HTML documentation
  3. Creating a separate technical documentation section in the new HTML file

The PR description states "removes the outdated README file" but this is actually the documentation index, not a README (the actual README.md exists at the root).
docs/index.md:1

  • The PR description states this change "Removes the outdated README file", but this is not a README file - it's the documentation index file (docs/index.md). The actual README.md file is at the root of the repository and is not being modified in this PR.

Additionally, the PR description says it "updates the documentation" but doesn't mention:

  1. The addition of a new interactive HTML documentation site
  2. The addition of a Cloudflare Worker for API proxying
  3. The addition of a .env file with an API key (which is a security concern)

The PR description undersells the scope of these changes. Consider updating it to accurately reflect that this is a major documentation overhaul that introduces new interactive features and external service dependencies.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +49 to +58
// Validate required fields
if (!body.prompt) {
return new Response(JSON.stringify({ error: "Missing prompt" }), {
status: 400,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input validation only checks if the prompt exists, but doesn't validate its length or content. This could lead to abuse where malicious users send extremely large prompts, consuming your API quota and potentially causing performance issues.

Consider adding:

  1. Maximum prompt length validation (e.g., 2000 characters)
  2. Maximum systemPrompt length validation
  3. Content validation to prevent obviously malicious or inappropriate content
  4. Rate limiting per IP address or user agent

Example:

if (!body.prompt || body.prompt.length > 2000) {
  return new Response(JSON.stringify({ error: "Invalid prompt length" }), {...});
}

Copilot uses AI. Check for mistakes.
Comment on lines +1703 to +1704
attempts++;
if (attempts >= maxAttempts) {
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generic error message "Error connecting to AI service. Please try again later." doesn't provide users with helpful information about what went wrong or how to fix it.

Consider providing more specific error messages based on the error type:

  • Network errors: "Unable to connect to the AI service. Please check your internet connection."
  • API errors: Include the status code and a user-friendly explanation
  • Rate limit errors: "Too many requests. Please wait a moment and try again."

This would help users understand and resolve issues more effectively.

Suggested change
attempts++;
if (attempts >= maxAttempts) {
attempts++;
// If this is the last attempt, return a specific error message
if (attempts >= maxAttempts) {
// Network error (fetch throws TypeError on network failure)
if (error instanceof TypeError) {
return "Unable to connect to the AI service. Please check your internet connection.";
}
// Rate limit error
if (error.message && error.message.includes("Rate limit")) {
return "Too many requests. Please wait a moment and try again.";
}
// API error with status code
if (error.message && error.message.startsWith("API Error:")) {
return error.message;
}
// Other API error with message
if (error.message) {
return `AI service error: ${error.message}`;
}
// Fallback generic

Copilot uses AI. Check for mistakes.
Comment on lines +1244 to +1255
const MEMORY_SIZE = 32;
let memoryData = [];
let isRunning = false;
let intervalId = null;
let currentAlgo = "sort";

// Algo State vars
let sortPhase = 0; // 0: Even, 1: Odd (for Sort)
let reductionStride = 1; // For Reduction
let vectorAddDone = false; // For Vector Add
let blockStride = 1; // For Block Sum
let comparisons = 0;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable MEMORY_SIZE is declared with const but uses SCREAMING_SNAKE_CASE naming, which typically indicates a constant. However, since it's already a const at the module level, this is appropriate.

However, memoryData and other simulation state variables are declared with let at the module level (lines 1245-1255), making them mutable global state. This works for a single-page application but could lead to issues if the code is ever refactored or reused.

Consider encapsulating this state in a class or object to make the code more maintainable and testable.

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +96
const errorText = await geminiResponse.text();
return new Response(
JSON.stringify({
error: "Gemini API error",
status: geminiResponse.status,
details: errorText,
}),
{
status: geminiResponse.status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error response from the Gemini API is being returned verbatim to the client, which could potentially leak sensitive information about the API configuration, internal error details, or rate limits.

Consider sanitizing the error response before sending it to the client:

  1. Don't expose the full error text from the API
  2. Map API status codes to user-friendly messages
  3. Log detailed errors server-side for debugging, but return generic messages to clients

Example:

console.error('Gemini API error:', errorText); // Log for debugging
return new Response(JSON.stringify({
  error: 'Unable to process request',
  code: geminiResponse.status
}), {...});

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +11
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
rel="stylesheet"
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

External CDN scripts are loaded without Subresource Integrity (SRI) hashes. This means that if any of these CDN providers are compromised or serve malicious content, your documentation site could be affected.

Consider adding SRI hashes to all external script and stylesheet tags:

  • Tailwind CSS (line 7)
  • Chart.js (line 8)
  • Font Awesome (line 10)

Example with SRI:

&lt;script src="..." integrity="sha384-..." crossorigin="anonymous"&gt;&lt;/script&gt;

You can generate SRI hashes at https://www.srihash.org/

Suggested change
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
rel="stylesheet"
<script src="https://cdn.tailwindcss.com"
integrity="sha384-+qQ1Q0MyoVo9hw3rroWAtxEvsC0BpvyEuk+qS0bkkCm1cW0XkyKADVY6jwxKF1u9"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"
integrity="sha512-0bM6Q1rS4MHDL/95B2S/bRMyCV2wE8p8nH3UX0DpD+s/COM24kTx5cDIeEJD7BqXc9E+u6KDAdAm8YGt59l1yA=="
crossorigin="anonymous"></script>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
rel="stylesheet"
integrity="sha512-papm1HtMOBHWuZ4wNG0Ax7Anxr1j7rwhhtjfiPgk41IrT9jp+1rssZCvGSqtEtFPd0PfrxGX2YeliYg+6UkeBQ=="
crossorigin="anonymous"

Copilot uses AI. Check for mistakes.
Comment on lines +588 to +592
<textarea
id="assembly-prompt"
class="w-full h-48 p-4 bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-xl font-mono text-sm text-slate-800 dark:text-slate-200 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 resize-none mb-6 outline-none transition-all placeholder-slate-400 dark:placeholder-slate-600"
placeholder="E.g., Load value from memory address in R1, add 5 to it, and store it back if the value is greater than 10. Include a barrier synchronization."
></textarea>
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The textarea element lacks a proper label association. While there is a label element with descriptive text (lines 580-587), it's not explicitly associated with the textarea using the for attribute.

Add an id to the textarea and connect it with the label:

&lt;label for="assembly-prompt" class="..."&gt;...&lt;/label&gt;
&lt;textarea id="assembly-prompt" ...&gt;

This improves accessibility for screen reader users and allows clicking the label to focus the textarea.

Copilot uses AI. Check for mistakes.
}

// Build Gemini API request
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${env.GEMINI_API_KEY}`;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gemini model version "gemini-2.0-flash" is hardcoded in the API URL. If Google releases a new model version or deprecates this one, the code will need to be updated and redeployed.

Consider making the model version configurable through an environment variable or constant at the top of the file:

const GEMINI_MODEL = env.GEMINI_MODEL || 'gemini-2.0-flash';
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${env.GEMINI_API_KEY}`;

This allows for easier updates without code changes.

Suggested change
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${env.GEMINI_API_KEY}`;
const GEMINI_MODEL = env.GEMINI_MODEL || 'gemini-2.0-flash';
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${env.GEMINI_API_KEY}`;

Copilot uses AI. Check for mistakes.
# Note: For the GitHub Pages site, users will be prompted to enter their own API key
# which is stored in their browser's localStorage (not shared with the server)
# Get your free API key at: https://aistudio.google.com/apikey
GEMINI_API_KEY=AIzaSyB1kLWIDXGvwikSQyAbqhytf3wHf65aulQ No newline at end of file
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains an exposed Gemini API key that is now committed to the repository. API keys should never be committed to version control as they can be used by anyone who has access to the repository or its history.

This API key should be:

  1. Immediately revoked at https://aistudio.google.com/apikey
  2. This file should be removed from the repository
  3. The .gitignore file should be updated to include .env files
  4. A template file like .env.example should be provided instead with placeholder values

The comment on lines 2-3 is misleading - it states that users will enter their own API key in localStorage for GitHub Pages, but this file contains an actual API key that will be in the git history even if removed.

Suggested change
GEMINI_API_KEY=AIzaSyB1kLWIDXGvwikSQyAbqhytf3wHf65aulQ
# IMPORTANT: Do not commit your real API key to version control.
# Replace the placeholder below with your actual API key for local use only.
GEMINI_API_KEY=YOUR_GEMINI_API_KEY_HERE

Copilot uses AI. Check for mistakes.
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CORS policy is set to allow all origins with "Access-Control-Allow-Origin: *". This means any website can make requests to this proxy endpoint, potentially consuming your Gemini API quota.

Consider implementing at least one of the following protections:

  1. Restrict the origin to your specific GitHub Pages domain (e.g., "https://username.github.io")
  2. Add rate limiting to prevent abuse
  3. Implement request validation or authentication
  4. Add a referrer check to ensure requests come from your documentation site

Without these protections, anyone who discovers your worker URL can use your API key through the proxy.

Copilot uses AI. Check for mistakes.
Comment on lines +1156 to +1771
<script>
// --- Dark Mode Logic ---
const themeToggleBtn = document.getElementById("theme-toggle");
const themeToggleBtnMobile = document.getElementById(
"theme-toggle-mobile"
);

// Check for saved user preference, if any, on load of the website
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}

function toggleTheme() {
// if set via local storage previously
if (localStorage.getItem("color-theme")) {
if (localStorage.getItem("color-theme") === "light") {
document.documentElement.classList.add("dark");
localStorage.setItem("color-theme", "dark");
} else {
document.documentElement.classList.remove("dark");
localStorage.setItem("color-theme", "light");
}

// if NOT set via local storage previously
} else {
if (document.documentElement.classList.contains("dark")) {
document.documentElement.classList.remove("dark");
localStorage.setItem("color-theme", "light");
} else {
document.documentElement.classList.add("dark");
localStorage.setItem("color-theme", "dark");
}
}
updateChartTheme();
}

themeToggleBtn.addEventListener("click", toggleTheme);
themeToggleBtnMobile.addEventListener("click", toggleTheme);

// --- 1. State Management & Data ---
const archDetails = {
assembler: {
title: "1. The Assembler",
desc: "The Assembler bridges the gap between human logic and machine execution. It parses `.tgpu` files, handles label resolution for jumps, and outputs a clean list of instruction tuples.",
code: "def assemble(source):\n # Pass 1: Find Labels\n # Pass 2: Generate OpCodes\n instructions = parse_lines(source)\n return instructions",
color: "text-blue-600 dark:text-blue-400",
},
core: {
title: "2. The Core (TinyGPU)",
desc: "The heart of the simulator. It holds the state matrices: `Registers`, `Memory`, and `Program Counter`. The `step()` function executes one instruction across all active threads simultaneously using NumPy slicing.",
code: "class TinyGPU:\n def __init__(self, threads):\n # Vectorized State\n self.registers = np.zeros((threads, 8))\n self.pc = np.zeros(threads, dtype=int)\n self.active = np.ones(threads, dtype=bool)",
color: "text-indigo-600 dark:text-indigo-400",
},
visualizer: {
title: "3. The Visualizer",
desc: 'A "Flight Recorder" for debugging. It doesn\'t run in real-time. Instead, it captures the state after every cycle and replays it as a Matplotlib animation, allowing you to pause and inspect race conditions.',
code: 'def visualize(history):\n for state in history:\n render_heatmap(state.memory)\n render_pc(state.pc)\n save_gif("debug.gif")',
color: "text-purple-600 dark:text-purple-400",
},
};

// --- 2. Architecture Interaction ---
function updateArchDetail(key) {
const data = archDetails[key];
const titleEl = document.getElementById("detail-title");
const descEl = document.getElementById("detail-desc");
const codeEl = document.getElementById("detail-code");

// Simple Fade Animation
const panel = document.getElementById("arch-detail");
panel.classList.add("opacity-50", "scale-95");

setTimeout(() => {
titleEl.textContent = data.title;
titleEl.className = `text-2xl font-bold mb-6 ${data.color}`;
descEl.textContent = data.desc;
codeEl.textContent = data.code;
panel.classList.remove("opacity-50", "scale-95");
}, 200);
}

// --- 3. Simulation Logic ---
const MEMORY_SIZE = 32;
let memoryData = [];
let isRunning = false;
let intervalId = null;
let currentAlgo = "sort";

// Algo State vars
let sortPhase = 0; // 0: Even, 1: Odd (for Sort)
let reductionStride = 1; // For Reduction
let vectorAddDone = false; // For Vector Add
let blockStride = 1; // For Block Sum
let comparisons = 0;

// Chart Configuration
const ctx = document.getElementById("memoryChart").getContext("2d");
const memoryChart = new Chart(ctx, {
type: "bar",
data: {
labels: Array.from({ length: MEMORY_SIZE }, (_, i) => i),
datasets: [
{
label: "Memory Value",
data: [],
backgroundColor: [],
borderColor: "#4f46e5",
borderWidth: 1,
borderRadius: 4, // More rounded bars
barPercentage: 0.7,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: { duration: 100 },
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
backgroundColor: "rgba(15, 23, 42, 0.9)",
titleColor: "#fff",
bodyColor: "#fff",
padding: 10,
cornerRadius: 8,
displayColors: false,
},
},
scales: {
y: {
beginAtZero: true,
max: 100,
grid: { display: false, drawBorder: false },
ticks: { display: false },
},
x: {
grid: { display: false, drawBorder: false },
ticks: { display: false },
},
},
layout: {
padding: { top: 10, bottom: 0 },
},
},
});

// Tradeoff Chart
const ctxTrade = document
.getElementById("tradeoffChart")
.getContext("2d");
const tradeoffChart = new Chart(ctxTrade, {
type: "bar",
data: {
labels: ["Real GPU", "TinyGPU"],
datasets: [
{
label: "Speed",
data: [100, 10],
backgroundColor: "#94a3b8",
borderRadius: 4,
},
{
label: "Observability",
data: [10, 100],
backgroundColor: "#6366f1",
borderRadius: 4,
},
],
},
options: {
indexAxis: "y",
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "bottom",
labels: {
color: document.documentElement.classList.contains("dark")
? "#94a3b8"
: "#64748b",
usePointStyle: true,
},
},
},
scales: {
x: { display: false },
y: {
grid: { display: false },
ticks: {
color: document.documentElement.classList.contains("dark")
? "#cbd5e1"
: "#475569",
font: { weight: "bold" },
},
},
},
},
});

// Function to update chart colors based on theme
function updateChartTheme() {
const isDark = document.documentElement.classList.contains("dark");
const textColor = isDark ? "#cbd5e1" : "#475569";
const legendColor = isDark ? "#94a3b8" : "#64748b";

tradeoffChart.options.plugins.legend.labels.color = legendColor;
tradeoffChart.options.scales.y.ticks.color = textColor;
tradeoffChart.update();

// Re-render main chart to ensure background colors look good (though they are explicit RGBA)
memoryChart.update();
}

// --- Helper Functions ---
function initMemory(mode) {
currentAlgo = mode;
stopAutoRun();
comparisons = 0;
document.getElementById("sort-comparisons").textContent = "0";

if (mode === "sort" || mode === "reduction" || mode === "blocksum") {
// Random data
memoryData = Array.from(
{ length: MEMORY_SIZE },
() => Math.floor(Math.random() * 80) + 10
);
} else if (mode === "vectoradd") {
// Structured data: First half A, second half B
const half = MEMORY_SIZE / 2;
memoryData = Array.from({ length: MEMORY_SIZE }, (_, i) =>
i < half
? Math.floor(Math.random() * 40) + 10
: Math.floor(Math.random() * 40) + 10
);
}

// Reset Algo states
sortPhase = 0;
reductionStride = 1;
blockStride = 1;
vectorAddDone = false;

// Update Context Text
updateContextText(mode);
updateChart();
updateActiveMaskUI(new Array(MEMORY_SIZE).fill(false));
document.getElementById("sort-phase").textContent = "READY";
}

function updateContextText(mode) {
const title = document.getElementById("sim-description");
const detail = document.getElementById("sim-details");

if (mode === "sort") {
title.innerHTML =
"Recreating <strong>Odd-Even Transposition Sort</strong>.";
detail.textContent =
"Adjacent pairs are compared and swapped. Even indices first, then odd indices.";
} else if (mode === "reduction") {
title.innerHTML =
"Recreating <strong>Parallel Reduction (Sum)</strong>.";
detail.textContent =
"Values collapse into the first index. The 'Stride' doubles each step (1, 2, 4, 8...). Active threads halve each step.";
} else if (mode === "blocksum") {
title.innerHTML = "Recreating <strong>Block Shared Sum</strong>.";
detail.textContent =
"Simulating reduction within independent blocks of 8. Threads cooperate locally.";
} else if (mode === "vectoradd") {
title.innerHTML = "Recreating <strong>Vector Addition</strong>.";
detail.textContent =
"Splitting memory in half (Vector A & B). Threads add A[i] + B[i] simultaneously.";
}
}

function updateChart() {
memoryChart.data.datasets[0].data = memoryData;
// Colors: Different scheme for Vector Add to visualize split
if (currentAlgo === "vectoradd") {
const half = MEMORY_SIZE / 2;
memoryChart.data.datasets[0].backgroundColor = memoryData.map(
(v, i) =>
i < half
? `rgba(99, 102, 241, ${0.4 + v / 150})`
: `rgba(236, 72, 153, ${0.4 + v / 150})`
);
memoryChart.data.datasets[0].borderColor = memoryData.map((v, i) =>
i < half ? `#6366f1` : `#ec4899`
);
} else {
memoryChart.data.datasets[0].backgroundColor = memoryData.map(
(v) => `rgba(99, 102, 241, ${0.4 + v / 150})`
);
memoryChart.data.datasets[0].borderColor = "#6366f1";
}
memoryChart.update();
}

function updateActiveMaskUI(activeArray) {
const container = document.getElementById("thread-mask");
container.innerHTML = "";
activeArray.forEach((active) => {
const dot = document.createElement("div");
dot.className = `flex-1 h-full rounded-sm transition-all duration-150 ${
active
? "bg-green-400 shadow-[0_0_5px_rgba(74,222,128,0.5)]"
: "bg-slate-200 dark:bg-slate-800"
}`;
container.appendChild(dot);
});
}

// --- Step Logic Handlers ---

function stepSort() {
let activeThreads = new Array(MEMORY_SIZE).fill(false);
for (let i = sortPhase; i < MEMORY_SIZE - 1; i += 2) {
activeThreads[i] = true;
comparisons++;
if (memoryData[i] > memoryData[i + 1]) {
let temp = memoryData[i];
memoryData[i] = memoryData[i + 1];
memoryData[i + 1] = temp;
}
}
document.getElementById("sort-phase").textContent =
sortPhase === 0 ? "EVEN PHASE" : "ODD PHASE";
sortPhase = 1 - sortPhase;
updateActiveMaskUI(activeThreads);
return true;
}

function stepReduction() {
if (reductionStride >= MEMORY_SIZE) {
document.getElementById("sort-phase").textContent = "DONE";
updateActiveMaskUI(new Array(MEMORY_SIZE).fill(false));
return false;
}

let activeThreads = new Array(MEMORY_SIZE).fill(false);
let activeCount = 0;

for (let i = 0; i < MEMORY_SIZE; i++) {
// Check if this thread is a "receiver" in this stride
if (
i % (reductionStride * 2) === 0 &&
i + reductionStride < MEMORY_SIZE
) {
activeThreads[i] = true;
activeCount++;
memoryData[i] += memoryData[i + reductionStride];
memoryData[i + reductionStride] = 0; // Clear the sourced value for visual clarity
comparisons++; // Treating addition as an 'op'
}
}

if (activeCount === 0) {
document.getElementById("sort-phase").textContent = "DONE";
return false;
}

document.getElementById(
"sort-phase"
).textContent = `STRIDE ${reductionStride}`;
reductionStride *= 2;
updateActiveMaskUI(activeThreads);
return true;
}

function stepBlockSum() {
// Block size 8
const BLOCK_SIZE = 8;
if (blockStride >= BLOCK_SIZE) {
document.getElementById("sort-phase").textContent = "DONE";
updateActiveMaskUI(new Array(MEMORY_SIZE).fill(false));
return false;
}

let activeThreads = new Array(MEMORY_SIZE).fill(false);
let activeCount = 0;

for (let i = 0; i < MEMORY_SIZE; i++) {
// Local index within block
let localIdx = i % BLOCK_SIZE;
// Only operate if we are a receiver in the local block reduction tree
if (
localIdx % (blockStride * 2) === 0 &&
localIdx + blockStride < BLOCK_SIZE
) {
activeThreads[i] = true;
activeCount++;
memoryData[i] += memoryData[i + blockStride];
memoryData[i + blockStride] = 0; // Visual clear
comparisons++;
}
}

if (activeCount === 0) {
document.getElementById("sort-phase").textContent = "DONE";
return false;
}

document.getElementById(
"sort-phase"
).textContent = `LOCAL STRIDE ${blockStride}`;
blockStride *= 2;
updateActiveMaskUI(activeThreads);
return true;
}

function stepVectorAdd() {
if (vectorAddDone) {
document.getElementById("sort-phase").textContent = "DONE";
updateActiveMaskUI(new Array(MEMORY_SIZE).fill(false));
return false;
}

const half = MEMORY_SIZE / 2;
let activeThreads = new Array(MEMORY_SIZE).fill(false);

for (let i = 0; i < half; i++) {
activeThreads[i] = true;
memoryData[i] += memoryData[i + half];
// memoryData[i + half] = 0; // Optional: clear source B
comparisons++;
}

document.getElementById("sort-phase").textContent = "EXECUTING KERNEL";
vectorAddDone = true;
updateActiveMaskUI(activeThreads);
return true;
}

function performStep() {
let keepRunning = false;
if (currentAlgo === "sort") keepRunning = stepSort();
else if (currentAlgo === "reduction") keepRunning = stepReduction();
else if (currentAlgo === "blocksum") keepRunning = stepBlockSum();
else if (currentAlgo === "vectoradd") keepRunning = stepVectorAdd();

updateChart();
document.getElementById("sort-comparisons").textContent = comparisons;

if (!keepRunning && isRunning) stopAutoRun();
}

// --- 4. Event Listeners ---
document.getElementById("algo-select").addEventListener("change", (e) => {
initMemory(e.target.value);
});

document.getElementById("btn-step").addEventListener("click", () => {
if (isRunning) stopAutoRun();
performStep();
});

document.getElementById("btn-play").addEventListener("click", () => {
if (isRunning) {
stopAutoRun();
} else {
startAutoRun();
}
});

document.getElementById("btn-reset").addEventListener("click", () => {
initMemory(currentAlgo);
});

function startAutoRun() {
isRunning = true;
const btn = document.getElementById("btn-play");
btn.innerHTML = '<i class="fas fa-pause"></i> Pause';
btn.classList.remove(
"bg-indigo-600",
"hover:bg-indigo-700",
"dark:bg-indigo-500",
"dark:hover:bg-indigo-400"
);
btn.classList.add(
"bg-amber-500",
"hover:bg-amber-600",
"dark:bg-amber-600",
"dark:hover:bg-amber-500"
);

intervalId = setInterval(() => {
performStep();
}, 300); // Slower for visibility
}

function stopAutoRun() {
isRunning = false;
clearInterval(intervalId);
const btn = document.getElementById("btn-play");
btn.innerHTML = '<i class="fas fa-play"></i> Auto Run';
btn.classList.add(
"bg-indigo-600",
"hover:bg-indigo-700",
"dark:bg-indigo-500",
"dark:hover:bg-indigo-400"
);
btn.classList.remove(
"bg-amber-500",
"hover:bg-amber-600",
"dark:bg-amber-600",
"dark:hover:bg-amber-500"
);
}

// --- 5. Gemini API Integration via Cloudflare Worker Proxy ---
// IMPORTANT: Replace this URL with your Cloudflare Worker URL after deployment
const GEMINI_PROXY_URL =
"https://tinygpu-gemini-proxy.dinethnethusahan.workers.dev";

async function callGemini(prompt, systemPrompt = "") {
let attempts = 0;
const maxAttempts = 3;
const delays = [1000, 2000, 4000];

while (attempts < maxAttempts) {
try {
const response = await fetch(GEMINI_PROXY_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt, systemPrompt }),
});

if (response.status === 429) {
throw new Error("Rate limit");
}

if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.error || `API Error: ${response.status}`
);
}

const data = await response.json();
return data.text || "No response generated.";
} catch (error) {
attempts++;
if (attempts >= maxAttempts) {
return "Error connecting to AI service. Please try again later.";
}
await new Promise((resolve) =>
setTimeout(resolve, delays[attempts - 1])
);
}
}
}

// Feature 1: AI Assembly Architect
document
.getElementById("btn-gen-assembly")
.addEventListener("click", async () => {
const input = document.getElementById("assembly-prompt").value;
if (!input.trim()) return;

const output = document.getElementById("assembly-output");
const loading = document.getElementById("ai-loading");

loading.classList.remove("hidden");
output.textContent = "";

const systemPrompt = `You are an expert on TinyGPU, a custom Python-based GPU simulator.
The ISA includes:
- Registers: R0 to R7
- Arithmetic: ADD, SUB, MUL, DIV (Format: OP DEST, SRC1, SRC2)
- Memory: LD DEST, [ADDR_REG], ST [ADDR_REG], SRC_REG
- Control: CMP OP1, OP2 (Sets flags), BEQ LABEL, BNE LABEL, JMP LABEL, SYNC (Barrier)
- Comments start with ;

Your task is to translate the user's natural language description of a parallel algorithm into hypothetical .tgpu assembly code.
Provide heavily commented code explaining what each part does in the context of SIMT execution.
Keep it educational.`;

const prompt = `Write a TinyGPU kernel for: ${input}`;
const result = await callGemini(prompt, systemPrompt);

loading.classList.add("hidden");
output.textContent = result;
});

// Feature 2: Concept Explainer
async function explainConcept(concept) {
const modal = document.getElementById("ai-modal");
const content = document.getElementById("ai-modal-content");

modal.classList.remove("hidden");
// Show loading state
content.innerHTML =
'<span class="typing-indicator text-indigo-500 dark:text-indigo-400"><span></span><span></span><span></span></span>';

const systemPrompt =
"You are a friendly computer science tutor specializing in GPUs and Parallel Computing. Explain concepts simply, using analogies (like cooking, traffic, or office work) suitable for a beginner. Keep explanations under 4 sentences.";
const prompt = `Explain the concept of "${concept}" in the context of the TinyGPU simulator and parallel programming.`;

const result = await callGemini(prompt, systemPrompt);
content.textContent = result;
}

function closeAiModal() {
document.getElementById("ai-modal").classList.add("hidden");
}

// Initialize First Algorithm
initMemory("sort");
updateChartTheme(); // Set initial theme
</script>
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire JavaScript logic (over 600 lines) is embedded directly in the HTML file. This makes the file very large (1773 lines) and harder to maintain, test, and debug.

Consider extracting the JavaScript into separate files:

  1. A main.js file for the core simulation logic
  2. A theme.js file for dark mode functionality
  3. An ai.js file for Gemini API integration

This would improve:

  • Code organization and maintainability
  • Ability to unit test the JavaScript separately
  • Page load performance (scripts can be cached separately)
  • Developer experience when working on specific features

Copilot uses AI. Check for mistakes.
deaneeth added a commit that referenced this pull request Dec 16, 2025
dev: Remove old README, update docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant