From e4dd8786d17b57236e17c5a748793715822dc45e Mon Sep 17 00:00:00 2001 From: ChrisKyle Date: Wed, 26 Feb 2025 06:53:39 +0500 Subject: [PATCH 1/4] feat: resolve #15 --- js/config/config.js | 2 +- js/main/agent.js | 5 +++++ js/script.js | 5 +++++ js/ws/client.js | 9 +++++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/js/config/config.js b/js/config/config.js index 9e1afd8..bd4bddf 100644 --- a/js/config/config.js +++ b/js/config/config.js @@ -23,7 +23,7 @@ export const getConfig = () => ({ temperature: parseFloat(localStorage.getItem('temperature')) || 1.8, top_p: parseFloat(localStorage.getItem('top_p')) || 0.95, top_k: parseInt(localStorage.getItem('top_k')) || 65, - responseModalities: "audio", + responseModalities: "text", speechConfig: { voiceConfig: { prebuiltVoiceConfig: { diff --git a/js/main/agent.js b/js/main/agent.js index 16521ed..e87bbd8 100644 --- a/js/main/agent.js +++ b/js/main/agent.js @@ -83,6 +83,11 @@ export class GeminiAgent{ } setupEventListeners() { + // Handle incoming text from the model + this.client.on('text', (text) => { + this.emit('text', text); + }); + // Handle incoming audio data from the model this.client.on('audio', async (data) => { try { diff --git a/js/script.js b/js/script.js index d80de06..58285e0 100644 --- a/js/script.js +++ b/js/script.js @@ -45,6 +45,11 @@ geminiAgent.on('turn_complete', () => { chatManager.finalizeStreamingMessage(); }); +geminiAgent.on('text', (text) => { + console.log('text', text); + chatManager.updateStreamingMessage(text); +}); + geminiAgent.connect(); setupEventListeners(geminiAgent); \ No newline at end of file diff --git a/js/ws/client.js b/js/ws/client.js index 15f503c..14f2f8d 100644 --- a/js/ws/client.js +++ b/js/ws/client.js @@ -121,10 +121,15 @@ export class GeminiWebsocketClient extends EventEmitter { this.emit('turn_complete'); } if (serverContent.modelTurn) { - // console.debug(`${this.name} is sending content`); - // Split content into audio and non-audio parts + // Split content into text, audio, and non-audio parts let parts = serverContent.modelTurn.parts; + // Filter out text parts + const textParts = parts.filter((p) => p.text); + textParts.forEach((p) => { + this.emit('text', p.text); + }); + // Filter out audio parts from the model's content parts const audioParts = parts.filter((p) => p.inlineData && p.inlineData.mimeType.startsWith('audio/pcm')); From 8b0a4cc7000f4a6a90b6a923e9ee446233ada7d5 Mon Sep 17 00:00:00 2001 From: Jayreddin Date: Thu, 1 May 2025 15:26:25 +0000 Subject: [PATCH 2/4] feat: Implement mobile layout and touch controls - Added a header with disconnect, connect, and settings buttons. - Created a button banner for mic, camera, and screen share controls. - Updated styles for mobile responsiveness, including fixed positioning for header and button banner. - Enhanced button styles and interactions for touch devices. - Added touch event listeners for disconnect, connect, mic, camera, screen share, and send buttons. - Adjusted chat history layout and message styling for better mobile experience. - Introduced a new plan for future phases of development, focusing on mobile layout and optimizations. --- APIDOCS/puter_api_docs.md | 5503 +++++++++++++++++++++++++++++++++++++ css/styles.css | 195 +- index.html | 30 +- js/dom/events.js | 97 +- plan/planned.md | 29 + 5 files changed, 5782 insertions(+), 72 deletions(-) create mode 100644 APIDOCS/puter_api_docs.md create mode 100644 plan/planned.md diff --git a/APIDOCS/puter_api_docs.md b/APIDOCS/puter_api_docs.md new file mode 100644 index 0000000..5151354 --- /dev/null +++ b/APIDOCS/puter_api_docs.md @@ -0,0 +1,5503 @@ + +IMPORTANT: This file contains the concatenated documentation for puter.js, a JavaScript SDK for the Puter Web OS. Use this documentation to answer questions about puter.js, its features, usage, and APIs. +WAIT FOR MY QUESTIONS BEFORE PROVIDING ANY INFORMATION. DO NOT SAY ANYTHING UNTIL I START ASKING QUESTIONS. + + + + + + + + + + + + + +-------------------------------------------- +-------------------------------------------- + +The following document is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + +-------------------------------------------- +-------------------------------------------- + + + +Given a prompt returns the completion that best matches the prompt. + +## Syntax +```js +puter.ai.chat(prompt) +puter.ai.chat(prompt, options = {}) +puter.ai.chat(prompt, testMode = false, options = {}) +puter.ai.chat(prompt, imageURL, testMode = false, options = {}) +puter.ai.chat(prompt, [imageURLArray], testMode = false, options = {}) +puter.ai.chat([messages], testMode = false, options = {}) +``` + +## Parameters +#### `prompt` (String) +A string containing the prompt you want to complete. + +#### `options` (Object) (Optional) +An object containing the following properties: +- `model` (String) - The model you want to use for the completion. Defaults to `gpt-4o-mini`. The following models are available: + - `gpt-4o-mini` (default) + - `gpt-4o` + - `o1` + - `o1-mini` + - `o1-pro` + - `o3` + - `o3-mini` + - `o4-mini` + - `gpt-4.1` + - `gpt-4.1-mini` + - `gpt-4.1-nano` + - `gpt-4.5-preview` + - `claude-3-7-sonnet` + - `claude-3-5-sonnet` + - `deepseek-chat` + - `deepseek-reasoner` + - `gemini-2.0-flash` + - `gemini-1.5-flash` + - `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo` + - `meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo` + - `meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo` + - `mistral-large-latest` + - `pixtral-large-latest` + - `codestral-latest` + - `google/gemma-2-27b-it` + - `grok-beta` +- `stream` (Boolean) - A boolean indicating whether you want to stream the completion. Defaults to `false`. +- `tools` (Array) (Optional) - An array of function definitions that the AI can call. Each function definition should have: + - `type` (String) - Must be "function" + - `function` (Object): + - `name` (String) - The name of the function + - `description` (String) - A description of what the function does + - `parameters` (Object) - JSON Schema object describing the parameters + - `strict` (Boolean) - Whether to enforce strict parameter validation + +#### `testMode` (Boolean) (Optional) +A boolean indicating whether you want to use the test API. Defaults to `false`. This is useful for testing your code without using up API credits. + +#### `imageURL` (String) +A string containing the URL of an image you want to provide as context for the completion. Also known as "GPT Vision". + +#### `imageURLArray` (Array) +An array of strings containing the URLs of images you want to provide as context for the completion. + +#### `messages` (Array) +An array of objects containing the messages you want to complete. Each object must have a `role` and a `content` property. The `role` property must be one of `system`, `assistant`, `user`, or `function`. The `content` property must be a string containing the message. An example of a valid `messages` parameter is: + +```js +[ + { + role: 'system', + content: 'Hello, how are you?' + }, + { + role: 'user', + content: 'I am doing well, how are you?' + }, +] +``` + +Providing a messages array is especially useful for building chatbots where you want to provide context to the completion. + +## Return value + +When `stream` is set to `false` (default): +- Will resolve to a response object containing the completion message +- If a function call is made, the response will include `tool_calls` array containing: + - `id` (String) - Unique identifier for the function call + - `function` (Object): + - `name` (String) - Name of function to call + - `arguments` (String) - JSON string of function arguments + +When `stream` is set to `true`: +- Returns an async iterable object that you can use with a `for await...of` loop to receive the response in parts as they become available. + +In case of an error, the `Promise` will reject with an error message. + +## Vendors + +We use the following vendors to provide AI services: + +- `gpt-4o-mini` (default): OpenAI +- `gpt-4o`: OpenAI +- `o3-mini`: OpenAI +- `o1-mini`: OpenAI +- `claude-3-5-sonnet`: Anthropic +- `deepseek-chat`: High-Flyer (DeepSeek) +- `deepseek-reasoner`: High-Flyer (DeepSeek) +- `gemini-2.0-flash`: Google +- `gemini-1.5-flash`: Google +- `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo`: Together.ai +- `meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo`: Together.ai +- `meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo`: Together.ai +- `mistral-large-latest`: Mistral AI +- `pixtral-large-latest`: Mistral AI +- `codestral-latest`: Mistral AI +- `google/gemma-2-27b-it`: Groq +- `grok-beta`: xAI + + +## Examples + +Ask GPT-4o mini a question + +```html;ai-chatgpt + + + + + + +``` + +GPT-4 Vision + +```html;ai-gpt-vision + + + + + + + +``` + +Stream the response + +```html;ai-chat-stream + + + + + + +``` + +Function Calling + +```html;ai-function-calling + + + + Weather Function Calling Demo + + + + + +
+

Weather Function Calling Demo

