From d17ddd9371c98590889fbe4faf60a51cb8ff6ad6 Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Fri, 14 Nov 2025 18:02:15 +0700 Subject: [PATCH 01/21] Init generate playground pages --- build.js | 2 + src/playground.js | 431 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 src/playground.js diff --git a/build.js b/build.js index d7fe374..d4ca233 100644 --- a/build.js +++ b/build.js @@ -8,6 +8,7 @@ const { encode } = require('html-entities'); const { JSDOM } = require('jsdom'); const yaml = require('js-yaml'); const esbuild = require('esbuild'); +const { generatePlayground } = require('./src/playground') const site = "https://docs.puter.com"; @@ -689,6 +690,7 @@ generateDocumentation('./src'); generateRedirects(); generateSitemap(); generateLLMs(); +generatePlayground(); if (anyErrors) { process.exit(1); diff --git a/src/playground.js b/src/playground.js new file mode 100644 index 0000000..0f551cd --- /dev/null +++ b/src/playground.js @@ -0,0 +1,431 @@ +const fs = require('fs'); +const path = require('path'); + +const playgroundHtml = ` + + + + + + + Puter.js Playground + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Puter.js Playground

+ +
+
+
+
+

Code: +

+
+
+ +
+
+
+
+

+ Preview:

+ +
+
+
+
+ + + + +` + +const examples = [ + { + title: 'Chat with Claude', + slug: 'ai-chat-claude', + source: '/playground/examples/ai-chat-claude.html' + } +] + +const generatePlayground = () => { + examples.forEach(example => { + // Read source file from src/ directory + const sourcePath = path.join('src', example.source); + const sourceContent = fs.readFileSync(sourcePath, 'utf8'); + + // Copy playgroundHtml to avoid tainting the original + const htmlTemplate = playgroundHtml.slice(); + + // Replace {{CODE}} in the copied template with the source content + const finalHtml = htmlTemplate.replace('{{CODE}}', sourceContent); + + // Create output directory + const outputDir = path.join('dist', 'playground', example.slug); + fs.mkdirSync(outputDir, { recursive: true }); + + // Write the file + const outputPath = path.join(outputDir, 'index.html'); + fs.writeFileSync(outputPath, finalHtml, 'utf8'); + + console.log(`Generated: ${outputPath}`); + }); +} + +module.exports = { generatePlayground }; From 417af08be1d98fcfe9444a92c06ff5a7935f8bc7 Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Fri, 14 Nov 2025 18:06:45 +0700 Subject: [PATCH 02/21] Add more examples --- src/playground.js | 408 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 406 insertions(+), 2 deletions(-) diff --git a/src/playground.js b/src/playground.js index 0f551cd..d737aee 100644 --- a/src/playground.js +++ b/src/playground.js @@ -397,10 +397,414 @@ const playgroundHtml = ` ` const examples = [ + // Introduction { - title: 'Chat with Claude', + title: 'Chat with GPT-5 nano', + slug: 'intro-chatgpt', + source: '/playground/examples/intro-chatgpt.html' + }, + { + title: 'Image Analysis', + slug: 'intro-gpt-vision', + source: '/playground/examples/intro-gpt-vision.html' + }, + { + title: 'Cloud Storage', + slug: 'intro-fs-write', + source: '/playground/examples/intro-fs-write.html' + }, + { + title: 'Key-Value Store', + slug: 'intro-kv-set', + source: '/playground/examples/intro-kv-set.html' + }, + { + title: 'Publish a Website', + slug: 'intro-hosting', + source: '/playground/examples/intro-hosting.html' + }, + { + title: 'Authentication', + slug: 'intro-auth', + source: '/playground/examples/intro-auth.html' + }, + // AI + { + title: 'Chat with GPT-5 nano', + slug: 'ai-chatgpt', + source: '/playground/examples/ai-chatgpt.html' + }, + { + title: 'Image Analysis', + slug: 'ai-gpt-vision', + source: '/playground/examples/ai-gpt-vision.html' + }, + { + title: 'Stream the response', + slug: 'ai-chat-stream', + source: '/playground/examples/ai-chat-stream.html' + }, + { + title: 'Function Calling', + slug: 'ai-function-calling', + source: '/playground/examples/ai-function-calling.html' + }, + { + title: 'AI Resume Analyzer (File handling)', + slug: 'ai-resume-analyzer', + source: '/playground/examples/ai-resume-analyzer.html' + }, + { + title: 'Chat with OpenAI o3-mini', + slug: 'ai-chat-openai-o3-mini', + source: '/playground/examples/ai-chat-openai-o3-mini.html' + }, + { + title: 'Chat with Claude Sonnet', slug: 'ai-chat-claude', source: '/playground/examples/ai-chat-claude.html' + }, + { + title: 'Chat with DeepSeek', + slug: 'ai-chat-deepseek', + source: '/playground/examples/ai-chat-deepseek.html' + }, + { + title: 'Chat with Gemini', + slug: 'ai-chat-gemini', + source: '/playground/examples/ai-chat-gemini.html' + }, + { + title: 'Chat with xAI (Grok)', + slug: 'ai-xai', + source: '/playground/examples/ai-xai.html' + }, + { + title: 'Extract Text from Image', + slug: 'ai-img2txt', + source: '/playground/examples/ai-img2txt.html' + }, + { + title: 'Text to Image', + slug: 'ai-txt2img', + source: '/playground/examples/ai-txt2img.html' + }, + { + title: 'Text to Image with options', + slug: 'ai-txt2img-options', + source: '/playground/examples/ai-txt2img-options.html' + }, + { + title: 'Text to Image with image-to-image generation', + slug: 'ai-txt2img-image-to-image', + source: '/playground/examples/ai-txt2img-image-to-image.html' + }, + { + title: 'Text to Speech', + slug: 'ai-txt2speech', + source: '/playground/examples/ai-txt2speech.html' + }, + { + title: 'Text to Speech with options', + slug: 'ai-txt2speech-options', + source: '/playground/examples/ai-txt2speech-options.html' + }, + { + title: 'Text to Speech with engines', + slug: 'ai-txt2speech-engines', + source: '/playground/examples/ai-txt2speech-engines.html' + }, + { + title: 'Text to Speech with OpenAI', + slug: 'ai-txt2speech-openai', + source: '/playground/examples/ai-txt2speech-openai.html' + }, + { + title: 'Text to Video', + slug: 'ai-txt2vid', + source: '/playground/examples/ai-txt2vid.html' + }, + { + title: 'Text to Video with options', + slug: 'ai-txt2vid-options', + source: '/playground/examples/ai-txt2vid-options.html' + }, + // FileSystem + { + title: 'Write File', + slug: 'fs-write', + source: '/playground/examples/fs-write.html' + }, + { + title: 'Read File', + slug: 'fs-read', + source: '/playground/examples/fs-read.html' + }, + { + title: 'Make a Directory', + slug: 'fs-mkdir', + source: '/playground/examples/fs-mkdir.html' + }, + { + title: 'Delete', + slug: 'fs-delete', + source: '/playground/examples/fs-delete.html' + }, + { + title: 'Read Directory', + slug: 'fs-readdir', + source: '/playground/examples/fs-readdir.html' + }, + { + title: 'Rename', + slug: 'fs-rename', + source: '/playground/examples/fs-rename.html' + }, + { + title: 'Get File/Directory Info', + slug: 'fs-stat', + source: '/playground/examples/fs-stat.html' + }, + { + title: 'Copy File/Directory', + slug: 'fs-copy', + source: '/playground/examples/fs-copy.html' + }, + { + title: 'Move File/Directory', + slug: 'fs-move', + source: '/playground/examples/fs-move.html' + }, + { + title: 'Upload', + slug: 'fs-upload', + source: '/playground/examples/fs-upload.html' + }, + { + title: 'Write a file with deduplication', + slug: 'fs-write-dedupe', + source: '/playground/examples/fs-write-dedupe.html' + }, + { + title: 'Create a new file with input coming from a file input', + slug: 'fs-write-from-input', + source: '/playground/examples/fs-write-from-input.html' + }, + { + title: 'Create a file in a directory that does not exist', + slug: 'fs-write-create-missing-parents', + source: '/playground/examples/fs-write-create-missing-parents.html' + }, + { + title: 'Create a directory with deduplication', + slug: 'fs-mkdir-dedupe', + source: '/playground/examples/fs-mkdir-dedupe.html' + }, + { + title: 'Create a directory with missing parent directories', + slug: 'fs-mkdir-create-missing-parents', + source: '/playground/examples/fs-mkdir-create-missing-parents.html' + }, + { + title: 'Move a file with missing parent directories', + slug: 'fs-move-create-missing-parents', + source: '/playground/examples/fs-move-create-missing-parents.html' + }, + { + title: 'Delete a directory', + slug: 'fs-delete-directory', + source: '/playground/examples/fs-delete-directory.html' + }, + // Key-Value Store + { + title: 'Set', + slug: 'kv-set', + source: '/playground/examples/kv-set.html' + }, + { + title: 'Get', + slug: 'kv-get', + source: '/playground/examples/kv-get.html' + }, + { + title: 'Increment', + slug: 'kv-incr', + source: '/playground/examples/kv-incr.html' + }, + { + title: 'Increment (Object value)', + slug: 'kv-incr-nested', + source: '/playground/examples/kv-incr-nested.html' + }, + { + title: 'Decrement', + slug: 'kv-decr', + source: '/playground/examples/kv-decr.html' + }, + { + title: 'Decrement (Object value)', + slug: 'kv-decr-nested', + source: '/playground/examples/kv-decr-nested.html' + }, + { + title: 'Delete', + slug: 'kv-del', + source: '/playground/examples/kv-del.html' + }, + { + title: 'List', + slug: 'kv-list', + source: '/playground/examples/kv-list.html' + }, + { + title: 'Flush', + slug: 'kv-flush', + source: '/playground/examples/kv-flush.html' + }, + { + title: "What's your name?", + slug: 'kv-name', + source: '/playground/examples/kv-name.html' + }, + // Networking + { + title: 'Basic TCP Socket', + slug: 'net-basic', + source: '/playground/examples/net-basic.html' + }, + { + title: 'TLS Socket', + slug: 'net-tls', + source: '/playground/examples/net-tls.html' + }, + { + title: 'Fetch', + slug: 'net-fetch', + source: '/playground/examples/net-fetch.html' + }, + // Hosting + { + title: 'Create a simple website displaying "Hello world!"', + slug: 'hosting-create', + source: '/playground/examples/hosting-create.html' + }, + { + title: 'Create 3 random websites and then list them', + slug: 'hosting-list', + source: '/playground/examples/hosting-list.html' + }, + { + title: 'Create a random website then delete it', + slug: 'hosting-delete', + source: '/playground/examples/hosting-delete.html' + }, + { + title: 'Update a subdomain to point to a new directory', + slug: 'hosting-update', + source: '/playground/examples/hosting-update.html' + }, + { + title: 'Retrieve information about a subdomain', + slug: 'hosting-get', + source: '/playground/examples/hosting-get.html' + }, + // Authentication + { + title: 'Sign in', + slug: 'auth-sign-in', + source: '/playground/examples/auth-sign-in.html' + }, + { + title: 'Sign out', + slug: 'auth-sign-out', + source: '/playground/examples/auth-sign-out.html' + }, + { + title: 'Check sign in', + slug: 'auth-is-signed-in', + source: '/playground/examples/auth-is-signed-in.html' + }, + { + title: 'Get user', + slug: 'auth-get-user', + source: '/playground/examples/auth-get-user.html' + }, + { + title: "Get user's monthly usage", + slug: 'auth-get-monthly-usage', + source: '/playground/examples/auth-get-monthly-usage.html' + }, + // Apps + { + title: 'To-Do List', + slug: 'app-todo', + source: '/playground/examples/app-todo.html' + }, + { + title: 'AI Chat', + slug: 'app-ai-chat', + source: '/playground/examples/app-ai-chat.html' + }, + { + title: 'Camera Photo Describer', + slug: 'app-camera', + source: '/playground/examples/app-camera.html' + }, + { + title: 'Text Summarizer', + slug: 'app-summarizer', + source: '/playground/examples/app-summarizer.html' + }, + { + title: 'Create an app pointing to example.com', + slug: 'app-create', + source: '/playground/examples/app-create.html' + }, + { + title: 'Create 3 random apps and then list them', + slug: 'app-list', + source: '/playground/examples/app-list.html' + }, + { + title: 'Create a random app then delete it', + slug: 'app-delete', + source: '/playground/examples/app-delete.html' + }, + { + title: 'Create a random app then change its title', + slug: 'app-update', + source: '/playground/examples/app-update.html' + }, + { + title: 'Create a random app then get it', + slug: 'app-get', + source: '/playground/examples/app-get.html' + }, + // Workers + { + title: 'Create a worker', + slug: 'workers-create', + source: '/playground/examples/workers-create.html' + }, + { + title: 'List workers', + slug: 'workers-list', + source: '/playground/examples/workers-list.html' + }, + { + title: 'Get a worker', + slug: 'workers-get', + source: '/playground/examples/workers-get.html' + }, + { + title: 'Workers Management', + slug: 'workers-management', + source: '/playground/examples/workers-management.html' + }, + { + title: 'Authenticated Worker Requests', + slug: 'workers-exec', + source: '/playground/examples/workers-exec.html' } ] @@ -424,8 +828,8 @@ const generatePlayground = () => { const outputPath = path.join(outputDir, 'index.html'); fs.writeFileSync(outputPath, finalHtml, 'utf8'); - console.log(`Generated: ${outputPath}`); }); + console.log(`Generated ${examples.length} playground examples.`); } module.exports = { generatePlayground }; From e5cad004ec382285905661d6dafb7200fc2541f4 Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Fri, 14 Nov 2025 18:10:49 +0700 Subject: [PATCH 03/21] Init sidebar --- src/playground.js | 175 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 2 deletions(-) diff --git a/src/playground.js b/src/playground.js index d737aee..ec02562 100644 --- a/src/playground.js +++ b/src/playground.js @@ -1,6 +1,60 @@ const fs = require('fs'); const path = require('path'); +// Function to generate sidebar HTML +const generateSidebarHtml = (examples) => { + const categories = { + 'Introduction': [], + 'AI': [], + 'FileSystem': [], + 'Key-Value Store': [], + 'Networking': [], + 'Hosting': [], + 'Authentication': [], + 'Apps': [], + 'Workers': [] + }; + + // Group examples by category based on slug prefix + examples.forEach(example => { + if (example.slug.startsWith('intro-')) { + categories['Introduction'].push(example); + } else if (example.slug.startsWith('ai-')) { + categories['AI'].push(example); + } else if (example.slug.startsWith('fs-')) { + categories['FileSystem'].push(example); + } else if (example.slug.startsWith('kv-')) { + categories['Key-Value Store'].push(example); + } else if (example.slug.startsWith('net-')) { + categories['Networking'].push(example); + } else if (example.slug.startsWith('hosting-')) { + categories['Hosting'].push(example); + } else if (example.slug.startsWith('auth-')) { + categories['Authentication'].push(example); + } else if (example.slug.startsWith('app-')) { + categories['Apps'].push(example); + } else if (example.slug.startsWith('workers-')) { + categories['Workers'].push(example); + } + }); + + let sidebarHtml = ''; + return sidebarHtml; +}; + const playgroundHtml = ` @@ -204,6 +258,87 @@ const playgroundHtml = ` .resizer.dragging { background: #ccc; } + + /* Sidebar styles */ + .sidebar { + position: fixed; + left: -300px; + top: 50px; + width: 300px; + height: calc(100vh - 50px); + background: #f8f9fa; + border-right: 1px solid #e1e1e1; + overflow-y: auto; + transition: left 0.3s ease; + z-index: 1000; + } + + .sidebar.open { + left: 0; + } + + .sidebar-toggle { + position: fixed; + left: 10px; + top: 10px; + background: white; + border: none; + padding: 8px 12px; + cursor: pointer; + border-radius: 4px; + z-index: 1001; + font-size: 16px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + + .sidebar-toggle:hover { + background: #f0f0f0; + } + + .sidebar-content { + padding: 20px; + } + + .sidebar-category { + margin-bottom: 25px; + } + + .sidebar-category-title { + font-weight: bold; + font-size: 14px; + color: #333; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .sidebar-item { + display: block; + padding: 8px 12px; + color: #555; + text-decoration: none; + border-radius: 4px; + margin-bottom: 4px; + font-size: 14px; + transition: background 0.2s ease; + } + + .sidebar-item:hover { + background: #e9ecef; + color: #2563eb; + } + + .sidebar-item.active { + background: #2563eb; + color: white; + } + + @media (max-width: 768px) { + .sidebar { + width: 250px; + left: -250px; + } + } @@ -215,6 +350,14 @@ const playgroundHtml = ` crossorigin="anonymous" referrerpolicy="no-referrer"> + + + + + +

Puter.js Playground

- -
-
-

Code:

+
+ +
+
+

Code:

+
+
-
- -
- - -
- - -
-
-

Preview:

- + + +
+ + +
+
+

Preview:

+ +
+
-
+ diff --git a/src/playground/assets/css/style.css b/src/playground/assets/css/style.css index 5f5753f..4b36617 100644 --- a/src/playground/assets/css/style.css +++ b/src/playground/assets/css/style.css @@ -91,7 +91,7 @@ body { } #code-container { - flex: 1; + width: 50%; height: 100%; position: relative; min-width: 0; @@ -100,7 +100,7 @@ body { } #output-container { - flex: 1; + width: 50%; height: 100%; position: relative; min-width: 0; diff --git a/src/playground/assets/js/app.js b/src/playground/assets/js/app.js index 47b950a..f61ec98 100644 --- a/src/playground/assets/js/app.js +++ b/src/playground/assets/js/app.js @@ -113,19 +113,16 @@ resizer.addEventListener('mousedown', (e) => { document.addEventListener('mousemove', (e) => { if (!isResizing) return; - const mainContainer = codeContainer.parentElement; - const sidebarContainer = document.getElementById('sidebar-container'); - const sidebarWidth = sidebarContainer.offsetWidth; - const availableWidth = mainContainer.offsetWidth - sidebarWidth - 6; // minus resizer width + const parentWidth = codeContainer.parentElement.offsetWidth; const diffX = e.pageX - startX; - const newCodeWidth = startWidthCode + diffX; - const newOutputWidth = startWidthOutput - diffX; + const newCodeWidth = ((startWidthCode + diffX) / parentWidth * 100); + const newOutputWidth = ((startWidthOutput - diffX) / parentWidth * 100); - // Set minimum width to 200px - if (newCodeWidth >= 200 && newOutputWidth >= 200) { - codeContainer.style.flex = `0 0 \${newCodeWidth}px`; - outputContainer.style.flex = `0 0 \${newOutputWidth}px`; + // Set minimum width to 20% + if (newCodeWidth >= 20 && newOutputWidth >= 20) { + codeContainer.style.width = `${newCodeWidth}%`; + outputContainer.style.width = `${newOutputWidth}%`; editor.layout(); // Resize Monaco editor } }); From ed413d297e6de74e5e774e209cb62a822297c24d Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Mon, 17 Nov 2025 18:05:14 +0700 Subject: [PATCH 10/21] Fix active sidebar --- src/playground.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playground.js b/src/playground.js index 02dfabc..47a65bd 100644 --- a/src/playground.js +++ b/src/playground.js @@ -10,7 +10,7 @@ const generateSidebarHtml = (sections) => { sidebarHtml += ``; }); From db3d7ab27165d1ad2eb8d8d5e3188b9599fee7bf Mon Sep 17 00:00:00 2001 From: Reynaldi Chernando Date: Mon, 17 Nov 2025 18:16:46 +0700 Subject: [PATCH 11/21] Init first page as hello world --- src/examples.js | 5 - src/playground.js | 27 +++ src/playground/index.html | 478 -------------------------------------- 3 files changed, 27 insertions(+), 483 deletions(-) delete mode 100755 src/playground/index.html diff --git a/src/examples.js b/src/examples.js index a66c5b6..6239131 100644 --- a/src/examples.js +++ b/src/examples.js @@ -2,11 +2,6 @@ const examples = [ { title: 'Introduction', children: [ - { - title: 'Chat with GPT-5 nano', - slug: 'intro-chatgpt', - source: '/playground/examples/intro-chatgpt.html' - }, { title: 'Image Analysis', slug: 'intro-gpt-vision', diff --git a/src/playground.js b/src/playground.js index 47a65bd..b6e0b0a 100644 --- a/src/playground.js +++ b/src/playground.js @@ -6,6 +6,9 @@ const examples = require('./examples') const generateSidebarHtml = (sections) => { let sidebarHtml = '