+ + +
+
+ + + +``` + + + + +Given an image will return the text contained in the image. Also known as OCR (Optical Character Recognition), this API can be used to extract text from images of printed text, handwriting, or any other text-based content. + +## Syntax +```js +puter.ai.img2txt(image, testMode = false) +``` + +## Parameters +#### `image` (String|File|Blob) (required) +A string containing the URL, or path (on Puter) of the image you want to recognize, or a `File` or `Blob` object containing the image. + +#### `testMode` (Boolean) (Optional) +A boolean indicating whether you want to use the test API. Defaults to `false`. This is useful for testing your code without using up API credits. + +## Return value +A `Promise` that will resolve to a string containing the text contained in the image. + +In case of an error, the `Promise` will reject with an error message. + +## Examples + +Extract the text contained in an image + +```html;ai-img2txt + + + + + + +``` + + + + +Given a prompt, generate an image using AI. + +## Syntax + +```js +puter.ai.txt2img(prompt, testMode = false) +``` + +## Parameters +#### `prompt` (String) (required) +A string containing the prompt you want to generate an image from. + +#### `testMode` (Boolean) (Optional) +A boolean indicating whether you want to use the test API. Defaults to `false`. This is useful for testing your code without using up API credits. + +## Return value +A `Promise` that will resolve to an image data URL when the image has been generated. + +## Examples + +Generate an image of a cat using DALL·E 3 + +```html;ai-txt2img + + + + + + +``` + + + + +Converts text into speech using AI. Supports multiple languages and voices. + +## Syntax + +```js +puter.ai.txt2speech(text) +puter.ai.txt2speech(text, language = 'en-US') +puter.ai.txt2speech(text, language = 'en-US', testMode = false) +``` + +## Parameters +#### `text` (String) (required) +A string containing the text you want to convert to speech. The text must be less than 3000 characters long. + +#### `language` (String) (optional) +The language to use for speech synthesis. Defaults to `en-US`. The following languages are supported: + +- Arabic (`ar-AE`) +- Catalan (`ca-ES`) +- Chinese (Cantonese) (`yue-CN`) +- Chinese (Mandarin) (`cmn-CN`) +- Danish (`da-DK`) +- Dutch (Belgian) (`nl-BE`) +- Dutch (`nl-NL`) +- English (Australian) (`en-AU`) +- English (British) (`en-GB`) +- English (Indian) (`en-IN`) +- English (New Zealand) (`en-NZ`) +- English (South African) (`en-ZA`) +- English (US) (`en-US`) +- English (Welsh) (`en-GB-WLS`) +- Finnish (`fi-FI`) +- French (`fr-FR`) +- French (Belgian) (`fr-BE`) +- French (Canadian) (`fr-CA`) +- German (`de-DE`) +- German (Austrian) (`de-AT`) +- Hindi (`hi-IN`) +- Icelandic (`is-IS`) +- Italian (`it-IT`) +- Japanese (`ja-JP`) +- Korean (`ko-KR`) +- Norwegian (`nb-NO`) +- Polish (`pl-PL`) +- Portuguese (Brazilian) (`pt-BR`) +- Portuguese (European) (`pt-PT`) +- Romanian (`ro-RO`) +- Russian (`ru-RU`) +- Spanish (European) (`es-ES`) +- Spanish (Mexican) (`es-MX`) +- Spanish (US) (`es-US`) +- Swedish (`sv-SE`) +- Turkish (`tr-TR`) +- Welsh (`cy-GB`) + +#### `testMode` (Boolean) (Optional) +A boolean indicating whether you want to use the test API. Defaults to `false`. This is useful for testing your code without using up API credits. + +## Return value +A `Promise` that will resolve to an MP3 stream when the speech has been synthesized. + +## Examples + +Convert text to speech + +```html;ai-txt2speech + + + + + + + +``` + + + + +Creates a Puter app with the given name. The app will be created in the user's apps, and will be accessible to this app. The app will be created with no permissions, and will not be able to access any data until permissions are granted to it. + +## Syntax +```js +puter.apps.create(name, indexURL) +puter.apps.create(name, indexURL, title) +puter.apps.create(name, indexURL, title, description) +puter.apps.create(options) +``` + +## Parameters +#### `name` (required) +The name of the app to create. This name must be unique to the user's apps. If an app with this name already exists, the promise will be rejected. + +#### `indexURL` (required) +The URL of the app's index page. This URL must be accessible to the user. If this parameter is not provided, the app will be created with no index page. The index page is the page that will be displayed when the app is started. + +**IMPORTANT**: The URL *must* start with either `http://` or `https://`. Any other protocols (including `file://`, `ftp://`, etc.) are not allowed and will result in an error. For example: + +✅ `https://example.com/app/index.html`
+✅ `http://localhost:3000/index.html`
+❌ `file:///path/to/index.html`
+❌ `ftp://example.com/index.html`
+ +#### `title` (required) +The title of the app. If this parameter is not provided, the app will be created with `name` as its title. + +#### `description` (optional) +The description of the app aimed at the end user. If this parameter is not provided, the app will be created with no description. + +#### `options` (required) +An object containing the options for the app to create. The object can contain the following properties: +- `name` (required): The name of the app to create. This name must be unique to the user's apps. If an app with this name already exists, the promise will be rejected. +- `indexURL` (required): The URL of the app's index page. This URL must be accessible to the user. If this parameter is not provided, the app will be created with no index page. +- `title` (optional): The human-readable title of the app. If this parameter is not provided, the app will be created with `name` as its title. +- `description` (optional): The description of the app aimed at the end user. If this parameter is not provided, the app will be created with no description. +- `icon` (optional): The new icon of the app. +- `maximizeOnStart` (optional): Whether the app should be maximized when it is started. Defaults to `false`. +- `filetypeAssociations` (optional): An array of strings representing the filetypes that the app can open. Defaults to `[]`. File extentions and MIME types are supported; For example, `[".txt", ".md", "application/pdf"]` would allow the app to open `.txt`, `.md`, and PDF files. + +## Return value +A `Promise` that will resolve to the [`app`](/Objects/app/) that was created. + +## Examples + +Create an app pointing to https://example.com + +```html + + + + + + +``` + + + + +Deletes an app with the given name. + +## Syntax +```js +puter.apps.delete(name) +``` + +## Parameters +#### `name` (required) +The name of the app to delete. + +## Return value +A `Promise` that will resolve to the app that was deleted. + +## Examples + +Create a random app then delete it + +```html + + + + + + +``` + + + + +Returns an app with the given name. If the app does not exist, the promise will be rejected. + +## Syntax +```js +puter.apps.get(name) +puter.apps.get(name, options) +``` + +## Parameters +#### `name` (required) +The name of the app to get. + +### options (optional) + +An object containing the following properties: + +- `stats_period` (optional): A string representing the period for which to get the user and open count. Possible values are `today`, `yesterday`, `7d`, `30d`, `this_month`, `last_month`, `this_year`, `last_year`, `month_to_date`, `year_to_date`, `last_12_months`. Default is `all` (all time). + +- `icon_size` (optional): An integer representing the size of the icons to return. Possible values are `null`, `16`, `32`, `64`, `128`, `256`, and `512`. Default is `null` (the original size). + +## Return value +A `Promise` that will resolve to the [`app`](/Objects/app/) with the given name. + +## Examples + +Create a random app then get it + +```html + + + + + + +``` + + + + +Returns an array of all apps belonging to the user and that this app has access to. If the user has no apps, the array will be empty. + +## Syntax +```js +puter.apps.list() +puter.apps.list(options) +``` + +## Parameters + +### options (optional) + +An object containing the following properties: + +- `stats_period` (optional): A string representing the period for which to get the user and open count. Possible values are `today`, `yesterday`, `7d`, `30d`, `this_month`, `last_month`, `this_year`, `last_year`, `month_to_date`, `year_to_date`, `last_12_months`. Default is `all` (all time). + +- `icon_size` (optional): An integer representing the size of the icons to return. Possible values are `null`, `16`, `32`, `64`, `128`, `256`, and `512`. Default is `null` (the original size). + +## Return value +A `Promise` that will resolve to an array of all [`app`s](/Objects/app/) belonging to the user that this app has access to. + +## Examples + +Create 3 random apps and then list them +```html + + + + + + +``` + + + + +Updates attributes of the app with the given name. + +## Syntax +```js +puter.apps.update(name, attributes) +``` + +## Parameters +#### `name` (required) +The name of the app to update. + +#### `attributes` (required) +An object containing the attributes to update. The object can contain the following properties: +- `name` (optional): The new name of the app. This name must be unique to the user's apps. If an app with this name already exists, the promise will be rejected. +- `indexURL` (optional): The new URL of the app's index page. This URL must be accessible to the user. +- `title` (optional): The new title of the app. +- `description` (optional): The new description of the app aimed at the end user. +- `icon` (optional): The new icon of the app. +- `maximizeOnStart` (optional): Whether the app should be maximized when it is started. Defaults to `false`. +- `filetypeAssociations` (optional): An array of strings representing the filetypes that the app can open. Defaults to `[]`. File extentions and MIME types are supported; For example, `[".txt", ".md", "application/pdf"]` would allow the app to open `.txt`, `.md`, and PDF files. + +## Return value +A `Promise` that will resolve to the [`app`](/Objects/app/) that was updated. + +## Examples + +Create a random app then change its title + +```html + + + + + + +``` + + + + +Returns the user's basic information. + + +## Syntax + +```js +puter.auth.getUser(); +``` + +## Parameters + +None + +## Return value + +A promise that resolves to an object containing the user's basic information. The user's basic information is an object with the following properties: + +- `uuid` - The user's UUID. This is a unique identifier that can be used to identify the user. +- `username` - The user's username. +- `email_confirmed` - Whether the user has confirmed their email address. + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Checks whether the user is signed into the application. + +## Syntax + +```js +puter.auth.isSignedIn(); +``` + +## Parameters + +None + +## Return value + +Returns `true` if the user is signed in, `false` otherwise. + +## Example + +```html + + + + + + +``` + + + +Initiates the sign in process for the user. This will open a popup window with the appropriate authentication method. Puter automatically handles the authentication process and will resolve the promise when the user has signed in. + +It is important to note that all essential methods in Puter handle authentication automatically. This method is only necessary if you want to handle authentication manually, for example if you want to build your own custom authentication flow. + +## Syntax + +```js +puter.auth.signIn(); +``` + +## Parameters + +None + +## Return value + +A `Promise` that will resolve to `true` when the user has signed in. The promise will never reject. + +## Example + +```html;auth-sign-in + + + + + + + +``` + + + + +Signs the user out of the application. + + +## Syntx + +```js +puter.auth.signOut(); +``` + +## Parameters + +None + +## Return value + +None + +## Example + +```html;auth-sign-out + + + + + + +``` + + + + +A low-level function that allows you to call any driver on any interface. This function is useful when you want to call a driver that is not directly exposed by Puter.js's high-level API or for when you need more control over the driver call. + +## Syntax +```js +puter.drivers.call(interface, driver, method) +puter.drivers.call(interface, driver, method, args = {}) +``` + +## Parameters +#### `interface` (String) (Required) +The name of the interface you want to call. + +#### `driver` (String) (Required) +The name of the driver you want to call. + +#### `method` (String) (Required) +The name of the method you want to call on the driver. + +#### `args` (Array) (Optional) +An object containing the arguments you want to pass to the driver. + +## Return value + +A `Promise` that will resolve to the result of the driver call. The result can be of any type, depending on the driver you are calling. + +In case of an error, the `Promise` will reject with an error message. + + + + +Get geolocation information using the current IP address. + +## Syntax + +The `ipgeo` method has a default parameter called `ip`. +Both of these calls are equivalent: + +```js +// Using the default parameter +puter.call('ipgeo', '1.1.1.1'); + +// Using named parameters +puter.call('ipgeo', { ip: '1.1.1.1' }); +``` + +## Parameters + +#### `ip` (string) (optional) + +The IP address to fetch geolocation information for. If this is not specified, +the IP address of the caller will be used. This location can vary based on how +the Puter server is configured. For example, you may get geolocation information +for the caller, but you may instead get geolocation information for the server +hosting Puter. + +## Return Value + +The response matches what is provided by [ipgeolocation.com](https://ipgeolocation.io/ip-location-api.html#documentation-overview). + + + + +Search news articles from [newsdata.io](https://newsdata.io/). + +## Syntax + +The `newsdata` method has a default parameter called `q`, which is a search query. +Both of these calls are equivalent: + +```js +// Using the default parameter +puter.call('newsdata', 'pizza'); + +// Using named parameters +puter.call('newsdata', { q: 'pizza' }); +``` + +When using named parameters, you can specify other values such as `language`: + +```js +puter.call('newsdata', { + q: 'pizza', + language: 'en', +}); +``` + +## Parameters + +Parameters are the same as on the [newsdata.io /latest endpoint](https://newsdata.io/documentation/#latest-news). +There are two exceptions: +- `apiKey` cannot be specified; Puter will always use the server's API key. + (developer-provided API keys may be supported in the future) +- `size` cannot be specified; 10 articles will always be returned. (this may change in the future) + +## Return Value + +The response matches what is provided by the [newsdata.io Response Object](https://newsdata.io/documentation/#latest-news). + + + + +Copies a file or directory from one location to another. + +## Syntax + +```js +puter.fs.copy(source, destination) +puter.fs.copy(source, destination, options) +``` + +## Parameters +#### `source` (String) (Required) +The path to the file or directory to copy. + +#### `destination` (String) (Required) +The path to the destination directory. If destination is a directory then the file or directory will be copied into that directory using the same name as the source file or directory. If the destination is a file, we overwrite if overwrite is `true`, otherwise we error. + +#### `options` (Object) (Optional) +The options for the `copy` operation. The following options are supported: +- `overwrite` (Boolean) - Whether to overwrite the destination file or directory if it already exists. Defaults to `false`. +- `dedupeName` (Boolean) - Whether to deduplicate the file or directory name if it already exists. Defaults to `false`. +- `newName` (String) - The new name to use for the copied file or directory. Defaults to `undefined`. + + +## Return value +A `Promise` that will resolve to the copied file or directory. If the source file or directory does not exist, the promise will be rejected with an error. + +## Examples + + Copy a file + +```html;fs-copy + + + + + + +``` + + + + +Deletes a file or directory. + +## Syntax +```js +puter.fs.delete(path) +puter.fs.delete(path, options) +``` + +## Parameters +#### `path` (String) (required) +Path of the file or directory to delete. +If `path` is not absolute, it will be resolved relative to the app's root directory. + +#### `options` (Object) (optional) +The options for the `delete` operation. The following options are supported: +- `recursive` (Boolean) - Whether to delete the directory recursively. Defaults to `true`. +- `descendantsOnly` (Boolean) - Whether to delete only the descendants of the directory and not the directory itself. Defaults to `false`. + + +## Return value +A `Promise` that will resolve when the file or directory is deleted. + +## Examples + + +Delete a file + +```html;fs-delete + + + + + + +``` + +Delete a directory + +```html;fs-delete-directory + + + + + + +``` + + + + +Allows you to create a directory. + +## Syntax +```js +puter.fs.mkdir(path) +puter.fs.mkdir(path, options) +``` + +## Parameters +#### `path` (string) (required) +The path to the directory to create. +If path is not absolute, it will be resolved relative to the app's root directory. + +#### `options` (object) +The options for the `mkdir` operation. The following options are supported: +- `overwrite` (boolean) - Whether to overwrite the directory if it already exists. Defaults to `false`. +- `dedupeName` (boolean) - Whether to deduplicate the directory name if it already exists. Defaults to `false`. +- `createMissingParents` (boolean) - Whether to create missing parent directories. Defaults to `false`. + +## Return value +Returns a promise that resolves to the directory object of the created directory. + +## Examples + +Create a new directory + +```html;fs-mkdir + + + + + + +``` + +Demonstrate the use of `dedupeName` + +```html;fs-mkdir-dedupe + + + + + + +``` + +Demonstrate the use of `createMissingParents` + +```html;fs-mkdir-create-missing-parents + + + + + + +``` + + + + +Moves a file or a directory from one location to another. + +## Syntax + +```js +puter.fs.move(source, destination) +puter.fs.move(source, destination, options) +``` + +## Parameters +#### `source` (String) (Required) +The path to the file or directory to move. + +#### `destination` (String) (Required) +The path to the destination directory. If destination is a directory then the file or directory will be moved into that directory using the same name as the source file or directory. If the destination is a file, we overwrite if overwrite is `true`, otherwise we error. + +#### `options` (Object) (Optional) +The options for the `move` operation. The following options are supported: +- `overwrite` (Boolean) - Whether to overwrite the destination file or directory if it already exists. Defaults to `false`. +- `dedupeName` (Boolean) - Whether to deduplicate the file or directory name if it already exists. Defaults to `false`. +- `createMissingParents` (Boolean) - Whether to create missing parent directories. Defaults to `false`. + +## Return value +A `Promise` that will resolve to the moved file or directory. If the source file or directory does not exist, the promise will be rejected with an error. + +## Examples + + Move a file + +```html;fs-move + + + + + + +``` + + Demonstrate the `createMissingParents` option + +```html;fs-move-create-missing-parents + + + + + + +``` + + + + +Reads data from a file. + +## Syntax +```js +puter.fs.read(path) +``` + +## Parameters +#### `path` (String) (required) +Path of the file to read. +If `path` is not absolute, it will be resolved relative to the app's root directory. + +## Return value +A `Promise` that will resolve to a `Blob` object containing the contents of the file. + +## Examples + +Read a file + +```html;fs-read + + + + + + +``` + + + + +Reads the contents of a directory, returning an array of items (files and directories) within it. This method is useful for listing all items in a specified directory in the Puter cloud storage. + +## Syntax +```js +puter.fs.readdir(path) +``` + +## Parameters +`path` (string) +The path to the directory to read. +If `path` is not absolute, it will be resolved relative to the app's root directory. + +## Return value +A `Promise` that resolves to an array of [`fsitem`s](/Objects/fsitem/) (files and directories) within the specified directory. + +## Examples + +Read a directory + +```html;fs-readdir + + + + + + +``` + + + + +Renames a file or directory to a new name. This method allows you to change the name of a file or directory in the Puter cloud storage. + +## Syntax +```js +puter.fs.rename(path, newName) +``` + +## Parameters +#### `path` (string) +The path to the file or directory to rename. +If `path` is not absolute, it will be resolved relative to the app's root directory. + +#### `newName` (string) +The new name of the file or directory. + +## Return value +Returns a promise that resolves to the file or directory object of the renamed file or directory. + +## Examples + +Rename a file + +```html;fs-rename + + + + + + +``` + + + + +Returns the storage space capacity and usage for the current user. + +
+ +This method requires permission to access the user's storage space. If the user has not granted permission, the method will return an error.
+ +## Syntax +```js +puter.fs.space() +``` + +## Parameters +None. + +## Return value +A `Promise` that will resolve to an object with the following properties: +- `capacity` (Number): The total amount of storage capacity available to the user, in bytes. +- `used` (Number): The amount of storage space used by the user, in bytes. + + +## Examples + +```html + + + + + + +``` + + + +This method allows you to get information about a file or directory. + +## Syntax + +```js +puter.fs.stat(path) +``` + +## Parameters +#### `path` (string) (required) +The path to the file or directory to get information about. +If `path` is not absolute, it will be resolved relative to the app's root directory. + +## Return value +A `Promise` that resolves to the [`fsitem`](/Objects/fsitem/) of the specified file or directory. + +## Examples + +Get information about a file + +```html;fs-stat + + + + + + +``` + + + + +Given a number of local items, upload them to the Puter filesystem. + +## Syntax + +```js +puter.fs.upload(items) +puter.fs.upload(items, dirPath) +puter.fs.upload(items, dirPath, options) +``` + +## Parameters +#### `items` (Array) (required) +The items to upload to the Puter filesystem. `items` can be an `InputFileList`, `FileList`, `Array` of `File` objects, or an `Array` of `Blob` objects. + +#### `dirPath` (String) (optional) +The path of the directory to upload the items to. If not set, the items will be uploaded to the app's root directory. + +#### `options` (Object) (optional) +A set of key/value pairs that configure the upload process. + + +## Return value +Returns a promise that resolves to an array of file objects of the uploaded files. + +## Examples + +Upload a file from a file input + +```html;fs-upload + + + + + + + +``` + + + + +Writes data to a specified file path. This method is useful for creating new files or modifying existing ones in the Puter cloud storage. + +## Syntax + +```js +puter.fs.write(path) +puter.fs.write(path, data) +puter.fs.write(path, data, options) +``` + +## Parameters +#### `path` (string) (required) +The path to the file to write to. +If path is not absolute, it will be resolved relative to the app's root directory. + +#### `data` (string|File|Blob) +The data to write to the file. + +#### `options` (object) +The options for the `write` operation. The following options are supported: +- `overwrite` (boolean) - Whether to overwrite the file if it already exists. Defaults to `true`. +- `dedupeName` (boolean) - Whether to deduplicate the file name if it already exists. Defaults to `false`. +- `createMissingParents` (boolean) - Whether to create missing parent directories. Defaults to `false`. + +## Return value +Returns a promise that resolves to the file object of the written file. + +## Examples + +Create a new file containing "Hello, world!" + +```html;fs-write + + + + + + +``` + +Create a new file with input coming from a file input + +```html;fs-write-from-input + + + + + + + +``` + +Demonstrate the use of `dedupeName` + +```html;fs-write-dedupe + + + + + + +``` + +Demonstrate the use of `createMissingParents` + +```html;fs-write-create-missing-parents + + + + + + +``` + + + + +Will create a new subdomain that will be served by the hosting service. Optionally, you can specify a path to a directory that will be served by the subdomain. + +## Syntax + +```js +puter.hosting.create(subdomain, dirPath) +``` + +## Parameters +#### `subdomain` (String) (required) +A string containing the name of the subdomain you want to create. + +#### `dirPath` (String) (optional) +A string containing the path to the directory you want to serve. If not specified, the subdomain will be created without a directory. + +## Return value +A `Promise` that will resolve to a [`subdomain`](/Objects/subdomain/) object when the subdomain has been created. If a subdomain with the given name already exists, the promise will be rejected with an error. If the path does not exist, the promise will be rejected with an error. + +## Examples + +Create a simple website displaying "Hello world!" + +```html;hosting-create + + + + + + +``` + + + + +Deletes a subdomain from your account. The subdomain will no longer be served by the hosting service. If the subdomain has a directory, it will be disconnected from the subdomain. The associated directory will not be deleted. + +## Syntax + +```js +puter.hosting.delete(subdomain) +``` + +## Parameters +#### `subdomain` (String) (required) +A string containing the name of the subdomain you want to delete. + +## Return value +A `Promise` that will resolve to `true` when the subdomain has been deleted. If a subdomain with the given name does not exist, the promise will be rejected with an error. + +## Examples + +Create a random website then delete it + +```html;hosting-delete + + + + + + +``` + + + + +Returns a subdomain. If the subdomain does not exist, the promise will be rejected with an error. + +## Syntax + +```js +puter.hosting.get(subdomain) +``` + +## Parameters +#### `subdomain` (String) (required) +A string containing the name of the subdomain you want to retrieve. + +## Return value +A `Promise` that will resolve to a [`subdomain`](/Objects/subdomain/) object when the subdomain has been retrieved. If a subdomain with the given name does not exist, the promise will be rejected with an error. + +## Examples + +Get a subdomain + +```html;hosting-get + + + + + + +``` + + + + +Returns an array of all subdomains in the user's subdomains that this app has access to. If the user has no subdomains, the array will be empty. + +## Syntax +```js +puter.hosting.list() +``` + +## Parameters +None + +## Return value +A `Promise` that will resolve to an array of all [`subdomain`s](/Objects/subdomain/) belonging to the user that this app has access to. + +## Examples + +Create 3 random websites and then list them + +```html;hosting-list + + + + + + +``` + + + + +Updates a subdomain to point to a new directory. If directory is not specified, the subdomain will be disconnected from its directory. + +## Syntax + +```js +puter.hosting.update(subdomain, dirPath) +``` + +## Parameters +#### `subdomain` (String) (required) +A string containing the name of the subdomain you want to update. + +#### `dirPath` (String) (optional) +A string containing the path to the directory you want to serve. If not specified, the subdomain will be disconnected from its directory. + +## Return value +A `Promise` that will resolve to a [`subdomain`](/Objects/subdomain/) object when the subdomain has been updated. If a subdomain with the given name does not exist, the promise will be rejected with an error. If the path does not exist, the promise will be rejected with an error. + +## Examples + +Update a subdomain to point to a new directory + +```html;hosting-update + + + + + + +``` + + + + +Decrements the value of a key. If the key does not exist, it is initialized with 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. + + +## Syntax + +```js +puter.kv.decr(key) +puter.kv.decr(key, amount) +``` + +## Parameters + +#### `key` (string) (required) + +The key of the value to decrement. + +#### `amount` (integer) (optional) + +The amount to decrement the value by. Defaults to 1. + +## Return Value + +Returns the new value of the key after the decrement operation. + +## Examples + +Decrement the value of a key + +```html;kv-decr + + + + + + +``` + + + + +When passed a key, will remove that key from the key-value storage. If there is no key with the given name in the key-value storage, nothing will happen. + +## Syntax +```js +puter.kv.del(key) +``` + +## Parameters +#### `key` (String) (required) +A string containing the name of the key you want to remove. + +## Return value +A `Promise` that will resolve to `true` when the key has been removed. + +## Examples + +Delete the key 'name' + +```html;kv-del + + + + + + +``` + + + + +Will remove all key-value pairs from the user's key-value store for the current app. + +## Syntax +```js +puter.kv.flush() +``` + +## Parameters +None + +## Return value +A `Promise` that will resolve to `true` when the key-value store has been flushed (emptied). The promise will never reject. + +## Examples + +```html;kv-flush + + + + + +``` + + + + +When passed a key, will return that key's value, or `null` if the key does not exist. + +## Syntax +```js +puter.kv.get(key) +``` + +## Parameters +#### `key` (String) (required) +A string containing the name of the key you want to retrieve the value of. + +## Return value +A `Promise` that will resolve to a string containing the value of the key. If the key does not exist, it will resolve to `null`. + +## Examples + +Retrieve the value of key 'name' + +```html;kv-get + + + + + + +``` + + + + +Increments the value of a key. If the key does not exist, it is initialized with 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers. + +## Syntax + +```js +puter.kv.incr(key) +puter.kv.incr(key, amount) +``` + +## Parameters + +#### `key` (string) (required) + +The key of the value to increment. + +#### `amount` (integer) (optional) + +The amount to increment the value by. Defaults to 1. + + +## Return Value + +Returns the new value of the key after the increment operation. + +## Examples + +Increment the value of a key + +```html;kv-incr + + + + + + +``` + + + + +Returns an array of all keys in the user's key-value store for the current app. If the user has no keys, the array will be empty. + +## Syntax +```js +puter.kv.list() +puter.kv.list(pattern) +puter.kv.list(returnValues = false) +puter.kv.list(pattern, returnValues = false) +``` + +## Parameters +#### `pattern` (String) (optional) +If set, only keys that match the given pattern will be returned. The pattern can contain the `*` wildcard character, which matches any number of characters. For example, `abc*` will match all keys that start with `abc`, such as `abc`, `abc123`, `abc123xyz`, etc. Default is `*`, which matches all keys. + +#### `returnValues` (Boolean) (optional) +If set to `true`, the returned array will contain objects with both `key` and `value` properties. If set to `false`, the returned array will contain only the keys. Default is `false`. + +## Return value +A `Promise` that will resolve to an array of all keys (and values, if `returnValues` is set to `true`) the user's key-value store for the current app. If the user has no keys, the array will be empty. + +## Examples + +Retrieve all keys in the user's key-value store for the current app + +```html;kv-list + + + + + +``` + + + + +When passed a key and a value, will add it to the user's key-value store, or update that key's value if it already exists. + +
Each app gets its own sandboxed key-value store in each user's account. Apps cannot access each other's key-value stores.
+ +## Syntax +```js +puter.kv.set(key, value) +``` + +## Parameters + +#### `key` (String) (required) +A string containing the name of the key you want to create/update. The maximum allowed `key` size is **1 KB**. + +#### `value` (String | Number | Boolean) +A string containing the value you want to give the key you are creating/updating. The maximum allowed `value` size is **400 KB**. + +## Return value +A `Promise` that will resolves to `true` when the key-value pair has been created or the existing key's value has been updated. + +## Examples + +Create a new key-value pair + +```html;kv-set + + + + + + +``` + + + + +Provides an interface for interaction with another app. + +## Attributes + +#### `usesSDK` (Boolean) +Whether the target app is using Puter.js. If not, then some features of `AppConnection` will not be available. + +## Methods + +#### `on(eventName, handler)` +Listen to an event from the target app. Possible events are: + +- `message` - The target app sent us a message with `postMessage()`. The handler receives the message. +- `close` - The target app has closed. The handler receives an object with an `appInstanceID` field of the closed app. + +#### `off(eventName, handler)` +Remove an event listener added with `on(eventName, handler)`. + +#### `postMessage(message)` +Send a message to the target app. Think of it as a more limited version of [`window.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). `message` can be anything that [`window.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) would accept for its `message` parameter. + +If the target app is not using the SDK, or the connection is not open, then nothing will happen. + +#### `close()` +Attempt to close the target app. If you do not have permission to close it, or the target app is already closed, then nothing will happen. + +An app has permission to close apps that it has launched with [`puter.ui.launchApp()`](/UI/launchApp). + +## Examples + +### Interacting with another app + +This example demonstrates two apps, `parent` and `child`, communicating with each other over using `AppConnection`. + +In order: +1. `parent` launches `child` +2. `parent` sends a message, `"Hello!"`, to `child` +3. `child` shows that message in an alert dialog. +4. `child` sends a message back. +5. `parent` receives the message and logs it. +6. `parent` closes the child app. + +```html + + + Parent app + + + + + + + + + + + + Child app + + + + + + +``` + +### Single app with multiple windows + +Multi-window applications can also be implemented with a single app, by launching copies of itself that check if they have a parent and wait for instructions from it. + +In this example, a parent app (with the name `traffic-light`) launches three children that display the different colors of a traffic light. + +```html + + + Traffic light + + + + +``` + + + + + + +## Attributes + +#### `uid` (String) + +A string containing the unique identifier of the app. This is a unique identifier generated by Puter when the app is created. + +#### `name` (String) + +A string containing the name of the app. + +#### `icon` (String) + +A string containing the Data URL of the icon of the app. This is a base64 encoded image. + +#### `description` (String) + +A string containing the description of the app. + + +#### `title` (String) + +A string containing the title of the app. + +#### `maximize_on_start` (Boolean) (default: `false`) + +A boolean value indicating whether the app should be maximized when it is started. + +#### `index_url` (String) + +A string containing the URL of the index file of the app. This is the file that will be loaded when the app is started. + +#### `created_at` (String) + +A string containing the date and time when the app was created. The format of the date and time is `YYYY-MM-DDTHH:MM:SSZ`. + +#### `background` (Boolean) (default: `false`) + +A boolean value indicating whether the app should run in the background. If this is set to `true`. + +#### `filetype_associations` (Array) + +An array of strings containing the file types that the app can open. Each string should be in the format `"."` or `"mime/type"`. e.g. `[".txt", "image/png"]`. For a directory association, the string should be `.directory`. + +#### `open_count` (Number) + +A number containing the number of times the app has been opened. If the `stats_period` option is set to a value other than `all`, this will be the number of times the app has been opened in that period. + +#### `user_count` (Number) + +A number containing the number of users that have access to the app. If the `stats_period` option is set to a value other than `all`, this will be the number of users that have access to the app in that period. + + + + +An fsitem object represents a file or a directory in the file system of a Puter. + +## Attributes + +#### `id` (String) + +A string containing the unique identifier of the item. This is a unique identifier generated by Puter when the item is created. + +#### `uid` (String) + +This is an alias for `id`. + +#### `name` (String) + +A string containing the name of the item. + +#### `path` (String) + +A string containing the path of the item. This is the path of the item relative to the root directory of the file system. + +#### `is_dir` (Boolean) + +A boolean value indicating whether the item is a directory. If this is set to `true`, the item is a directory. If this is set to `false`, the item is a file. + +#### `parent_id` (String) + +A string containing the unique identifier of the parent directory of the item. + +#### `parent_uid` (String) + +This is an alias for `parent_id`. + +#### `created` (Integer) + +An integer containing the Unix timestamp of the date and time when the item was created. + +#### `modified` (Integer) + +An integer containing the Unix timestamp of the date and time when the item was last modified. + +#### `accessed` (Integer) + +An integer containing the Unix timestamp of the date and time when the item was last accessed. + + +#### `size` (Integer) + +An integer containing the size of the item in bytes. If the item is a directory, this will be `null`. + + +#### `writable` (Boolean) + +A boolean value indicating whether the item is writable. If this is set to `true`, the item is writable. If this is set to `false`, the item is not writable. If the item is a directory and `writable` is `false`, it means new items cannot be added to the directory; +however, it is possible that subdirectories may be writable or contain writable files. + + + + +## Attributes + +#### `uid` (String) + +A string containing the unique identifier of the subdomain. + +#### `subdomain` (String) + +A string containing the name of the subdomain. This is the part of the domain that comes before the main domain name. +e.g. in `example.puter.site`, `example` is the subdomain. + +#### `root_dir` (FSItem) + +An FSItem object representing the root directory of the subdomain. This is the directory where the files of the subdomain are stored. + + + +Grants a permission from the current actor (usually a user) to the specified app. +This "granted permission" is simply a link between the current actor and specified +app through which a permission may be obtained when the app is running on behalf of the current actor. +If the current actor does not have this permission or loses this permission at some point, +this link will have no effect (that does **not** mean this action has no effect). + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.grantApp(app_uid, permissionString); +``` + +## Parameters + +#### `app_uid` (string) (required) + +The UID of the app to grant permission to. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + + +Grants a permission from the current actor (usually a user) to the specified app. +This "granted permission" is simply a link between the current actor and specified +app through which a permission may be obtained when the app is running on behalf of any user. +If the current actor does not have this permission or loses this permission at some point, +this link will have no effect (that does **not** mean this action has no effect). + +**Note**: This effectively grants the permission to any user, because users can access user-app tokens under their session. + +**This currently cannot be called from non-privileged apps** + +### Syntax + +```js +puter.perms.grantAppAnyUser(app_uid, permissionString); +``` + +### Parameters + +#### `app_uid` (string) (required) + +The UID of the app through which all users are granted this permission. + +#### `permissionString` (string) (required) + +### Return value + +Empty object (reserved for future use) + +### Example + +```html;auth-get-user + + + + + + +``` + + + + +## grantGroup + +Grants a permission from the current actor (usually a user) to the specified group. +This "granted permission" is simply a link between the current actor and specified +group through which a permission may be obtained. If the current actor does not have +this permission or loses this permission at some point, this link will have no +effect (that does **not** mean this action has no effect). + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.grantGroup(group_uid, permissionString); +``` + +## Parameters + +#### `group_uid` (string) (required) + +The UUID of the group to grant permission to. + +#### `permissionString` (string) (required) + +### Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Grants a permission from the current actor (usually a user) to the specified origin. +This "granted permission" is simply a link between the current actor and the app representing +the specified origin through which a permission may be obtained when the app is running on behalf of the current actor. +If the current actor does not have this permission or loses this permission at some point, +this link will have no effect (that does **not** mean this action has no effect). + +This origin will be translated to an app UID that represents a website using puter.js. + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.grantOrigin(origin, permissionString); +``` + +## Parameters + +#### `origin` (string) (required) + +The origin (e.g., "https://example.com") to grant permission to. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Grants a permission from the current actor (usually a user) to the specified user. +This "granted permission" is simply a link between the currect actor and specified +user through which a permission may be obtained. If the current actor does not have +this permission or loses this permission at some point, this link will have no +effect (that does **not** mean this action has no effect). + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.grantUser(username, permissionString); +``` + +## Parameters + +#### `username` (string) (required) + +The username of the user to grant permission to. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Revokes a permission from the current actor (usually a user) to the specified app +which has already been granted by this actor. If the specified app has another +pathway to this permission then this revoke will only remove the link to this +permission between the current actor and the specified app and the specified app +will still have access until the other pathway is also revoked. + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.revokeApp(app_uid, permissionString); +``` + +## Parameters + +#### `app_uid` (string) (required) + +The UID of the app to revoke permission from. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Revokes a permission from the current actor (usually a user) to the specified app +which has already been granted by this actor for use with any user. If the specified app has another +pathway to this permission then this revoke will only remove the link to this +permission between the current actor and the specified app and the specified app +will still have access until the other pathway is also revoked. + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.revokeAppAnyUser(app_uid, permissionString); +``` + +## Parameters + +#### `app_uid` (string) (required) + +The UID of the app to revoke permission from. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Revokes a permission from the current actor (usually a user) to the specified group +which has already been granted by this actor. If the specified group has another +pathway to this permission then this revoke will only remove the link to this +permission between the current actor and the specified group and the specified group +will still have access until the other pathway is also revoked. + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.revokeGroup(group_uid, permissionString); +``` + +## Parameters + +#### `group_uid` (string) (required) + +The UUID of the group to revoke permission from. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Revokes a permission from the current actor (usually a user) to the specified origin +which has already been granted by this actor. If the app representing the specified origin has another +pathway to this permission then this revoke will only remove the link to this +permission between the current actor and the app and the app +will still have access until the other pathway is also revoked. + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.revokeOrigin(origin, permissionString); +``` + +## Parameters + +#### `origin` (string) (required) + +The origin (e.g., "https://example.com") to revoke permission from. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + +Revokes a permission from the current actor (usually a user) to the specified user +which has already been granted by this actor. If the specified user has another +pathway to this permission then this revoke will only remove the link to this +permission between the current actor and the specified user and the specified user +will still have access until the other pathway is also revoked. + +**This currently cannot be called from non-privileged apps** + +## Syntax + +```js +puter.perms.revokeUser(username, permissionString); +``` + +## Parameters + +#### `username` (string) (required) + +The username of the user to revoke permission from. + +#### `permissionString` (string) (required) + +## Return value + +Empty object (reserved for future use) + +## Example + +```html;auth-get-user + + + + + + +``` + + + + + +Creates a new thread, either as a root thread or as a child of an existing thread. + +## Syntax + +```js +puter.threads.create(options); +``` + +## Parameters + +#### `options` (object) (required) + +An object containing the following properties: + +- `content` (any) (required): The content to store in the thread. +- `parent_uuid` (string) (optional): The UUID of the parent thread. If not provided, a root thread is created. + +## Return value + +An object containing information about the created thread: + +```js +{ + uuid: string, // The UUID of the created thread + content: any, // The content stored in the thread + parent_uuid: string // The UUID of the parent thread (if applicable) +} +``` + +## Permissions Required + +To create a child thread, the current actor needs the `thread:UUID-OF-PARENT:post` permission. Root threads can be created without special permissions. + +## Example + +```html + + + + + + +``` + + + + +Deletes a thread and optionally its child threads. + +## Syntax + +```js +puter.threads.delete(uuid, options); +``` + +## Parameters + +#### `uuid` (string) (required) + +The UUID of the thread to delete. + +#### `options` (object) (optional) + +An object containing the following properties: + +- `recursive` (boolean) (optional): If set to `true`, deletes all child threads recursively. Default is `false`. + +## Return value + +An empty object (reserved for future use). + +## Permissions Required + +The current actor needs the `thread:UUID-OF-THREAD:delete` permission to delete the thread, unless they are the owner. + +To delete child threads recursively, the actor needs either: +- `thread:UUID-OF-THREAD:children-of:delete` permission (to delete any child thread) +- `thread:UUID-OF-THREAD:own-children-of:delete` permission (to delete only child threads they own) + +## Example + +```html + + + + + + +``` + + + + +Updates the content of an existing thread. + +## Syntax + +```js +puter.threads.edit(uuid, content); +``` + +## Parameters + +#### `uuid` (string) (required) + +The UUID of the thread to edit. + +#### `content` (any) (required) + +The new content to store in the thread. + +## Return value + +An object containing information about the updated thread: + +```js +{ + uuid: string, // The UUID of the thread + content: any, // The updated content stored in the thread + parent_uuid: string // The UUID of the parent thread (if applicable) +} +``` + +## Permissions Required + +The current actor needs the `thread:UUID-OF-THREAD:edit` permission to edit the thread, unless they are the owner. + +## Example + +```html + + + + + + +``` + + + + +Retrieves information about a specific thread. + +## Syntax + +```js +puter.threads.get(uuid); +``` + +## Parameters + +#### `uuid` (string) (required) + +The UUID of the thread to retrieve. + +## Return value + +An object containing information about the requested thread: + +```js +{ + uuid: string, // The UUID of the thread + content: any, // The content stored in the thread + parent_uuid: string // The UUID of the parent thread (if applicable) +} +``` + +## Permissions Required + +The current actor needs the `thread:UUID-OF-THREAD:read` permission to read the contents of the thread, unless they are the owner. + +## Example + +```html + + + + + + +``` + + + + +Lists all child threads of a given parent thread. + +## Syntax + +```js +puter.threads.list(parent_uuid, options); +``` + +## Parameters + +#### `parent_uuid` (string) (required) + +The UUID of the parent thread whose children should be listed. + +#### `options` (object) (optional) + +An object containing the following properties: + +- `limit` (number) (optional): Maximum number of threads to return. Default is server-defined. +- `offset` (number) (optional): Number of threads to skip. Used for pagination. Default is 0. + +## Return value + +An array of objects, each containing information about a child thread: + +```js +[ + { + uuid: string, // The UUID of the child thread + content: any, // The content stored in the child thread + parent_uuid: string // The UUID of the parent thread + }, + // ...more thread objects +] +``` + +## Permissions Required + +The current actor needs the `thread:UUID-OF-PARENT:list` permission to list child threads, unless they are the owner. + +## Example + +```html + + + + + + +``` + + + + +Subscribes to events on a specific thread. + +## Syntax + +```js +puter.threads.subscribe(uuid, options, callback); +``` + +## Parameters + +#### `uuid` (string) (required) + +The UUID of the thread to subscribe to. + +#### `options` (object) (optional) + +An object containing subscription options: + +- `events` (array) (optional): Array of event names to subscribe to. Default is all events. + Possible events: + - `post`: Emitted when a child thread is added + - `edit`: Emitted when this thread is edited + - `delete`: Emitted when this thread is deleted + - `child-edit`: Emitted when a direct child thread is edited + - `child-delete`: Emitted when a direct child thread is deleted + +#### `callback` (function) (required) + +Function to be called when a subscribed event occurs. The callback receives an event object: + +```js +{ + type: string, // The type of event ('post', 'edit', 'delete', 'child-edit', 'child-delete') + thread: { // The thread that triggered the event + uuid: string, // The UUID of the thread + content: any, // The content of the thread + parent_uuid: string // The UUID of the parent thread (if applicable) + } +} +``` + +## Return value + +A subscription object with methods: + +```js +{ + unsubscribe: function // Call this function to stop receiving events +} +``` + +## Permissions Required + +The current actor needs the appropriate read permissions for the thread to subscribe to its events. + +## Example + +```html + + + + + + +``` + + + + +The `puter.threads` module provides functionality for creating, managing, and interacting with thread objects. Threads are identified by UUIDs and can be organized hierarchically with parent-child relationships. + +## Overview + +Threads provide a flexible way to create nested conversations or content structures. They can be used for: +- Comment systems +- Discussion forums +- Nested content organization +- Real-time collaborative features + +Each thread: +- Has a unique UUID +- Can have a parent thread (optional) +- Can have multiple child threads +- Supports subscription to various events +- Has permission-based access controls + +## Methods + +### puter.threads.create + +Creates a new thread, either as a root thread or as a child of an existing thread. + +#### Syntax + +```js +puter.threads.create(options); +``` + +#### Parameters + +##### `options` (object) (required) + +An object containing the following properties: + +- `content` (any) (required): The content to store in the thread. +- `parent_uuid` (string) (optional): The UUID of the parent thread. If not provided, a root thread is created. + +#### Return value + +An object containing information about the created thread: + +```js +{ + uuid: string, // The UUID of the created thread + content: any, // The content stored in the thread + parent_uuid: string // The UUID of the parent thread (if applicable) +} +``` + +#### Permissions Required + +To create a child thread, the current actor needs the `thread:UUID-OF-PARENT:post` permission. Root threads can be created without special permissions. + +#### Example + +```html + + + + + + +``` + +### puter.threads.get + +Retrieves information about a specific thread. + +#### Syntax + +```js +puter.threads.get(uuid); +``` + +#### Parameters + +##### `uuid` (string) (required) + +The UUID of the thread to retrieve. + +#### Return value + +An object containing information about the requested thread: + +```js +{ + uuid: string, // The UUID of the thread + content: any, // The content stored in the thread + parent_uuid: string // The UUID of the parent thread (if applicable) +} +``` + +#### Permissions Required + +The current actor needs the `thread:UUID-OF-THREAD:read` permission to read the contents of the thread, unless they are the owner. + +#### Example + +```html + + + + + + +``` + +### puter.threads.edit + +Updates the content of an existing thread. + +#### Syntax + +```js +puter.threads.edit(uuid, content); +``` + +#### Parameters + +##### `uuid` (string) (required) + +The UUID of the thread to edit. + +##### `content` (any) (required) + +The new content to store in the thread. + +#### Return value + +An object containing information about the updated thread: + +```js +{ + uuid: string, // The UUID of the thread + content: any, // The updated content stored in the thread + parent_uuid: string // The UUID of the parent thread (if applicable) +} +``` + +#### Permissions Required + +The current actor needs the `thread:UUID-OF-THREAD:edit` permission to edit the thread, unless they are the owner. + +#### Example + +```html + + + + + + +``` + +### puter.threads.delete + +Deletes a thread and optionally its child threads. + +#### Syntax + +```js +puter.threads.delete(uuid, options); +``` + +#### Parameters + +##### `uuid` (string) (required) + +The UUID of the thread to delete. + +##### `options` (object) (optional) + +An object containing the following properties: + +- `recursive` (boolean) (optional): If set to `true`, deletes all child threads recursively. Default is `false`. + +#### Return value + +An empty object (reserved for future use). + +#### Permissions Required + +The current actor needs the `thread:UUID-OF-THREAD:delete` permission to delete the thread, unless they are the owner. + +To delete child threads recursively, the actor needs either: +- `thread:UUID-OF-THREAD:children-of:delete` permission (to delete any child thread) +- `thread:UUID-OF-THREAD:own-children-of:delete` permission (to delete only child threads they own) + +#### Example + +```html + + + + + + +``` + +### puter.threads.list + +Lists all child threads of a given parent thread. + +#### Syntax + +```js +puter.threads.list(parent_uuid, options); +``` + +#### Parameters + +##### `parent_uuid` (string) (required) + +The UUID of the parent thread whose children should be listed. + +##### `options` (object) (optional) + +An object containing the following properties: + +- `limit` (number) (optional): Maximum number of threads to return. Default is server-defined. +- `offset` (number) (optional): Number of threads to skip. Used for pagination. Default is 0. + +#### Return value + +An array of objects, each containing information about a child thread: + +```js +[ + { + uuid: string, // The UUID of the child thread + content: any, // The content stored in the child thread + parent_uuid: string // The UUID of the parent thread + }, + // ...more thread objects +] +``` + +#### Permissions Required + +The current actor needs the `thread:UUID-OF-PARENT:list` permission to list child threads, unless they are the owner. + +#### Example + +```html + + + + + + +``` + +### puter.threads.subscribe + +Subscribes to events on a specific thread. + +#### Syntax + +```js +puter.threads.subscribe(uuid, options, callback); +``` + +#### Parameters + +##### `uuid` (string) (required) + +The UUID of the thread to subscribe to. + +##### `options` (object) (optional) + +An object containing subscription options: + +- `events` (array) (optional): Array of event names to subscribe to. Default is all events. + Possible events: + - `post`: Emitted when a child thread is added + - `edit`: Emitted when this thread is edited + - `delete`: Emitted when this thread is deleted + - `child-edit`: Emitted when a direct child thread is edited + - `child-delete`: Emitted when a direct child thread is deleted + +##### `callback` (function) (required) + +Function to be called when a subscribed event occurs. The callback receives an event object: + +```js +{ + type: string, // The type of event ('post', 'edit', 'delete', 'child-edit', 'child-delete') + thread: { // The thread that triggered the event + uuid: string, // The UUID of the thread + content: any, // The content of the thread + parent_uuid: string // The UUID of the parent thread (if applicable) + } +} +``` + +#### Return value + +A subscription object with methods: + +```js +{ + unsubscribe: function // Call this function to stop receiving events +} +``` + +#### Permissions Required + +The current actor needs the appropriate read permissions for the thread to subscribe to its events. + +#### Example + +```html + + + + + + +``` + +## Permissions + +Thread operations are controlled by the following permissions: + +| Permission | Description | +|------------|-------------| +| `thread:UUID:post` | Allows adding child threads to the specified thread | +| `thread:UUID:list` | Allows listing child threads of the specified thread | +| `thread:UUID:read` | Allows reading the contents of the specified thread | +| `thread:UUID:edit` | Allows editing the contents of the specified thread | +| `thread:UUID:delete` | Allows deleting the specified thread | +| `thread:UUID:children-of:delete` | Allows deleting any child thread of the specified thread | +| `thread:UUID:own-children-of:delete` | Allows deleting any child thread of the specified thread that is owned by the current actor | +| `thread:UUID:children-of:edit` | Allows editing any child thread of the specified thread | +| `thread:UUID:own-children-of:edit` | Allows editing any child thread of the specified thread that is owned by the current actor | + +**Note**: Thread owners automatically have all permissions for their own threads. + + + + +Displays an alert dialog by Puter. Puter improves upon the traditional browser alerts by providing more flexibility. For example, you can customize the buttons displayed. + +`puter.ui.alert()` will block the parent window until user responds by pressing a button. + +## Syntax +```js +puter.ui.alert(message) +puter.ui.alert(message, buttons) +``` + +## Parameters + +#### `message` (optional) +A string to be displayed in the alert dialog. If not set, the dialog will be empty. + +#### `buttons` (optional) +An array of objects that define the buttons to be displayed in the alert dialog. Each object must have a `label` property. The `value` property is optional. If it is not set, the `label` property will be used as the value. The `type` property is optional and can be set to `primary`, `success`, `info`, `warning`, or `danger`. If it is not set, the default type will be used. + + +## Return value +A `Promise` that resolves to the value of the button pressed. If the `value` property of button is set it is returned, otherwise `label` property will be returned. + +## Examples +```html + + + + + + +``` + + + +Presents a dialog to the user to authenticate with their Puter account. + +## Syntax + +```js +puter.ui.authenticateWithPuter() +``` + +## Parameters +None. + +## Return value +A `Promise` that will resolve to `true`. If the user cancels the dialog, the promise will be rejected with an error. + +## Examples + +```html + + + + + + +``` + + + +Creates and displays a window. + +## Syntax +```js +puter.ui.createWindow() +puter.ui.createWindow(options) +``` + +## Parameters + +#### `options` (optional) +A set of key/value pairs that configure the window. + +* `center` (Boolean): if set to `true`, window will be placed at the center of the screen. +* `content` (String): content of the window. +* `disable_parent_window` (Boolean): if set to `true`, the parent window will be blocked until current window is closed. +* `has_head` (Boolean): if set to `true`, window will have a head which contains the icon and close, minimize, and maximize buttons. +* `height` (Float): height of window in pixels. +* `is_resizable` (Boolean): if set to `true`, user will be able to resize the window. +* `show_in_taskbar` (Boolean): if set to `true`, window will be represented in the taskbar. +* `title` (String): title of the window. +* `width` (Float): width of window in pixels. + +## Examples +```html + + + + + + +``` + + + +Will terminate the running application and close its window. + +## Syntax +```js +puter.exit() +``` + +## Parameters +`puter.exit()` does not accept any parameters. + +## Examples + +```html + + + + + + + +``` + + + + +Allows you to dynamically launch another app from within your app. + +## Syntax +```js +puter.ui.launchApp() +puter.ui.launchApp(appName) +puter.ui.launchApp(appName, args) +puter.ui.launchApp(args) +``` + +## Parameters +#### `appName` (String) +Name of the app. If not provided, a new instance of the current app will be launched. + +#### `args` (Object) +Arguments to pass to the app. If `appName` is not provided, these arguments will be passed to the current app. + +## Return value +A `Promise` that will resolve to an [`AppConnection`](/Objects/AppConnection) once the app is launched. + +## Examples + +```html + + + + + + +``` + + + + +Listen to broadcast events from Puter. If the broadcast was received before attaching the handler, then the handler is called immediately with the most recent value. + + +## Syntax +```js +puter.ui.on(eventName, handler) +``` + +## Parameters + +#### `eventName` (String) +Name of the event to listen to. + +#### `handler` (Function) +Callback function run when the broadcast event is received. + +## Broadcasts +Possible broadcasts are: + +#### `localeChanged` +Sent on app startup, and whenever the user's locale on Puter is changed. The value passed to `handler` is: +```js +{ + language, // (String) Language identifier, such as 'en' or 'pt-BR' +} +``` + +#### `themeChanged` +Sent on app startup, and whenever the user's desktop theme on Puter is changed. The value passed to `handler` is: +```js +{ + palette: { + primaryHue, // (Float) Hue of the theme color + primarySaturation, // (String) Saturation of the theme color as a percentage, with % sign + primaryLightness, // (String) Lightness of the theme color as a percentage, with % sign + primaryAlpha, // (Float) Opacity of the theme color from 0 to 1 + primaryColor, // (String) CSS color value for text + } +} +``` + +## Examples + +```html + + + + + + +``` + + + + +Specify a function to execute when the one or more items have been opened. Items can be opened via a variety of methods such as: drag and dropping onto the app, double-clicking on an item, right-clicking on an item and choosing an app from the 'Open With...' submenu. + +**Note** `onItemsOpened` is not called when items are opened using `showOpenFilePicker()`. + +## Syntax +```js +puter.ui.onItemsOpened(handler) +``` + +## Parameters +#### `handler` (Function) +A function to execute after items are opened by user action. + +## Examples + +```html + + + + + + +``` + + + +Specify a callback function to execute if the app is launched with items. `onLaunchedWithItems` will be called if one or more items are opened via double-clicking on items, right-clicking on items and choosing the app from the 'Open With...' submenu. + +## Syntax +```js +puter.ui.onLaunchedWithItems(handler) +``` + +## Parameters +#### `handler` (Function) +A function to execute after items are opened by user action. The function will be passed an array of items. Each items is either a file or a directory. + +## Examples + +```html + + + + + + +``` + + + +Specify a function to execute when the window is about to close. For example the provided function will run right after the 'X' button of the window has been pressed. + +**Note** `onWindowClose` is not called when app is closed using `puter.exit()`. + +## Syntax +```js +puter.ui.onWindowClose(handler) +``` + +## Parameters +#### `handler` (Function) +A function to execute when the window is going to close. + + +## Examples +```html + + + + + + +``` + + + +Obtain a connection to the app that launched this app. + +## Syntax +```js +puter.ui.parentApp() +``` + +## Parameters +`puter.ui.parentApp()` does not accept any parameters. + +## Return value +An [`AppConnection`](/Objects/AppConnection) to the parent, or null if there is no parent app. + +## Examples + +```html + + + + + + +``` + + + + + +Creates a menubar in the UI. The menubar is a horizontal bar at the top of the window that contains menus. + +## Syntax + +```js +puter.ui.setMenubar(options) +``` + +## Parameters + +#### `options.items` (Array) + +An array of menu items. Each item can be a menu or a menu item. Each menu item can have a label, an action, and a submenu. + +#### `options.items.label` (String) + +The label of the menu item. + +#### `options.items.action` (Function) + +A function to execute when the menu item is clicked. + +#### `options.items.items` (Array) + +An array of submenu items. + +## Examples + +```html + + + + + + +``` + + + +Allows the user to dynamically set the height of the window. + +## Syntax +```js +puter.ui.setWindowHeight(height) +``` + +## Parameters + +#### `height` (Float) +The new height for this window. Must be a positive number. Minimum height is 200px, if a value less than 200 is provided, the height will be set to 200px. + +## Examples + +```html + + + + + + +``` + + + +Allows the user to set the position of the window. + +## Syntax +```js +puter.ui.setWindowPosition(x, y) +``` + +## Parameters + +#### `x` (Float) +The new x position for this window. Must be a positive number. + +#### `y` (Float) +The new y position for this window. Must be a positive number. + +## Examples + +```html + + + + + + +``` + + + +Allows the user to dynamically set the width and height of the window. + +## Syntax +```js +puter.ui.setWindowSize(width, height) +``` + +## Parameters + +#### `width` (Float) +The new width for this window. Must be a positive number. Minimum width is 200px, if a value less than 200 is provided, the width will be set to 200px. + +#### `height` (Float) +The new height for this window. Must be a positive number. Minimum height is 200px, if a value less than 200 is provided, the height will be set to 200px. + +## Examples + +```html + + + + + +``` + + + + +Allows the user to dynamically set the title of the window. + +## Syntax +```js +puter.ui.setWindowTitle(title) +``` + +## Parameters + +#### `title` (String) +The new title for this window. + +## Examples + +```html + + + + + + +``` + + + +Allows the user to dynamically set the width of the window. + +## Syntax +```js +puter.ui.setWindowWidth(width) +``` + +## Parameters + +#### `width` (Float) +The new width for this window. Must be a positive number. Minimum width is 200px, if a value less than 200 is provided, the width will be set to 200px. + +## Examples + +```html + + + + + + +``` + + + +Sets the X position of the window. + +## Syntax +```js +puter.ui.setWindowX(x) +``` + +## Parameters + +#### `x` (Float) (Required) +The new x position for this window. + + +## Examples + +```html + + + + + +``` + + + +Sets the y position of the window. + +## Syntax +```js +puter.ui.setWindowY(y) +``` + +## Parameters + +#### `y` (Float) (Required) +The new y position for this window. + +## Examples + +```html + + + + + +``` + + + +Presents the user with a color picker dialog allowing them to select a color. + +## Syntax +```js +puter.ui.showColorPicker() +puter.ui.showColorPicker(defaultColor) +puter.ui.showColorPicker(options) +``` + +## Examples + +```html + + + + + + +``` + + + +Presents the user with a directory picker dialog allowing them to pick a directory from their Puter cloud storage. + +## Syntax +```js +puter.ui.showDirectoryPicker() +puter.ui.showDirectoryPicker(options) +``` + +## Parameters + +#### `options` (optional) +A set of key/value pairs that configure the directory picker dialog. +* `multiple` (Boolean): if set to `true`, user will be able to select multiple directories. Default is `false`. + +## Return value +A `Promise` that resolves to either one FSItem or an array of FSItem objects, depending on how many directories were selected by the user. + +## Examples + +```html + + + + + + +

+
+ + + + +``` + + + +Presents the user with a list of fonts allowing them to preview and select a font. + +## Syntax +```js +puter.ui.showFontPicker() +puter.ui.showFontPicker(defaultFont) +puter.ui.showFontPicker(options) +``` + +## Parameters +#### `defaultFont` (String) +The default font to select when the font picker is opened. + + +## Examples + +```html + + + +

A cool Font Picker demo!

+ + + + +``` + + + +Presents the user with a file picker dialog allowing them to pick a file from their Puter cloud storage. + +## Syntax +```js +puter.ui.showOpenFilePicker() +puter.ui.showOpenFilePicker(options) +``` + +## Parameters + +#### `options` (optional) +A set of key/value pairs that configure the file picker dialog. +* `multiple` (Boolean): if set to `true`, user will be able to select multiple files. Default is `false`. +* `accept` (String): The list of MIME types or file extensions that are accepted by the file picker. Default is `*/*`. + - Example: `image/*` will allow the user to select any image file. + - Example: `['.jpg', '.png']` will allow the user to select files with `.jpg` or `.png` extensions. + +## Return value +A `Promise` that resolves to either one FSItem or an array of FSItem objects, depending on how many files were selected by the user. + +## Examples + +```html + + + + +

+ + +
+ + + + +``` + + + +Presents the user with a file picker dialog allowing them to specify where and with what name to save a file. + +## Syntax +```js +puter.ui.showSaveFilePicker() +puter.ui.showSaveFilePicker(data, defaultFileName) +``` + +## Parameters +#### `defaultFileName` (String) +The default file name to use. + +## Examples + +```html + + + +

+ + +
+ + + + +``` + + + + +Presents a dialog to the user allowing them to share a link on various social media platforms. + +## Syntax + +```js +puter.ui.socialShare(url) +puter.ui.socialShare(url, message) +puter.ui.socialShare(url, message, options) +``` + +## Parameters + +#### `url` (required) + +The URL to share. + + +#### `message` (optional) + +The message to prefill in the social media post. This parameter is only supported by some social media platforms. + +#### `options` (optional) + +A set of key/value pairs that configure the social share dialog. The following options are supported: + +* `left` (Number): The distance from the left edge of the window to the dialog. Default is `0`. +* `top` (Number): The distance from the top edge of the window to the dialog. Default is `0`. + + + +Returns whether the app was launched to open one or more items. Use this in conjunction with `onLaunchedWithItems()` to, for example, determine whether to display an empty state or wait for items to be provided. + +## Syntax +```js +puter.ui.wasLaunchedWithItems() +``` + +## Return value +Returns `true` if the app was launched to open items (via double-clicking, 'Open With...' menu, etc.), `false` otherwise. + + + + +A property of the `puter` object that returns the App ID of the running application. + +## Syntax + +```js +puter.appID +``` + +## Examples + +Get the ID of the current application + +
+ + +```html + + + + + + +``` + +
+ + + +A property of the `puter` object that returns the environment in which Puter.js is being used. + +## Syntax + +```js +puter.env +``` + +## Return value + +A string containing the environment in which Puter.js is being used: + +- `app` - Puter.js is running inside a Puter application. e.g. `https://puter.com/app/editor` + +- `web` - Puter.js is running inside a web page outside of the Puter environment. e.g. `https://example.com/index.html` + +- `gui` - Puter.js is running inside the Puter GUI. e.g. `https://puter.com/` + +## Examples + +Get the environment in which Puter.js is running + +
+ + +```html + + + + + + +``` + +
+ + + +Prints a string by appending it to the body of the document. This is useful for debugging and testing purposes and is not recommended for production use. + +## Syntax + +```js +puter.print(text); +``` + +## Parameters + +#### `text` (String) +The text to print. + +## Examples + +Print "Hello, world!" + +
+ + +```html + + + + + + +``` + +
+ + + +A function that generates a domain-safe name by combining a random adjective, a random noun, and a random number (between 0 and 9999). The result is returned as a string with components separated by hyphens by default. You can change the separator by passing a string as the first argument to the function. + +## Syntax + +```js +puter.randName() +puter.randName(separator) +``` + +## Parameters + +#### `separator` (String) +The separator to use between components. Defaults to `-`. + +## Examples + +Generate a random name + +
+ + +```html + + + + + + +``` + +
+ + + +
+ +
+ +
+
+
+
+
+

AI Chat

+

A chat app with AI using the Puter AI module. This app is powered by OpenAI GPT-4o mini.

+
+
+ +
+ +
+
+
+
+
+

To Do List

+

A simple to do list app with cloud functionalities powered by the Puter Key-Value Store.

+
+
+ +
+ +
+
+
+
+
+

Notepad

+

A simple notepad app with cloud functionalities.

+

Source Code

+
+
+ +
+ +
+
+
+
+
+

Image Describer

+

Allows you take a picture and describe it using the Puter AI module. This app is powered by OpenAI GPT-4 Vision.

+
+
+ +
+ +
+
+
+
+
+

Text Summarizer

+

Uses the Puter AI module to summarize a given long text. The model used in the background is GPT-4o mini.

+
+
+ +
+ + + + +## Installation + +To begin using Puter.js, simply add it to your HTML file using the following script tag: + +```html + +``` + +That's it! You're now ready to start using Puter.js in your web application. No need to install any dependencies or set up a server. No API keys or configuration required. + +## Basic Usage +Once you've added the Puter.js script to your web application, a global `puter` object will be available for you to use. This object contains all of the functionality provided by Puter.js. For example, to use GPT-4o mini, you can call the `puter.ai.chat` function: + +```html + + + + + + +``` + +This is all you need to use GPT-4o mini in your app. No backend code, no configuration, and no API keys. Just include the Puter.js script, and you're ready to start. + +## Where to Go From Here + +To learn more about the capabilities of Puter.js and how to use them in your web application, check out + +- [Tutorials](https://developer.puter.com/tutorials): Step-by-step guides to help you get started with Puter.js and build powerful applications. + +- [Playground](https://docs.puter.com/playground): Experiment with Puter.js in your browser and see the results in real-time. Many examples are available to help you understand how to use Puter.js effectively. + +- [Examples](https://docs.puter.com/examples): A collection of code snippets and full applications that demonstrate how to use Puter.js to solve common problems and build innovative applications. + + + +## Puter.js + +Puter.js brings serverless auth, cloud, and AI services directly to your browser-side JavaScript with no backend code or configuration required. Just include a single ` + + + +``` + +Read a file from the cloud + +```html;fs-read + + + + + + +``` + + + +
+ +#### Save user preference in the cloud Key-Value Store + +```html;intro-kv-set + + + + + + +``` + +
+ + +
+ + +#### Chat with GPT-4o mini + +```html;intro-chatgpt + + + + + + +``` + +

GPT-4 Vision

+ +```html;intro-gpt-vision + + + + + + + +``` + +Generate an image of a cat using DALL·E 3 + +```html;ai-txt2img + + + + + + +``` + + +

Stream the response

+ +```html;ai-chat-stream + + + + + + +``` + + +
+ +
+ +#### Publish a static website + +```html;intro-hosting + + + + + + +``` + +
+ +
+ +#### Authenticate a user + +```html;intro-auth + + + + + + + +``` + +
+ + + +In this document we will cover the security model of Puter.js and how it manages apps' access to user data and cloud resources. + +## Authentication + +If Puter.js is being used in a website, as opposed to a puter.com app, the user will have to authenticate with Puter.com first, or in other words, the user needs to give your website permission before you can use any of the cloud services on their behalf. + +Fortunately, Puter.js handles this automatically and the user will be prompted to sign in with their Puter.com account when your code tries to access any cloud services. If the user is already signed in, they will not be prompted to sign in again. You can build your app as if the user is already signed in, and Puter.js will handle the authentication process for you whenever it's needed. + +
+ +
The user will be automatically prompted to sign in with their Puter.com account when your code tries to access any cloud services or resources.
+
+ +If Puter.js is being used in an app published on Puter.com, the user will be automatically signed in and your app will have full access to all cloud services. + +## Default permissions + +Once the user has been authenticated, your app will get a few things by default: + +- **An app directory** in the user's cloud storage. This is where your app can freely store files and directories. The path to this directory will look like `~/AppData//`. This directory is automatically created for your app when the user has been authenticated the first time. Your app will not be able to access any files or data outside of this directory by default. + +- **A key-value store** in the user's space. Your app will have its own sandboxed key-value store that it can freely write to and read from. Only your app will be able to access this key-value store, and no other apps will be able to access it. Your app will not be able to access any other key-value stores by default either. + +
Apps are sandboxed by default! Apps are not able to access any files, directories, or data outside of their own directory and key-value store within a user's account. This is to ensure that apps can't access any data or resources that they shouldn't have access to.
+ +Your app will also be able to use the following services by default: + +- **AI**: Your app will be able to use the AI services provided by Puter.com. This includes chat, txt2img, img2txt, and more. + +- **Hosting**: Your app will be able to use puter to create and publish websites on the user's behalf. + + + + +The User Pays Model is the underlying business and operational approach that allows Puter.js to offer its services for free to developers. Under this model, each user of your application pays for their own consumption of resources (storage, AI requests, computing power) through their Puter account, rather than the developer bearing these costs. + +When a user interacts with your Puter.js-powered application, they authenticate with their Puter account, and any resources consumed (such as cloud storage used or AI API calls made) are charged to their account—not to you as the developer. + +

+ +# Advantages of the User Pays Model + +## 1. Zero Infrastructure Costs for Developers + +Perhaps the most significant advantage is that you, as a developer, don't pay anything for infrastructure costs when using Puter.js for your infrastructure. Whether your app serves one user or one million users, your costs remain the same: zero. + +## 2. No Need for API Key Management + +You don't need to: +- Register for various AI and cloud service providers +- Manage and rotate API keys +- Worry about securing your API keys +- Monitor usage and billing for each service +- Pay for services that you don't use + + +## 3. Built-in Security + +The authentication and authorization are handled by Puter's infrastructure: +- Users authenticate directly with Puter +- Your app operates within the permissions granted by the user +- Data is protected through Puter's security mechanisms + +## 4. No Anti-Abuse Implementation Required + +You don't need to implement: +- Rate limiting +- CAPTCHA verification +- IP blocking +- Usage quotas +- Fraud detection + +Bad actors have no incentive to abuse the system because they are paying for their own usage. + +## 5. Simpler Codebase + +Since authentication, storage, and API access are all handled through Puter.js: +- Your codebase is significantly simpler +- You can focus entirely on your application's unique functionality +- Frontend-only development is possible for many applications + +## 6. No Need to Ask Users for Their API Keys + +Many AI applications require users to provide their own API keys for services like OpenAI. With Puter.js: +- Users don't need to have their own API keys +- Users don't need to understand how to get or manage API keys +- You avoid the security and UX concerns of handling user API keys + +## 7. Simplified User Experience + +For your users: +- Single sign-on through Puter +- Unified billing through their existing Puter account +- No need to create accounts with multiple service providers + +
+ +## Everybody wins! + +The User Pays Model enables Puter.js to provide a truly serverless development experience where you can build sophisticated applications with AI, cloud storage, and authentication—all from the frontend—without worrying about infrastructure costs, security, or scaling. It creates a win-win situation where developers can build without overhead costs, and users pay only for the resources they actually consume. \ No newline at end of file diff --git a/css/styles.css b/css/styles.css index 1213637..28303ff 100644 --- a/css/styles.css +++ b/css/styles.css @@ -5,6 +5,8 @@ --text-color: #ffffff; --accent-color: #4CAF50; --danger-color: #ff4444; + --header-bg: #333; + --button-banner-bg: #444; } * { @@ -22,19 +24,20 @@ body { flex-direction: column; } -.app-container { - position: relative; - width: 100%; - height: 100vh; +.header { + position: absolute; + top: 0; + left: 0; + right: 0; display: flex; - justify-content: center; + justify-content: space-between; align-items: center; + padding: 10px; + background-color: var(--header-bg); + z-index: 1000; } .disconnect-btn { - position: absolute; - top: 20px; - left: 20px; padding: 10px 20px; background-color: var(--button-bg); color: var(--danger-color); @@ -51,9 +54,6 @@ body { } .connect-btn { - position: absolute; - top: 20px; - left: 20px; padding: 10px 20px; background-color: var(--button-bg); color: var(--accent-color); @@ -82,9 +82,6 @@ body { align-items: center; transition: all 0.3s ease; z-index: 2; - position: absolute; - bottom: 22px; - right: 25px; } .camera-btn, @@ -101,16 +98,26 @@ body { align-items: center; transition: all 0.3s ease; z-index: 2; - position: absolute; - right: 25px; } .camera-btn { - bottom: 142px; + /* Position handled by button-banner */ } .screen-btn { - bottom: 82px; + /* Position handled by button-banner */ +} + +/* Media query for devices with width less than 340px */ +@media (max-width: 340px) { + .camera-preview { + width: 180px; + right: 25px; + } + .screen-preview { + width: 180px; + height: 101px; /* Maintain 16:9 aspect ratio */ + } } .camera-btn:hover, @@ -154,12 +161,14 @@ body { .text-input-container { position: absolute; - bottom: 20px; - left: 20px; - right: 100px; /* Leave space for mic button */ + bottom: 0; + left: 0; + right: 0; + padding: 10px; display: flex; gap: 10px; z-index: 2; + background-color: var(--button-banner-bg); /* Match banner background */ } .text-input { @@ -167,7 +176,7 @@ body { padding: 12px; border-radius: 8px; border: 1px solid var(--accent-color); - background-color: var(--button-bg); + background-color: var(--bg-color); /* Use background color for input */ color: var(--text-color); font-size: 16px; outline: none; @@ -182,9 +191,9 @@ body { width: 40px; height: 40px; border-radius: 8px; - background-color: var(--button-bg); - border: 1px solid var(--accent-color); - color: var(--accent-color); + background-color: var(--accent-color); /* Use accent color for send button */ + border: none; /* Remove border */ + color: var(--text-color); cursor: pointer; display: flex; justify-content: center; @@ -199,19 +208,19 @@ body { .visualizer { position: absolute; - bottom: 0; + bottom: 60px; /* Adjust position to be above the button banner */ left: 0; width: 100%; - height: 200px; + height: 100px; /* Reduce height */ z-index: 1; } .camera-preview { position: absolute; - bottom: 100px; - left: 20px; - width: 240px; /* Default width */ - height: 180px; + bottom: 110px; /* Adjust position */ + left: 10px; /* Adjust position */ + width: 160px; /* Reduce size for mobile */ + height: 120px; /* Reduce size for mobile */ background-color: var(--button-bg); border: 1px solid var(--accent-color); border-radius: 8px; @@ -228,10 +237,10 @@ body { .screen-preview { position: absolute; - bottom: 300px; - left: 20px; - width: 240px; - height: 135px; /* 16:9 aspect ratio */ + bottom: 240px; /* Adjust position */ + left: 10px; /* Adjust position */ + width: 160px; /* Reduce size for mobile */ + height: 90px; /* Maintain 16:9 aspect ratio */ background-color: var(--button-bg); border: 1px solid var(--accent-color); border-radius: 8px; @@ -246,15 +255,98 @@ body { object-fit: contain; /* Maintain aspect ratio without cropping */ } -/* Media query for devices with width less than 340px */ -@media (max-width: 340px) { - .camera-preview { - width: 180px; - right: 25px; +/* Media query for mobile devices */ +@media (max-width: 768px) { + .app-container { + flex-direction: column; + justify-content: flex-start; + padding-top: 60px; /* Space for header */ + padding-bottom: 60px; /* Space for input */ + } + + .header { + position: fixed; /* Keep header fixed */ + } + + .disconnect-btn, + .connect-btn, + .settings-btn { + position: static; /* Remove absolute positioning */ + } + + .chat-history { + position: static; /* Remove absolute positioning */ + flex: 1; /* Allow chat history to grow */ + width: 100%; + margin: 0; /* Remove margins */ + border-radius: 0; /* Remove border radius */ + padding: 10px; } + + .chat-message { + max-width: 95%; /* Increase max-width */ + } + + .user-message { + margin-left: 5%; /* Adjust margin */ + } + + .model-message { + margin-right: 5%; /* Adjust margin */ + } + + .button-banner { + position: fixed; + bottom: 60px; /* Position above input */ + left: 0; + right: 0; + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + background-color: var(--button-banner-bg); + z-index: 2; + gap: 20px; /* Add gap between buttons */ + } + + .mic-btn, + .camera-btn, + .screen-btn { + position: static; /* Remove absolute positioning */ + bottom: auto; + right: auto; + } + + .text-input-container { + position: fixed; /* Keep input fixed */ + bottom: 0; + left: 0; + right: 0; + padding: 10px; + background-color: var(--button-banner-bg); /* Match banner background */ + } + + .visualizer { + display: none; /* Hide visualizer on mobile */ + } + + .camera-preview, .screen-preview { - width: 180px; - height: 101px; /* Maintain 16:9 aspect ratio */ + position: fixed; /* Keep previews fixed */ + bottom: 120px; /* Position above button banner */ + left: 10px; + width: 120px; /* Further reduce size */ + height: 90px; + } + + .screen-preview { + bottom: 220px; /* Adjust position */ + height: 67.5px; /* Maintain 16:9 aspect ratio */ + } + + .settings-dialog { + width: 95%; /* Increase width */ + padding: 15px; /* Reduce padding */ } } @@ -281,7 +373,7 @@ body { background: rgba(0, 0, 0, 0.7); } -/* Hide on desktop */ +/* Hide camera switch button on desktop */ @media (hover: hover) and (pointer: fine) { .camera-switch-btn { display: none; @@ -289,9 +381,6 @@ body { } .settings-btn { - position: absolute; - top: 20px; - right: 20px; padding: 10px; background-color: var(--button-bg); color: var(--text-color); @@ -399,11 +488,11 @@ body { .chat-history { position: absolute; top: 60px; - left: 20px; - right: 20px; - bottom: 120px; + left: 0; + right: 0; + bottom: 60px; /* Adjust bottom to make space for the button banner and input */ background: rgba(0, 0, 0, 0.7); - border-radius: 10px; + border-radius: 0; /* Remove border-radius for full width */ padding: 15px; overflow-y: auto; display: flex; @@ -415,7 +504,7 @@ body { .chat-message { padding: 10px 15px; border-radius: 15px; - max-width: 80%; + max-width: 90%; /* Increase max-width for mobile */ word-wrap: break-word; line-height: 1.4; } @@ -424,14 +513,14 @@ body { background: #2c5282; color: white; align-self: flex-end; - margin-left: 20%; + margin-left: 10%; /* Adjust margin for mobile */ } .model-message { background: #2d3748; color: white; align-self: flex-start; - margin-right: 20%; + margin-right: 10%; /* Adjust margin for mobile */ } .model-message.streaming::after { diff --git a/index.html b/index.html index f454c0b..4bc65af 100644 --- a/index.html +++ b/index.html @@ -7,28 +7,30 @@ -
+
- - - +
+
- -
-
+
+ + + + +
-
+ diff --git a/js/dom/events.js b/js/dom/events.js index 7fba665..bb89f11 100644 --- a/js/dom/events.js +++ b/js/dom/events.js @@ -50,6 +50,18 @@ export function setupEventListeners(agent) { console.error('Error disconnecting:', error); } }); + // Add touchstart listener for disconnect + elements.disconnectBtn.addEventListener('touchstart', async (e) => { + e.preventDefault(); // Prevent potential double-tap zoom or other default touch actions + try { + await agent.disconnect(); + showConnectButton(); + [elements.cameraBtn, elements.screenBtn, elements.micBtn].forEach(btn => btn.classList.remove('active')); + isCameraActive = false; + } catch (error) { + console.error('Error disconnecting:', error); + } + }); // Connect handler elements.connectBtn.addEventListener('click', async () => { @@ -59,6 +71,15 @@ export function setupEventListeners(agent) { console.error('Error connecting:', error); } }); + // Add touchstart listener for connect + elements.connectBtn.addEventListener('touchstart', async (e) => { + e.preventDefault(); + try { + await ensureAgentReady(agent); + } catch (error) { + console.error('Error connecting:', error); + } + }); // Microphone toggle handler elements.micBtn.addEventListener('click', async () => { @@ -71,12 +92,44 @@ export function setupEventListeners(agent) { elements.micBtn.classList.remove('active'); } }); + // Add touchstart listener for mic + elements.micBtn.addEventListener('touchstart', async (e) => { + e.preventDefault(); + try { + await ensureAgentReady(agent); + await agent.toggleMic(); + elements.micBtn.classList.toggle('active'); + } catch (error) { + console.error('Error toggling microphone:', error); + elements.micBtn.classList.remove('active'); + } + }); // Camera toggle handler elements.cameraBtn.addEventListener('click', async () => { try { await ensureAgentReady(agent); - + + if (!isCameraActive) { + await agent.startCameraCapture(); + elements.cameraBtn.classList.add('active'); + } else { + await agent.stopCameraCapture(); + elements.cameraBtn.classList.remove('active'); + } + isCameraActive = !isCameraActive; + } catch (error) { + console.error('Error toggling camera:', error); + elements.cameraBtn.classList.remove('active'); + isCameraActive = false; + } + }); + // Add touchstart listener for camera + elements.cameraBtn.addEventListener('touchstart', async (e) => { + e.preventDefault(); + try { + await ensureAgentReady(agent); + if (!isCameraActive) { await agent.startCameraCapture(); elements.cameraBtn.classList.add('active'); @@ -92,9 +145,10 @@ export function setupEventListeners(agent) { } }); + // Screen sharing handler let isScreenShareActive = false; - + // Listen for screen share stopped events (from native browser controls) agent.on('screenshare_stopped', () => { elements.screenBtn.classList.remove('active'); @@ -105,7 +159,27 @@ export function setupEventListeners(agent) { elements.screenBtn.addEventListener('click', async () => { try { await ensureAgentReady(agent); - + + if (!isScreenShareActive) { + await agent.startScreenShare(); + elements.screenBtn.classList.add('active'); + } else { + await agent.stopScreenShare(); + elements.screenBtn.classList.remove('active'); + } + isScreenShareActive = !isScreenShareActive; + } catch (error) { + console.error('Error toggling screen share:', error); + elements.screenBtn.classList.remove('active'); + isScreenShareActive = false; + } + }); + // Add touchstart listener for screen share + elements.screenBtn.addEventListener('touchstart', async (e) => { + e.preventDefault(); + try { + await ensureAgentReady(agent); + if (!isScreenShareActive) { await agent.startScreenShare(); elements.screenBtn.classList.add('active'); @@ -126,14 +200,22 @@ export function setupEventListeners(agent) { try { await ensureAgentReady(agent); const text = elements.messageInput.value.trim(); - await agent.sendText(text); - elements.messageInput.value = ''; + if (text) { // Only send if text is not empty + await agent.sendText(text); + elements.messageInput.value = ''; + } } catch (error) { console.error('Error sending message:', error); } }; elements.sendBtn.addEventListener('click', sendMessage); + // Add touchstart listener for send button + elements.sendBtn.addEventListener('touchstart', (e) => { + e.preventDefault(); + sendMessage(); + }); + elements.messageInput.addEventListener('keypress', (event) => { if (event.key === 'Enter') { event.preventDefault(); @@ -143,6 +225,11 @@ export function setupEventListeners(agent) { // Settings button click elements.settingsBtn.addEventListener('click', () => settingsManager.show()); + // Add touchstart listener for settings button + elements.settingsBtn.addEventListener('touchstart', (e) => { + e.preventDefault(); + settingsManager.show(); + }); } // Initialize settings diff --git a/plan/planned.md b/plan/planned.md new file mode 100644 index 0000000..c6dc170 --- /dev/null +++ b/plan/planned.md @@ -0,0 +1,29 @@ +# Plan Phases and Steps +## Phase 1: Review and Planning +1. Review the full codebase files and learn the code structure. +2. Create a new folder called `APIDOCS` and store the provided API documentation in it. +3. Create a new folder called `plan` and summarize the files in it. + +## Phase 2: Mobile Layout Implementation +1. Implement a complete mobile layout for the web app. +2. Use various modes to construct/implement the UI/UX for mobile. +3. Mobile layout and touch control requirements: + - Request device permissions for camera, mic, and screen when activated. + - Camera video preview is a floating, draggable, and resizable thumbnail; camera icon glows red when active, disables camera and preview when toggled off, and includes a flip camera button. + - Screen share preview is a floating, draggable, and resizable thumbnail; screen icon glows red when active, disables screen share and preview when toggled off. + - MIC button activates microphone with red pulsing icon; can be active with camera or screen share; deactivates and stops pulsing when toggled off. + - Settings popup includes new themes: light mode, sunset, multicolour. + - Header includes a new Audio/Text switch to choose model reply mode. + +## Phase 3: App Layout Restructure +1. Restructure the app layout according to the provided design. +2. Move the settings button to the top right corner. +3. Implement touch-friendly controls for mobile. + +## Phase 4: Optimizations and New Features +1. Investigate and suggest app optimizations. +2. Investigate and suggest new features. + +## Phase 5: Implementation and Testing +1. Implement the plan phase by phase. +2. Test and validate each phase. \ No newline at end of file From 087378baeb2a2755f87dba6b3864092701cc2fe8 Mon Sep 17 00:00:00 2001 From: Jayreddin Date: Thu, 1 May 2025 15:27:10 +0000 Subject: [PATCH 3/4] feat: Add pulsing and glowing animations for mic, camera, and screen buttons --- css/styles.css | 34 ++++++++++++++++++++++++++++++++++ js/camera/camera.js | 28 ++++++++++++++++++---------- js/dom/events.js | 24 ++++++++++++++---------- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/css/styles.css b/css/styles.css index 28303ff..eaf89d1 100644 --- a/css/styles.css +++ b/css/styles.css @@ -534,3 +534,37 @@ body { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(255, 68, 68, 0.7); /* --danger-color */ + } + 70% { + box-shadow: 0 0 0 10px rgba(255, 68, 68, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(255, 68, 68, 0); + } +} + +@keyframes glow { + 0%, 100% { + box-shadow: 0 0 5px 2px rgba(255, 68, 68, 0.7); /* --danger-color */ + } + 50% { + box-shadow: 0 0 10px 5px rgba(255, 68, 68, 0.9); + } +} + +.mic-btn.pulsing { + animation: pulse 1.5s infinite; + background-color: var(--danger-color); /* Indicate active recording */ + border-color: var(--danger-color); +} + +.camera-btn.glowing, +.screen-btn.glowing { + animation: glow 2s infinite ease-in-out; + background-color: var(--danger-color); /* Indicate active streaming */ + border-color: var(--danger-color); +} diff --git a/js/camera/camera.js b/js/camera/camera.js index bfb205a..9130c49 100644 --- a/js/camera/camera.js +++ b/js/camera/camera.js @@ -52,8 +52,10 @@ export class CameraManager { if (!/Mobi|Android/i.test(navigator.userAgent)) return; this.switchButton = document.createElement('button'); + this.switchButton.id = 'camera-switch-btn'; // Add ID for consistency if needed this.switchButton.className = 'camera-switch-btn'; - this.switchButton.innerHTML = '⟲'; + this.switchButton.title = 'Switch Camera'; // Add title attribute + this.switchButton.innerHTML = '🔄'; // Use requested icon this.switchButton.addEventListener('click', () => this.switchCamera()); this.previewContainer.appendChild(this.switchButton); } @@ -122,16 +124,22 @@ export class CameraManager { this.videoElement = document.createElement('video'); this.videoElement.srcObject = this.stream; this.videoElement.playsInline = true; - - // Add video to preview container - const previewContainer = document.getElementById('cameraPreview'); - if (previewContainer) { - previewContainer.appendChild(this.videoElement); - this.previewContainer = previewContainer; - this._createSwitchButton(); // Add switch button - this.showPreview(); // Show preview when initialized + + // Create or find preview container + let previewContainer = document.getElementById('camera-preview'); + if (!previewContainer) { + previewContainer = document.createElement('div'); + previewContainer.id = 'camera-preview'; + previewContainer.className = 'camera-preview'; + // Append to body or a more specific container if available + document.body.appendChild(previewContainer); } - + previewContainer.innerHTML = ''; // Clear previous content if any + previewContainer.appendChild(this.videoElement); + this.previewContainer = previewContainer; + this._createSwitchButton(); // Add switch button + this.showPreview(); // Show preview when initialized + await this.videoElement.play(); // Get the actual video dimensions diff --git a/js/dom/events.js b/js/dom/events.js index bb89f11..dccef5b 100644 --- a/js/dom/events.js +++ b/js/dom/events.js @@ -85,11 +85,13 @@ export function setupEventListeners(agent) { elements.micBtn.addEventListener('click', async () => { try { await ensureAgentReady(agent); - await agent.toggleMic(); - elements.micBtn.classList.toggle('active'); + const isActive = await agent.toggleMic(); + elements.micBtn.classList.toggle('active', isActive); + elements.micBtn.classList.toggle('pulsing', isActive); } catch (error) { console.error('Error toggling microphone:', error); - elements.micBtn.classList.remove('active'); + // Ensure button state is reset on error (e.g., permission denied) + elements.micBtn.classList.remove('active', 'pulsing'); } }); // Add touchstart listener for mic @@ -112,15 +114,16 @@ export function setupEventListeners(agent) { if (!isCameraActive) { await agent.startCameraCapture(); - elements.cameraBtn.classList.add('active'); + elements.cameraBtn.classList.add('active', 'glowing'); } else { await agent.stopCameraCapture(); - elements.cameraBtn.classList.remove('active'); + elements.cameraBtn.classList.remove('active', 'glowing'); } isCameraActive = !isCameraActive; } catch (error) { console.error('Error toggling camera:', error); - elements.cameraBtn.classList.remove('active'); + elements.cameraBtn.classList.remove('active', 'glowing'); + isCameraActive = false; isCameraActive = false; } }); @@ -151,7 +154,7 @@ export function setupEventListeners(agent) { // Listen for screen share stopped events (from native browser controls) agent.on('screenshare_stopped', () => { - elements.screenBtn.classList.remove('active'); + elements.screenBtn.classList.remove('active', 'glowing'); // Remove glowing class isScreenShareActive = false; console.info('Screen share stopped'); }); @@ -162,15 +165,16 @@ export function setupEventListeners(agent) { if (!isScreenShareActive) { await agent.startScreenShare(); - elements.screenBtn.classList.add('active'); + elements.screenBtn.classList.add('active', 'glowing'); } else { await agent.stopScreenShare(); - elements.screenBtn.classList.remove('active'); + elements.screenBtn.classList.remove('active', 'glowing'); } isScreenShareActive = !isScreenShareActive; } catch (error) { console.error('Error toggling screen share:', error); - elements.screenBtn.classList.remove('active'); + elements.screenBtn.classList.remove('active', 'glowing'); + isScreenShareActive = false; isScreenShareActive = false; } }); From 2feab8b3c0e78544677c8590ec3ab154d238f4a7 Mon Sep 17 00:00:00 2001 From: Jayreddin Date: Thu, 1 May 2025 15:38:11 +0000 Subject: [PATCH 4/4] feat: Add draggable preview functionality for camera and screen components --- js/camera/camera.js | 80 ++++++++++++++++++++++++++++++++++++- js/screen/screen.js | 96 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 169 insertions(+), 7 deletions(-) diff --git a/js/camera/camera.js b/js/camera/camera.js index 9130c49..b5dd7bc 100644 --- a/js/camera/camera.js +++ b/js/camera/camera.js @@ -131,15 +131,26 @@ export class CameraManager { previewContainer = document.createElement('div'); previewContainer.id = 'camera-preview'; previewContainer.className = 'camera-preview'; - // Append to body or a more specific container if available document.body.appendChild(previewContainer); } previewContainer.innerHTML = ''; // Clear previous content if any + + // Add drag handle + const dragHandle = document.createElement('div'); + dragHandle.className = 'preview-drag-handle'; + dragHandle.style.cssText = 'cursor: grab; padding: 5px; background: rgba(0,0,0,0.5); color: white; text-align: center;'; + dragHandle.textContent = '⋮⋮'; // Drag handle icon + previewContainer.appendChild(dragHandle); + + // Add video element previewContainer.appendChild(this.videoElement); this.previewContainer = previewContainer; this._createSwitchButton(); // Add switch button this.showPreview(); // Show preview when initialized + // Initialize drag functionality + this._initializeDrag(dragHandle); + await this.videoElement.play(); // Get the actual video dimensions @@ -228,4 +239,71 @@ export class CameraManager { this.isInitialized = false; this.aspectRatio = null; } + + /** + * Initialize drag functionality for the preview container + * @param {HTMLElement} handle - The element to use as drag handle + * @private + */ + _initializeDrag(handle) { + let isDragging = false; + let currentX; + let currentY; + let initialX; + let initialY; + let xOffset = 0; + let yOffset = 0; + + const dragStart = (e) => { + if (e.type === "touchstart") { + initialX = e.touches[0].clientX - xOffset; + initialY = e.touches[0].clientY - yOffset; + } else { + initialX = e.clientX - xOffset; + initialY = e.clientY - yOffset; + } + + if (e.target === handle) { + isDragging = true; + handle.style.cursor = 'grabbing'; + } + }; + + const dragEnd = () => { + initialX = currentX; + initialY = currentY; + isDragging = false; + handle.style.cursor = 'grab'; + }; + + const drag = (e) => { + if (isDragging) { + e.preventDefault(); + + if (e.type === "touchmove") { + currentX = e.touches[0].clientX - initialX; + currentY = e.touches[0].clientY - initialY; + } else { + currentX = e.clientX - initialX; + currentY = e.clientY - initialY; + } + + xOffset = currentX; + yOffset = currentY; + + this.previewContainer.style.transform = + `translate3d(${currentX}px, ${currentY}px, 0)`; + } + }; + + // Touch events + handle.addEventListener("touchstart", dragStart, { passive: false }); + document.addEventListener("touchend", dragEnd); + document.addEventListener("touchmove", drag, { passive: false }); + + // Mouse events + handle.addEventListener("mousedown", dragStart); + document.addEventListener("mouseup", dragEnd); + document.addEventListener("mousemove", drag); + } } diff --git a/js/screen/screen.js b/js/screen/screen.js index d8c7fd4..54c1c2c 100644 --- a/js/screen/screen.js +++ b/js/screen/screen.js @@ -63,13 +63,30 @@ export class ScreenManager { this.videoElement.srcObject = this.stream; this.videoElement.playsInline = true; - // Add video to preview container - const previewContainer = document.getElementById('screenPreview'); - if (previewContainer) { - previewContainer.appendChild(this.videoElement); - this.previewContainer = previewContainer; - this.showPreview(); // Show preview when initialized + // Create or find preview container + let previewContainer = document.getElementById('screen-preview'); + if (!previewContainer) { + previewContainer = document.createElement('div'); + previewContainer.id = 'screen-preview'; + previewContainer.className = 'screen-preview'; + document.body.appendChild(previewContainer); } + previewContainer.innerHTML = ''; // Clear previous content if any + + // Add drag handle + const dragHandle = document.createElement('div'); + dragHandle.className = 'preview-drag-handle'; + dragHandle.style.cssText = 'cursor: grab; padding: 5px; background: rgba(0,0,0,0.5); color: white; text-align: center;'; + dragHandle.textContent = '⋮⋮'; // Drag handle icon + previewContainer.appendChild(dragHandle); + + // Add video element + previewContainer.appendChild(this.videoElement); + this.previewContainer = previewContainer; + this.showPreview(); // Show preview when initialized + + // Initialize drag functionality + this._initializeDrag(dragHandle); await this.videoElement.play(); @@ -163,4 +180,71 @@ export class ScreenManager { this.isInitialized = false; this.aspectRatio = null; } + + /** + * Initialize drag functionality for the preview container + * @param {HTMLElement} handle - The element to use as drag handle + * @private + */ + _initializeDrag(handle) { + let isDragging = false; + let currentX; + let currentY; + let initialX; + let initialY; + let xOffset = 0; + let yOffset = 0; + + const dragStart = (e) => { + if (e.type === "touchstart") { + initialX = e.touches[0].clientX - xOffset; + initialY = e.touches[0].clientY - yOffset; + } else { + initialX = e.clientX - xOffset; + initialY = e.clientY - yOffset; + } + + if (e.target === handle) { + isDragging = true; + handle.style.cursor = 'grabbing'; + } + }; + + const dragEnd = () => { + initialX = currentX; + initialY = currentY; + isDragging = false; + handle.style.cursor = 'grab'; + }; + + const drag = (e) => { + if (isDragging) { + e.preventDefault(); + + if (e.type === "touchmove") { + currentX = e.touches[0].clientX - initialX; + currentY = e.touches[0].clientY - initialY; + } else { + currentX = e.clientX - initialX; + currentY = e.clientY - initialY; + } + + xOffset = currentX; + yOffset = currentY; + + this.previewContainer.style.transform = + `translate3d(${currentX}px, ${currentY}px, 0)`; + } + }; + + // Touch events + handle.addEventListener("touchstart", dragStart, { passive: false }); + document.addEventListener("touchend", dragEnd); + document.addEventListener("touchmove", drag, { passive: false }); + + // Mouse events + handle.addEventListener("mousedown", dragStart); + document.addEventListener("mouseup", dragEnd); + document.addEventListener("mousemove", drag); + } }