# What is PrimeThink?

## Platform Overview

PrimeThink is an innovative platform that transforms how we interact with artificial intelligence in our daily work and life. Think of it as an operating system that uses natural language as its primary interface, powered by Large Language Models (LLMs). Just as your computer's operating system helps you manage files and run programs, PrimeThink helps you manage AI-powered conversations, tasks, and automation across different aspects of your work and life.

What makes PrimeThink special is its ability to create what we call "mini-brains" - specialized AI assistants that can be trained for specific purposes while maintaining privacy and security. These assistants can work together, share information when appropriate, and help automate complex tasks that would typically require multiple different tools and systems.

The platform works seamlessly across web browsers and mobile devices, making it accessible whenever and wherever you need it. You can think of it as having a team of intelligent assistants in your pocket, each specialized in different tasks but able to work together cohesively.

## Key Features and Benefits

### Intelligent Task Management

PrimeThink revolutionizes how we handle tasks and workflows. Instead of rigid, pre-programmed sequences, tasks in PrimeThink are goal-oriented and adaptable. You can define what you want to achieve, and the system will intelligently work toward that goal, adjusting its approach based on the context and circumstances. For example, you might set up a task to "help customers solve technical issues," and the system will not just follow a script but understand the context, access relevant information, and provide personalized assistance.

### Smart Memory System

The platform includes a sophisticated memory system that works similarly to human memory. It can remember important information from past interactions and apply it appropriately in future conversations. This memory is organized into two types: general knowledge that applies broadly, and personal information specific to individual users. The system uses advanced semantic search capabilities, meaning it can find relevant information even when questions are asked in different ways.

### Customizable Virtual Assistants

You can create specialized AI assistants tailored to specific needs. These assistants can be customized in terms of their capabilities, personality, and access to information. For instance, you might create one assistant that speaks casually and helps with creative tasks, and another that maintains a professional tone for business communications. Each assistant can be restricted to specific tools and information, ensuring security and appropriate use.

### Dynamic Interface Generation

PrimeThink can automatically create user interfaces based on natural language descriptions. This means you can describe what you want a page or form to look like, and the system will generate it. The interfaces can adapt based on user needs, preferences, and contexts, creating a truly personalized experience.

### Secure Architecture

The platform is built with security and privacy in mind. Each user's information is kept separate and secure, and you can create isolated groups for different projects or clients. The system can be deployed in various ways, from fully integrated solutions to API-only implementations, allowing organizations to maintain their security requirements.

## Use Cases

### Professional Task Support

PrimeThink excels in supporting professional workflows. It can record and transcribe meetings, track action items, monitor progress. The system maintains continuity across interactions by remembering previous discussions and decisions, and automatically generates summaries and progress reports.

### Educational Support

The platform can create personalized learning experiences by generating custom exercises, providing immediate feedback, and adapting to the learner's progress. It can schedule regular learning sessions, track completion of assignments, and provide progress reports to teachers or parents. The system can even integrate with reward systems to motivate learners.

### Document Processing and Analysis

PrimeThink helps organizations analyze documents, extract key information, and process communications efficiently. The system creates specialized agents for specific document-handling tasks, maintaining confidentiality while improving productivity. It organizes information, flags important details, and maintains secure communication channels.

### System Integration and Automation

PrimeThink serves as an intelligent interface for system automation. It learns from user habits and preferences, enabling natural language control of connected systems. The platform integrates with various automation platforms and devices, creating an intuitive and responsive environment.

### Customer Service Enhancement

Organizations can use PrimeThink to create sophisticated customer service solutions. The platform can handle initial customer inquiries, route complex issues to appropriate human agents, maintain conversation context, and ensure consistent follow-up. It can learn from each interaction to improve future responses while maintaining a personal touch.

### Personal productivity and note-taking

PrimeThink streamlines research and writing workflows. It helps gather and analyze information, organize ideas, and generate content outlines. The system maintains citation tracking, suggests relevant sources, and can assist with drafting and editing while ensuring content accuracy and consistency.

### Research and Development Support

Researchers can use PrimeThink to automate literature reviews, track experiments, analyze data, and facilitate collaboration. The system can maintain detailed records, generate reports, and help identify patterns or connections that might otherwise be missed.

### Interactive Multi-User Games

PrimeThink enables competitive learning through real-time multiplayer interactions. It manages quiz sessions, tracks participant responses, handles scoring, and maintains leaderboards. The system ensures fair play, provides instant feedback, and can adapt question difficulty based on participant performance.

### And much more...

These use cases represent just a fraction of what's possible with PrimeThink. The platform's flexibility and adaptability make it suitable for virtually any scenario where intelligent, context-aware automation and assistance would be valuable. As organizations and individuals continue to explore its capabilities, new and innovative uses for the platform continue to emerge.

## System Requirements

### Web Platform

* Modern web browser (Chrome, Firefox, Safari, Edge)

* Stable internet connection

* JavaScript enabled

* HTML5 compatible browser for advanced features

### Mobile Applications

#### iPhone

* iOS 12.0 or later

* Compatible with iPhone, iPad, and iPod touch

* Optimized for latest iOS versions

#### iPad

* iPadOS 12.0 or later

* Supports both iPad and iPad Pro

* Optimized for tablet interface

#### Android

* Android 8.0 (Oreo) or later

* Compatible with both phones and tablets

* Supports various screen sizes and resolutions

### Additional Requirements

* Microphone access for voice message features

* Location services (optional) for location sharing features

* Camera access (optional) for image sharing features



# Download PrimeThink

## Accessing PrimeThink

PrimeThink is a cross-platform solution designed to work seamlessly across multiple devices and environments. You can access the platform through various channels to suit your specific needs and preferences.

## Web Application

The primary way to access PrimeThink is through our web application:

* Browser Access: [https://app.primethink.ai](https://app.primethink.ai)

* Supported Browsers: Chrome, Firefox, Safari, and Edge (latest versions recommended)

* System Requirements: See [System Requirements](what-is-primethink.html#system-requirements) for detailed specifications

The web application offers the full range of features and is optimized for both desktop and mobile browsers, providing a responsive experience regardless of screen size.

## Mobile Applications

Access PrimeThink on the go with our dedicated mobile applications:

* iOS (iPhone and iPad): Coming Soon to the App Store

* Android (Phone and Tablet): Coming Soon to Google Play Store

Our mobile apps are designed to provide a native experience with optimized interfaces for smaller screens while maintaining feature parity with the web application.

## Browser Extensions

Enhance your browsing experience with our browser extension:

* Chrome Extension: [Available on Chrome Web Store](https://chromewebstore.google.com/detail/primethink/hadlipakfofndmkdohhomlpejpnofeid)

The browser extensions allow you to quickly access PrimeThink features without leaving your current webpage, PrimeThink is aware of the current URL in the browser while you use the extension. It's perfect for research and content collection workflows.



# Quick Start

Getting started with PrimeThink is straightforward. This guide will walk you through the essential steps to begin using the platform effectively. Follow these basic steps to start your journey with PrimeThink:

1. Create your account

2. Set up your first workspace

3. Invite your team members

4. Familiarize yourself with the navigation

5. Start your first chat

## Creating Your Account

To begin using PrimeThink, you'll need to set up your account. During this process, you'll:

* Choose your username and set a secure password

* Configure your profile information

* Set your preferences for notifications

* Choose your default virtual assistant settings

Keep in mind that your account will be your gateway to accessing different organizations and workspaces within PrimeThink, so take time to set it up properly.

## Setting Up Your Workspace

Once your account is created, you can set up your workspace to organize your work effectively:

### Creating Workspaces

1. Navigate to the workspaces tab section in the top navigation bar

2. Click the "+" button to create a new workspace

3. Name your workspace based on your project or work area

4. Organize your workspaces logically to separate different types of work

### Workspace Organization Tips

* Create separate workspaces for different projects or work types

* Use clear naming conventions for easy identification

* Set up any necessary virtual assistants for your workspace

* Configure workspace-specific settings as needed

### Initial Setup Checklist

* Configure workspace preferences

* Set up document collections if needed

* Establish any required virtual assistant configurations

* Create initial chat categories or structures

## Inviting Team Members

Bringing your team into PrimeThink is simple and can be done in a few steps:

### Sending Invitations

1. Locate the invite button in the Group Switcher's top actions

2. Enter team members' email addresses

3. Set appropriate access levels and permissions

4. Send invitations with any necessary welcome messages

### Managing Team Access

* Configure group-specific settings for your team

* Assign appropriate roles to team members (coming soon)

## Basic Navigation

Understanding PrimeThink's interface will help you work more efficiently:

### Top Navigation Bar

The top navigation bar is your primary navigation hub, containing:

* PrimeThink logo and brand identifier

* Workspace navigation tabs

* Essential control buttons

* Access to key features like Memory and Collections

### Left Icon Bar (Organization Switcher)

This vertical bar allows you to:

* Switch between different organizations

* Access group-specific features

* Toggle location sharing

* Manage text-to-speech settings

### Left Sidebar

The left sidebar contains:

* New Chat button for starting conversations

* Tasks Management for workflow automation

* Filtering tools for organizing chats

* List of active conversations

### Main Chat Window

Your primary workspace features:

* Clear message threading

* Rich content support

* File attachment capabilities

* Message interaction tools

### Right Context Panel

The adaptive sidebar provides:

* Context-specific tools

* Document management

* Additional features based on your current work

### Navigation Tips

* Use keyboard shortcuts for faster navigation

* Familiarize yourself with the quick action menus

* Learn to use filters effectively

* Practice switching between different workspaces

Remember that PrimeThink's interface is designed to be intuitive and will become more familiar as you use it. Start with these basic features and gradually explore more advanced functionality as you become comfortable with the platform.



# Introduction

PrimeThink is an innovative platform that combines the collaborative features of team chat applications with the power of AI assistants. Think of it as a workspace where you, your team members, and AI assistants can work together seamlessly to accomplish tasks, manage projects, and process information more efficiently.

## Getting Started

### Accessing PrimeThink

* Access the platform at: app.primethink.ai

* Log in with your group name, username, and password

* You'll be connected to your organization's workspace

### Understanding the Interface

PrimeThink's interface is organized into several key areas:

1. Top Navigation Bar: Switch between workspaces and access important tools

2. Left Icon Bar: Switch between different groups/organizations

3. Left Sidebar: Manage your chats and tasks

4. Main Chat Window: Your primary workspace for conversations

5. Right Context Panel: Access tools specific to your current chat

## Core Features

### 1. Chat with AI Assistants

Every chat in PrimeThink has a default AI assistant that will respond when you type a message. These assistants can:

* Answer questions and provide information

* Help with various tasks like writing, research, and analysis

* Process documents and extract information

* Remember context from your conversation

Pro Tip: You can choose different AI assistants based on your needs or create custom ones for specific purposes.

### 2. Multi-User Collaboration

PrimeThink makes it easy to work with teammates:

* Add multiple users to a chat, similar to platforms like Slack

* Tag specific people using @mentions

* Share documents and resources with your team

* Collaborate with both humans and AI in the same conversation

### 3. Document Handling

Upload and work with various types of documents:

* Upload files directly to chats (PDFs, Word docs, etc.)

* Paste URLs to automatically capture web content

* Record audio that gets transcribed into text

* The AI can search through documents to find relevant information

### 4. Organizing with Workspaces

Workspaces help you organize related work:

* Group related chats together under a workspace

* Share documents across all chats in a workspace

* Add team members at the workspace level

* Set workspace-specific preferences and instructions

### 5. Collections

Collections allow you to group related documents together:

* Create reusable sets of reference materials

* Share collections across different chats and workspaces

* Make collections private or public within your group

### 6. Tasks

Tasks are pre-defined workflows that help automate repetitive processes:

* Import tasks from the Task Library for common needs

* Create custom tasks for your specific workflows

* Schedule tasks to run automatically

* Tasks can have specific goals, prompts, and capabilities

## Common Use Cases

Here are some popular ways to use PrimeThink:

* Document Analysis: Upload documents to extract information, summarize content, or answer specific questions.

* Project Management: Create workspaces for projects and use tasks to track progress and organize information.

* Content Creation: Work with AI assistants to draft, edit, and refine various types of content.

* Knowledge Management: Build collections of important documents and make them searchable across your team.

* Automated Workflows: Create tasks for repetitive processes like data collection, reporting, or customer support.

## Getting Help

If you need assistance:

* Explore the detailed documentation to learn more

* Use the feedback button to report issues or suggest improvements

* Ask questions in the support chat within your group

* Contact your organization's PrimeThink administrator

Remember, PrimeThink is designed to be intuitive and adapt to your needs. The more you use it, the more you'll discover how it can enhance your workflow!



# User Interface Guide: a quick look

## Interface Overview

The PrimeThink platform features an intuitive interface organized into distinct sections that work together seamlessly, creating a powerful workspace for both individual and team collaboration.

## Key Interface Components

### Top Navigation Bar

* PrimeThink Logo: Brand identifier or default virtual assistant image

* Workspace Navigation: Quick switching between work contexts with customizable tabs

* Navigation Controls: Access to Memory, Collections, Members, and Notifications

### Organization Switcher (Left Icon Bar)

* Switch between different groups/collaborative spaces

* Each group maintains separate resources, participants, and conversations

* Bottom section includes tools for managing group access

### Left Sidebar

* New Chat Button: Start conversations and create workspaces

* Tasks Management: Launch predefined chat workflows

* Filtering Tools: Sort by chat types, favorites, and virtual assistants

* Chat List: Displays conversations with visual indicators and real-time updates

### Main Chat Window

* Chat Display: Chronological message thread with rich content support

* Message Interaction: Quick actions (play, copy, more options)

* Message Management: Save as file/memory/document, delete, report

* Composition Field: Text input with file attachments and @mentions

### Right Context Panel

* Info Tab: Essential information about the current chat

* Search Scope: Control where searches are performed

* Documents Tab: Manage associated files

* Collections Tab: Link existing collections to current chat

* Members Tab: List and manage chat participants

* Scheduled Jobs Tab: Create and manage automated tasks

## Chat Types

* Standard: One-to-one chat with virtual assistants

* Multi User: Group chat with multiple humans and virtual assistants

## Key Features

### Virtual Assistants

* Set a default assistant for automatic replies

* Mention specific assistants with @ symbol

* Configure assistant capabilities based on needs

### Workspaces

* Organize chats into logical sections

* Drag and drop chats between workspaces

* Create new workspaces with the "+" button

### Sub-chats

* Nested conversations for focused discussions

* Inherit properties from parent conversations

* Accessible through the Subchats tab

### Documents & Collections

* Upload files directly to chats

* Associate existing collections

* Search across documents and collections

## Getting Started

1. Familiarize yourself with the navigation bar

2. Practice switching between organizations

3. Explore chat creation and management

4. Try out utility controls in the chat window

5. Discover how the right sidebar adapts to different work types

For more detailed information, access the complete help center through the help icon in the top navigation bar.



# User Interface Guide: Deep Dive

The PrimeThink platform features a thoughtfully designed interface that prioritizes productivity while maintaining intuitive navigation. When you first open the application, you'll notice how the interface is organized into distinct sections that work together seamlessly, creating a powerful yet approachable workspace for both individual and team collaboration.

## Top Navigation Bar

The top navigation bar serves as your primary navigation hub, spanning the entire width of the interface.

### The PrimeThink Logo

At its leftmost position, you'll find the PrimeThink logo, which serves as a brand identifier or as the image of the default virtual assistant when a chat is selected.

### Workspace Navigation

Moving right, you'll encounter the workspaces tab section, which enables quick navigation between different work contexts. These tabs help you organize your work into logical sections, and you can create new workspaces using the "+" button whenever you need to expand your organization.

The workspace system is particularly powerful because it lets you separate different types of work or projects while keeping them all readily accessible. For example, you might have one workspace for client communications, another for internal team discussions, and a third for personal tasks.

## Organizing Workspaces

Create separate workspaces for different types of work or projects. This helps you:

* Maintain focus on specific tasks

* Keep related conversations and resources together

* Switch contexts cleanly when needed

* Manage multiple projects efficiently

### Navigation Controls

The far right of the top bar contains a collection of important navigation buttons, each with a descriptive tooltip to help you understand its function:

* View Memory: allows you to access historical data and saved contexts, helping you maintain continuity in your work and preferences.

* View Collections: opens the document management system where you can organize and access your resources.

* Members: provides access to team management tools

* Notification: button helps you track updates and notifications.

The Show Menu button reveals additional options:

* Virtual Assistants Admin: to manage the global and your personal virtual assistants.

* Scheduled Jobs: to manage all the scheduled jobs created on each chat.

In mobile view, you will have the option to open and close the chat sidebar when a chat is selected.

## Organization Switcher (Left Icon Bar)

The Group Switcher is your gateway to different collaborative spaces within PrimeThink. Located in the leftmost vertical bar of your interface, this essential tool helps you move seamlessly between different groups while maintaining clear boundaries between separate work contexts. Think of it as your professional control center – just as you might move between different teams or departments throughout your workday, the Group Switcher helps you transition smoothly between different collaborative spaces while keeping each one distinct and properly organized.

### What Is a Group?

A group in PrimeThink represents a distinct collaborative space with its own set of resources and participants. When you join a group, you become part of a self-contained workspace that includes:

* Your team members and their specific roles within the group

* All conversations and chat histories related to that group's activities

* Documents and resources shared within the group

* Specific settings and preferences for that particular collaborative space

* AI assistants configured for the group's needs

This separation ensures that work, conversations, and resources from one group remain separate from others, maintaining privacy and organizational clarity.

For example, if you're working with multiple teams or clients, each one can have its own group, ensuring that discussions and resources stay properly organized and confidential.

### Switching Between Groups

Each organization appears as a distinct icon, and a single click lets you switch between these different contexts while maintaining complete separation of content and conversations.

The currently active group remains highlighted, providing a clear visual indicator of your working context.

### Managing Group Access

At the bottom of the Group Switcher, you'll find essential tools for managing your group connections:

* Joining new groups when you receive invitations

* Creating a new group with the current logged user (coming soon)

### Top actions

A column of helpful actions, including:

* An invite button for adding new members to your group

* A location sharing toggle for when you need to share your location with the virtual assistants you use

* Text-to-speech controls to have messages read aloud automatically

### Bottom actions

A column of helpful actions, including:

* A feedback mechanism for reporting issues or suggesting improvements

* The Help button for the online help.

* User Settings specific to the selected group.

## Left Sidebar

The left sidebar functions as your command center for chat and task management. Understanding its three distinct sections will help you navigate more efficiently:

### Top Control Section

In the upper portion, you'll find essential controls that help you manage your daily workflow.

### New Chat Button

The New Chat button serves as your primary entry point for starting conversations. When you click this button, you're not just creating a simple chat - you're initiating a new workspace that can evolve based on your needs. This button adapts to your current context, meaning the options available to you will change depending on your permissions and the type of work you're doing.

When you click the New Chat button, you might notice that PrimeThink offers different types of conversations based on your needs. For example, you could start:

A standard chat for quick discussions or note-taking
A collaborative space for team discussions
A specialized chat with an AI assistant for specific tasks like document analysis or proofreading
A multi-participant workspace for larger team collaborations

Understanding when to use each type of chat can significantly improve your workflow. For instance, if you need to analyze a legal document, starting a chat with the Legal Document Analyzer assistant will provide you with specialized tools and capabilities specific to that task.

### Tasks Management

Next to it, the Tasks Management button helps you start new predefined chat workflows ("Tasks") with a click of a button.

## Filtering and Organization Tools

The filter system includes a dropdown menu for selecting chat types (such as Standard or Multi Users), along with toggles for marking favorites, archived (coming soon) and filtering by Virtual Assistant.

### Chat List Section

The middle section displays your conversations in an organized list. Each chat entry shows relevant information like the chat name and timestamp, with visual indicators helping you quickly identify different types of conversations. The list updates in real-time to show new messages, status changes and unread messages.

The chat are ordered by last activity (last first) and with re-order based on usage.

### Chat types

A chat could be of the following types:

* Standard: a 1-to-1 chat with one or more virtual assistant

* Multi user: a chat where beside the VA there are also more humans. In this chat the memory is automatically disabled and the system will not use it or extract new memories from the user messages.

### Assign workspace

You can drag and drop a chat into a workspace name to assign it to that specific workspace.

### Delete a chat

When you hover your mouse over a chat entry, a delete icon appears, giving you the option to remove conversations that are no longer needed.

### Sub-chats

For chats that contain sub-conversations or nested discussions, an arrow indicator appears at the end of the chat entry. This arrow serves as both a visual indicator that the chat contains subchats. The subchats will be visible in the Subchats tab, in the right chat sidebar.

Sub-chats inherit certain properties from their parent conversation while maintaining their own distinct space. This means you can have focused discussions about specific aspects of a project while keeping everything organized under the main topic.

### Chat Tips

1. Create meaningful naming conventions for chats

2. Use of workspaces for to group chats by workspace

3. Regular archive or deletion of outdated or completed conversations

4. Maintaining organized favorites

## Main Chat Window

The central area serves as your primary workspace, where most of your interaction happens. It's designed to provide a clear view of your conversations while offering powerful tools right where you need them:

### Chat Display

Messages appear in a chronological thread, with clear visual distinction between different types of content. The interface supports rich content including text, files, and code snippets, displaying each appropriately to maintain readability.

#### Message Interaction Features

PrimeThink provides several ways to interact with messages, making it easy to manage and use information within your conversations. Let's explore these interaction options in detail.

##### Quick Action Menu

Every message in your chat includes a Quick Action Menu in the top-right corner. This menu contains three primary actions that you'll use frequently:

The Play button activates text-to-speech for the message, allowing you to listen to the content instead of reading it. This feature proves particularly useful when you're multitasking or need to review longer messages while doing other work.

The Copy button creates a clipboard copy of the message content, preserving all formatting and structure. This makes it easy to reference or share information from your conversations in other contexts.

The More Options button (three dots) reveals additional message-specific actions, giving you access to advanced features when you need them.

##### Message Management Options

Each message can be managed through a contextual menu that appears when you click the More Options button. This menu provides several important functions:

You can add message content to your next prompt, building on previous conversations in a structured way. This feature helps maintain context and continuity in your discussions.

The Save options let you preserve message content in different ways:

* Play: play the message content using text-to-speech

* Copy: copy the content of the message to your clipboard

* Add to prompt: paste the content of the message into the new message input text area

* Save as File creates a downloadable copy of the message

* Save as Memory stores the content in your workspace's memory system

* Save as Document adds the message to your document collections

* Delete: removes messages you no longer need, helping keep your conversations organized and relevant. When you delete a message, the system will ask for confirmation to prevent accidental removals.

* Report: allows you to flag messages that need attention or review, helping maintain quality and address any issues or concerns.

* See extra: extra information attached to the message

You can also select and copy part of the message content. If the message contains code snippets, you can copy only the code blocks, by clicking the "copy" button at the top of the block.

### Composing Messages

At the bottom of the chat window, you'll find a versatile message composition field. Alongside text input, you can easily attach files, mention users, chats and virtual assistant using the `@` key, and activate the voice message functionality to dictate messages instead of typing.

#### Attachments

Near the text input field, you'll find tools for enriching your messages with additional content. These tools help you:

Share files and documents relevant to your discussion. The system handles various file types appropriately, showing previews when possible and ensuring secure transmission of your content.

Tip:

IMPORTANT: when you attach a file you also have the options to add them as documents

## Current Chat Context Panel (Right Sidebar)

The Right Context Sidebar serves as your companion in PrimeThink, adapting its contents based on the selected capabilities of the chat and the default virtual assistant. Every configuration, option, or feature you see in the sidebar is contextualized to the current chat context and the default virtual assistant.

## Adapting to Different Chat Types

The sidebar's appearance and functionality change depending on the type of chat you're currently engaged in. Let's explore how it adapts to different scenarios.

### Default Virtual Assistant

TODO explain what it is in the context of single of multi user chat.

## Working with the Sidebar Tools

Understanding how to leverage the sidebar's context-specific tools can significantly improve your workflow. The sidebar is composed of multiple tabs, each with their own set of tools and information.

### Info tab

The Info tab provides essential information and configuration about the current chat.

## Top Controls

### Search Scope Buttons

A row of segmented buttons at the top of the panel that control the search scope:

* Search In Chat: Also search to the current chat history and summary (chat icon)

* Search In Workspace: Available when in a workspace, searches across workspace chats (workspace icon)

* Search In Documents: Available with RAG capability, searches through associated documents (document icon)

* Search In Collections: Available with RAG capability, searches within collections (list icon)

* Global Memory: Available with Memory capability, accesses theglobal memory of the user (memory chip icon)

These buttons can be:

* Selected multiple at once for broader searches

* All deselected if needed

* Dynamically shown/hidden based on available capabilities

### Favorite Toggle

* Star icon located in the top-right corner

* Allows marking the current chat as a favorite

* Helps quickly access important conversations

## Key Sections and Their Functions

### Name

* Primary identifier for your current chat session (shown in the chats list)

* Editable via the pencil icon

* Helps organize and quickly identify different conversations

### Workspace

* Organizational structure for grouping related chats

* Can be empty (shows as "no workspace")

* Customizable using the edit icon

* Helps maintain clear separation between different projects or topics

### Select Default Virtual Assistant

* Displays your currently selected default virtual assistant

* Available AI capabilities depends on the ddefault virtual assistant

* Editable via the pencil icon

* If selected, every message without a specific mention will be replied by the default virtual assistant

### Summary

* Provides a concise overview of the chat's context and purpose

* Automatically updates based on conversation content

* Expandable via "More" link (if too long)

* Helps maintain context across long conversations

### Goal

* Dedicated space for defining chat objectives

* Helps maintain focus and direction for the default virtual assistant

* Can be updated as the conversation evolves

* Useful for tracking progress and outcomes

### Memo

* Free-form space for additional notes, mainly used by the default virtual assistant

* Can be updated by the default virtual assistant throughout the conversation

* Helps maintain important context

### Mention Name

* Specify names or term that can be used to reference the current chat in other chats (mention, accessed by typing the character `@`)

### Capabilities

* Controls the feature set available to your virtual assistant

* Default capabilities apply when none are specifically selected

* Includes multiple toggleable features:

#### Available Capability Toggles:

1. Base: Core assistant functionalities

2. Memory: Enhanced context retention

3. Multimodality: Support for various types of input/output

4. Goal: Objective-tracking features

5. Memo: Note-taking capabilities

6. RAG: Retrieval-Augmented Generation features

7. Scheduled tasks: Time-based action management

8. Subchats: Nested conversation support

## Documents Tab

### Documents Tab Actions Bar

* Upload: Attach files directly from your device (paper clip icon)

* Paste: Insert a text or a url to scrape (clipboard icon)

* Refresh: Update the document list (circular arrow icon): useful to check the progress of indexing the newly uploaded files

### Document List

The list of documents associated with the current chat is displayed here. Each document is listed with its filename and the file type is indicated by both icon and extension. Multiple file types are supported including .md, .pdf, .docx, and text files.

Each document in the list has three action buttons:

* Preview: View the document content

* Download: Save the document to your device (down arrow icon)

* Status: Status of the indexing of the document

* Delete: Remove the document (trash bin icon)

## Collections Tab

### Collections Tab Actions Bar

* Associate Collection: Allows users to link existing collections to the current chat

### Collection List

Lists all collections added to the current chat.
Each collection entry has a Delete button to remove collection association (trash bin icon).

### Collection Types

Collections can be:

* Public collections: they are setup by the group admin and can be seen by everybody in the group.

* Private collections: they are create by each user and are only visible to them.

## Members Tab

The Members tab provides a list of all participants in the current chat. They can be other users or virtual assistants.

### Members Tab Actions Bar

* Add Members: Invite new members or virtual assistants to the chat.

Each member entry shows:

* Profile picture or avatar initials in a circle

* Name

* Remove button: removes a member from conversation (trash bin icon)

### Member Types

Members can be:

* Human users (shown with initials or profile pictures)

* AI Assistants (shown with specific assistant avatars)

* System users

* Guest users

## Scheduled Jobs Tab

The scheduled jobs tab provides a list of all scheduled tasks associated with the current chat.

Each scheduled job shows:

* Schedule information (e.g., "Run every day at 10:00 AM")

* Task description (e.g., "search the web and update on AI news related to Langchain")

* Job status and actions

### Top Actions Bar

* Add a Scheduled Job: Allows creation of new scheduled tasks (plus icon)

### Job Actions

Each job has three action buttons:

* Edit: Modify job settings (pencil icon)

* Pause/Resume: Toggle job status (pause icon)

* Delete: Remove the scheduled job (trash bin icon)

### Job Properties

Jobs include:

* Timing (daily, weekly, monthly, etc.)

* Execution time

* Task description

* Status (active/paused)

### Scheduling Tips

* Set clear, specific task descriptions

* Consider timezone differences

* Avoid scheduling too many concurrent jobs

* Set appropriate intervals for tasks

* Review job execution history regularly

### Job Management

* Schedule new jobs with "Add a scheduled job"

* Pause jobs temporarily when needed

* Edit jobs to update timing or tasks

* Remove unnecessary jobs to maintain clarity

## Working with the Interface

Understanding how these sections work together will help you make the most of the platform:

### Navigation Flow

The interface follows a natural hierarchy that makes navigation intuitive:

1. First, select your organization using the left icon bar

2. Then, choose your workspace context from the top navigation

3. Use the left sidebar to manage your chats and tasks

4. Conduct your primary work in the central chat window

5. Access context-specific tools through the right sidebar

### Keyboard Shortcuts

As you become more familiar with the interface, you can use keyboard shortcuts to speed up common actions. These shortcuts complement the visual interface while providing quick access to frequently used features. You can view available shortcuts through the help center.

### Adaptive Features

The interface intelligently adapts to your current task, showing relevant tools and information when you need them. This contextual awareness helps maintain a clean, focused interface while ensuring all necessary tools are readily available.

## Getting Started

If you're new to the platform, we recommend starting with the basics:

1. Familiarize yourself with the top navigation bar and its various tools

2. Practice switching between different organizations using the left icon bar

3. Explore the chat creation and management features in the left sidebar

4. Try out the various utility controls in the main chat window

5. Discover how the right sidebar adapts to different types of work

The help center, accessible through the help icon in the top navigation bar, provides additional guidance, tutorials, and best practices to help you make the most of these powerful tools.



# Documents and Collections in Chats

* [Document Management](#document-management)

* [Document Status Types](#document-status-types)

* [Collections](#collections)

* [Managing Document Visibility]()

* [Understanding RAG (Retrieval Augmented Generation)]()

* [Understanding CAG (Context Augmented Generation)]()

* [Best Practices]()

## Document Management

PrimeThink offers robust document handling capabilities within chats, allowing you to share and work with various types of content. You can upload and manage:

* Document files (PDF, Word, Excel, PowerPoint, etc.)

* Plain text (pasted directly into the chat)

* URLs (which will be automatically scraped for their content)

* Audio notes (which will be transcribed into text)

All uploaded documents are processed and indexed to make their content available to virtual assistants within the chat. This enables AI assistants to reference, analyze, and utilize document content in their responses.

## Document Status Types

Each document in PrimeThink has two important status indicators that control how it's processed and used:

### 1. Processing Status

This status indicates where the document is in the processing pipeline:

* Added - The file has been uploaded to the system

* Loaded - The text has been successfully extracted from the document

* Processed - The document text has been chunked into manageable sections

* Ready - The text has been fully indexed and is available for use

* Error - An issue occurred during one of the processing stages

### 2. Access Status

This status determines how virtual assistants can access and use the document:

* Archived - The document is only listed in the context, and the virtual assistant will need to explicitly use a tool to read it. This is useful for agentic retrieval where you want the assistant to intentionally access the document.

* Search - The system will automatically search the document based on the user's query and include relevant parts in the context. This enables Retrieval Augmented Generation (RAG), where the system intelligently fetches relevant document sections.

* Context - The system will place the entire document text (if it fits) directly in the context. This enables Context Augmented Generation (CAG), providing the assistant with the complete document content.

You can mix these status types across different documents in the same chat, creating a flexible environment where some documents are fully available while others require explicit retrieval.

## Collections

In addition to individual documents, PrimeThink allows you to associate entire document collections with a chat. Collections are organized sets of documents that can be:

* Public collections - Set up by the group administrator and visible to everyone in the group

* Private collections - Created by individual users and only visible to them

When you associate a collection with a chat, all documents within that collection become available to the virtual assistants in that chat, according to their respective accessibility status settings.

## Managing Document Visibility in Chats

PrimeThink's document system offers flexible options for controlling how information is accessed within chats. When uploading documents, text, URLs, or audio recordings (which get transcribed), you can choose how virtual assistants interact with this content by selecting the appropriate access status. This allows you to create an optimal knowledge environment for your specific use case.
The system offers three primary access options:

* Archived - Documents are listed in the context but require explicit tool use by the virtual assistant to access. Ideal for specific document lookup scenarios where you want intentional retrieval.

* Search - The system automatically searches documents based on user queries and includes relevant portions in the context (RAG approach). Perfect for large reference documents where only certain sections may be relevant.

* Context - The entire document text is placed directly in the context window if size permits (CAG approach). Best for smaller, highly relevant documents that should be considered in their entirety.

Read more about [Managing Document Visibility](managing-document-visibility.html) to learn more about these options and how they impact optimal RAG and CAG performance.



# Managing Document Visibility

For certain workflows, you might want to attach documents that are visible to the virtual assistant but not to the users in the chat. This is particularly useful for:

* Providing background information to the assistant

* Including reference materials that don't need to clutter the user interface

* Setting up knowledge bases that work behind the scenes

This functionality is available in task configurations, where you can set the "hidden" flag for documents while still specifying their accessibility status.

## Understanding RAG (Retrieval Augmented Generation)

RAG (Retrieval Augmented Generation) is a powerful approach that enhances AI language models by connecting them to external knowledge sources. In PrimeThink, RAG is implemented through the document system with the "Search" accessibility status.

### How RAG Works in PrimeThink

1. Indexing Phase

* When you upload a document and set its accessibility status to "Search," PrimeThink processes and indexes the document

* The system breaks down the document into meaningful chunks

* Each chunk is converted into a vector representation (embedding) that captures its semantic meaning

* These embeddings are stored in a vector database for efficient retrieval

2. Retrieval Phase

* When a user asks a question or makes a request in the chat

* The system converts the query into the same vector space as the document chunks

* It performs a similarity search to find the most relevant document sections

* The most relevant chunks are retrieved based on semantic similarity, not just keyword matching

3. Generation Phase

* The retrieved document chunks are added to the context window for the AI assistant

* The assistant uses both its training knowledge and the retrieved information to generate a response

* This allows for more accurate, up-to-date, and contextually relevant answers

### Benefits of RAG in PrimeThink

* Access to Specialized Knowledge: RAG enables virtual assistants to leverage specific information from your documents that wouldn't be in their general training data.

* Reduced Hallucinations: By grounding responses in retrieved document content, RAG significantly reduces the likelihood of AI assistants generating incorrect information.

* Customized Responses: The system provides answers that are specific to your organization's knowledge, policies, or domain expertise.

* Transparency: Responses can reference specific sources from your documents, making information more traceable and verifiable.

* Efficiency: Only the most relevant parts of documents are used, rather than overwhelming the AI with entire document contents.

### Understanding CAG (Context Augmented Generation)

CAG (Context Augmented Generation) represents another approach to working with documents in PrimeThink, implemented through the "Context" access status. Unlike RAG, which retrieves only relevant portions of documents, CAG provides the entire document content to the AI assistant.

#### How CAG Works

1. When a document's access status is set to "Context," the entire document (if it fits within the context window) is directly provided to the AI assistant.

2. This approach effectively "augments" the model's context with the complete document, making all information readily available without a retrieval step.

3. The AI assistant can then reference, analyze, and utilize any part of the document without needing to explicitly request specific sections.

#### When to Use CAG

CAG is particularly effective for:

* Smaller documents that need to be analyzed in their entirety

* Situations where the whole document provides important context

* Cases where you want to ensure nothing is missed through the retrieval process

* Documents where the relationships between different sections are important

While CAG provides comprehensive access to document content, it's limited by the AI's context window size. For larger documents, RAG often provides a more efficient approach by retrieving only the most relevant sections.

### RAG vs. Other Document Access Methods

| Feature |Archived (Agentic) |Search (RAG) |Context (CAG) |
------------------------------------------------------------
| AI access method |Explicit tool use |Automatic retrieval |Always available |
| Document size limitation |None |None |Limited by context window |
| Precision |High (targeted retrieval) |Medium-High (semantic search) |Low (entire document) |
| Use case |Specific document lookup |General knowledge queries |Full document analysis |
| Context window usage |Efficient |Efficient |Can be inefficient |

### Practical Applications

RAG is particularly effective for:

* Knowledge Bases: Making company policies, procedures, and FAQ documents accessible to virtual assistants

* Legal Document Analysis: Retrieving relevant precedents or clauses from large legal corpora

* Research Support: Pulling relevant information from academic papers or reports

* Customer Support: Finding accurate product information from technical documentation

## Best Practices

* Use Archived status when you want assistants to specifically reference documents only when needed

* Use Search status for large reference documents where only portions may be relevant to any given query

* Use Context status for smaller, highly relevant documents that should be fully considered

* Regularly review and update document collections to ensure assistants have access to the most current information

* Consider using a mix of status types to create the optimal knowledge environment for your specific use case

* For optimal RAG performance, ensure documents are well-structured with clear headings and concise sections

* Test different chunk sizes and overlap settings if you have access to advanced RAG configuration options



# Group Management

## What Is a Group?

A group in PrimeThink represents a distinct collaborative space with its own set of resources and participants. When you join a group, you become part of a self-contained workspace that includes:

* Your team members and their specific roles within the group

* All conversations and chat histories related to that group's activities

* Documents and resources shared within the group

* Specific settings and preferences for that particular collaborative space

* AI assistants configured for the group's needs

This separation ensures that work, conversations, and resources from one group remain separate from others, maintaining privacy and organizational clarity.

For example, if you're working with multiple teams or clients, each one can have its own group, ensuring that discussions and resources stay properly organized and confidential.

## Group Switcher

For a more detailed description of the group switch, please refer to [User Interface Guide: Deep Dive](user-interface.html).

## Group creation and configuration

TODO

## User roles and permissions (Group Admin)

TODO

## Group settings and customization



# Best Practices for Group Management

## Setting Up Groups Effectively

When you're creating or joining new groups, consider these strategies for optimal organization:

Take time to set up your group's visual identity. A distinctive icon helps you quickly identify the right workspace, reducing the chance of context-switching errors. Your group might use a team logo or a meaningful symbol that represents the group's purpose.

Choose clear, descriptive names for your groups. Instead of generic names like "Team 1," use names that reflect the group's purpose or project, making it easier for members to navigate between different collaborative spaces.

## Daily Workflow Optimization

Working across multiple groups requires thoughtful management of your attention and time. Here's how to maintain efficiency:

Begin your day by reviewing notifications across all your groups, but then try to focus on one group at a time. This approach helps maintain context and reduces the mental effort of constant switching.

Consider dedicating specific time blocks to different groups. For instance, you might focus on client-related groups in the morning and internal team groups in the afternoon. This structured approach helps maintain clear boundaries and improves productivity.

## Team Coordination

When collaborating with teams across multiple groups, clear communication becomes essential:

Always verify your current group before sending messages or sharing documents. The Group Switcher's visual indicators help prevent accidental sharing of information across different groups.

Customize your notification preferences for each group. You might want immediate alerts from your primary group but batched updates from others to manage information flow more effectively (coming soon).



# Agents

AI agents are intelligent virtual assistants within the PrimeThink platform that enhance productivity and streamline workflows. These specialized digital entities can perform a wide range of tasks, from answering questions and retrieving information to executing complex workflows and integrating with external systems.

PrimeThink's agent architecture allows for seamless collaboration between human users and AI assistants, creating a powerful ecosystem where routine tasks can be delegated while maintaining human oversight on critical decisions. Whether you're looking to automate customer support, enhance team collaboration, or create specialized assistants for specific domains, PrimeThink's agent capabilities provide the foundation for intelligent automation.

This section explores how to effectively work with AI agents in PrimeThink, including their configuration, capabilities, and best practices for integration into your workflows.

Topics:

* Available AI agents and their capabilities

* Adding AI agents to channels

* Agent permissions and access levels

* Agent commands and syntax

* Best practices for agent interaction



# What is an Agent?

In PrimeThink, an agent is an intelligent virtual assistant powered by Large Language Models (LLMs) that can perform a wide range of tasks to enhance productivity and streamline workflows. Think of agents as specialized "mini-brains" that can be trained for specific purposes.

## Core Characteristics of PrimeThink Agents

### Intelligence and Adaptability

PrimeThink agents leverage advanced LLM technology to understand natural language, learn from interactions, and adapt to user needs. Unlike traditional chatbots, agents can reason through complex problems, understand context, use tools and APIs to gather information or perform actions, and provide nuanced responses.

### Specialization

Each agent can be customized for specific domains or functions. Whether you need assistance with data analysis, content creation, customer support, or technical tasks, agents can be configured with the appropriate knowledge, tools, and capabilities to excel in their designated role.

### Memory and Context Awareness

Agents in chats maintain both short-term and long-term memory, allowing them to:

* Remember previous conversations and user preferences

* Recall relevant information from past interactions

* Apply contextual understanding to current tasks

* Build a personalized experience over time

Tip:

See [Memory](memory.html) for more details.

### Tool Integration

Agents can connect to and utilize various tools and systems, enabling them to:

* Access and process information from different sources

* Interact with external applications and APIs

* Execute actions on behalf of users (with appropriate permissions)

* Automate complex workflows across multiple systems

### Collaboration Capabilities

PrimeThink agents can work together as a team, sharing information when appropriate while maintaining security boundaries. This collaborative approach allows for:

* Seamless handoffs between specialized agents

* Coordinated problem-solving across domains

* Efficient distribution of complex tasks

* Improved decision-making and resource utilization

* More accurate and reliable information

## Types of Agents in PrimeThink

### General-Purpose Agents

These versatile assistants can handle a wide range of queries and tasks, serving as primary interfaces for users. They excel at conversation, information retrieval, and coordinating with specialized agents when necessary.

### Domain-Specific Agents

These agents possess deep expertise in particular fields such as:

* Data analysis and visualization

* Content creation and editing

* Research and information synthesis

* Technical support and troubleshooting

* Project management and coordination

### Function-Specific Agents

These agents focus on executing particular types of tasks:

* Document processing agents

* Scheduling and calendar management agents

* Email and communication agents

* Learning and educational support agents

* Customer service agents

### System Agents

These agents work behind the scenes to maintain and optimize the PrimeThink environment:

* Security and access control agents

* Resource management agents

* Monitoring and analytics agents

* Integration and workflow agents

## How Agents Differ from Traditional Software

Unlike conventional software applications that require specific commands and inputs, PrimeThink agents:

* Understand natural language instructions

* Infer user intent even from incomplete information

* Learn and improve from interactions over time

* Adapt their responses based on context and user preferences

* Provide explanations for their reasoning and recommendations

## Security and Privacy Considerations

PrimeThink agents are designed with robust security and privacy protections:

* Each agent operates within defined permission boundaries

* Access to sensitive information can be strictly controlled

* Interactions can be logged and audited for compliance purposes

* Data handling follows privacy best practices and regulations

## Getting Started with Agents

To begin working with PrimeThink agents:

1. Explore the [Agents Library](agents-library.html) to discover available pre-configured agents

2. Learn how to [customize agent capabilities](working-with-ai-agents.html) for your specific needs

3. Understand best practices for [agent interaction and collaboration](collaboration.html)

4. Review [security settings and permissions](security-and-privacy.html) to ensure appropriate access controls

By leveraging PrimeThink's agent capabilities, you can create a powerful ecosystem of AI assistants that enhance productivity, automate routine tasks, and provide intelligent support across all aspects of your work and personal life.



# What is a Large Language Model (LLM)?

A large language model is an advanced artificial intelligence system designed to understand, analyze, and generate human-like text[1][5]. These models are trained on massive datasets of text, often containing petabytes of information, which enables them to recognize patterns and relationships in language.

LLMs use deep learning techniques and transformer architecture to process and generate text. They work by predicting the next word in a sequence based on the context of previous words, allowing them to produce coherent and contextually relevant responses.

## LLMs as Workflow Engines

LLMs serve as powerful engines for automating and optimizing various business tasks and workflows in several ways:

Task Automation

* Generate high-quality content and documentation

* Process and analyze large volumes of data

* Provide customer support through chatbots

* Assist with code generation and review

Workflow Enhancement

* Streamline content creation and data analysis processes

* Improve decision-making through data-driven insights

* Reduce manual workload on employees

* Enable scalable operations across different departments

Integration Benefits

* Increased efficiency through automation of repetitive tasks

* Enhanced productivity by freeing up employees for strategic work

* Better decision-making through data-driven insights

* Significant cost savings through process optimization

## Leading LLM Providers and Their Strengths

Here are the top LLM providers and their key strengths:

| Provider |Key Strengths |
---------------------------
| OpenAI |Excellent language generation, wide developer support, flexible pricing |
| Anthropic |Versatile capabilities, reliable performance, strong in summarization and analysis |
| Google (Gemini) |Advanced reasoning capabilities, strong performance in complex tasks |
| Mistral AI |Strong multilingual capabilities, excellent reasoning and math performance, 32K context window |
| DeepSeek |Superior reasoning capabilities, cost-efficient training, open-source availability |
| Groq |Ultra-fast inference speeds (300+ tokens/sec), custom LPU hardware, cost-effective scaling |
| Cohere |Highly customizable solutions, developer-friendly APIs |
| Hugging Face |Extensive open-source community, wide selection of pre-trained models |
| Microsoft Azure |Secure enterprise solutions, strong integration with cloud services |

## Multimodal Large Language Models (MLLMs)

Multimodal Large Language Models (MLLMs) represent a significant advancement in artificial intelligence by combining the ability to process and understand multiple types of data simultaneously - including text, images, video, and audio. Unlike traditional LLMs that only handle text, MLLMs create a unified framework that enables more sophisticated understanding and generation of content across different modalities.

### Key Capabilities

Data Integration
MLLMs excel at processing diverse inputs through sophisticated algorithms that extract and combine features from multiple sources. They employ specialized neural networks for each modality - using CNNs for images, RNNs for audio, and advanced NLP techniques for text processing.

Applications

* Visual dialogue and explanation

* Image captioning and classification

* Math equation processing

* Optical character recognition (OCR)

* Cross-modal information transfer



# Supported LLM Providers and Models

Our platform integrates with multiple Large Language Model (LLM) providers to offer you flexibility and choice in selecting the most suitable AI models for your needs. Each provider offers unique capabilities and model variations, allowing you to leverage state-of-the-art AI technology through a unified interface. This document outlines the currently supported providers, their available models, and the requirements for using them.

| Provider |Prefix |Required API Key |Available Models |Model names URL |
-------------------------------------------------------------------------
| Google |`google_genai` |`GOOGLE_API_KEY` |gemini-2.5-pro-preview-03-25, gemini-2.5-flash-preview-04-17, gemini-2.0-flash, gemini-2.0-flash-lite |[https://ai.google.dev/gemini-api/docs/models](https://ai.google.dev/gemini-api/docs/models) |
| Anthropic |`anthropic` |`ANTHROPIC_API_KEY` |claude-3-7-sonnet-latest, claude-3-5-haiku-latest, claude-3-5-sonnet-latest |[https://docs.anthropic.com/en/docs/about-claude/models/all-models](https://docs.anthropic.com/en/docs/about-claude/models/all-models) |
| OpenAI |`openai` |`OPENAI_API_KEY` |gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, 04-mini, o3, o3-mini |[https://platform.openai.com/docs/models](https://platform.openai.com/docs/models) |
| Mistral AI |`mistralai` |`MISTRAL_API_KEY` |codestral-latest, mistral-large-latest, mistral-small-latest, open-mistral-nemo |[https://docs.mistral.ai/getting-started/models/models_overview/](https://docs.mistral.ai/getting-started/models/models_overview/) |
| Groq |`groq` |`GROQ_API_KEY` |gemma2-9b-it, llama-3.3-70b-versatile, llama-3.1-8b-instant, llama-guard-3-8b |[https://console.groq.com/docs/models](https://console.groq.com/docs/models) |
| DeepSeek |`deepseek` |`DEEPSEEK_API_KEY` |deepseek-chat, deepseek-reasoner |[https://api-docs.deepseek.com/quick_start/pricing](https://api-docs.deepseek.com/quick_start/pricing) |

## How it works

When using these models in your application:

1. Each model requires its corresponding API key to be set in your user settings

2. The model name must be prefixed with the provider's prefix. Examples:

* Google: `google_genai:gemini-2.0-flash`

* Anthropic: `anthropic-claude-3-7-sonnet-latest`

* OpenAI: `openai:gpt-4.1`

3. The system will automatically:

* Validate the presence of the required API key

* Strip the provider prefix when needed (for providers like Anthropic and Groq)

* Initialize the appropriate client with the correct endpoints and configurations

## Future Updates

We are actively working on expanding our supported providers and models. Future updates will include:

* Additional language model providers

* New model versions as they become available

* Enhanced capabilities and specialized models

* Support for more regional endpoints and deployment options

Please check our documentation regularly for updates on newly supported models and providers.



# Agents Library

## General Agents using different LLM Providers and models

The General Agent is an AI assistant designed to serve as a versatile digital companion, balancing helpfulness with thoughtful interaction across a wide spectrum of user needs. Built with a foundation of respect and solution-oriented responses, this assistant navigates complex topics with measured perspectives while maintaining the self-awareness to acknowledge knowledge limitations when appropriate. It approaches tasks methodically by breaking down complex challenges into manageable steps, explaining its reasoning process when beneficial, and skillfully balancing creative approaches with user specifications.

| Task Name |Description |Import URL |Import Link |
---------------------------------------------------
| Gemini 2.5 Flash |General Agent using Gemini 2.5 Flash |va://7f1275e7-a91b-4c9a-9b1c-d7b6679a0945 |[Import Agent](https://app.primethink.ai/import/agent/7f1275e7-a91b-4c9a-9b1c-d7b6679a0945) |
| Gemini 2.5 Pro |General Agent using Gemini 2.5 Pro |va://34665e41-145d-46f2-9ab0-2660e4e12b4c |[Import Agent](https://app.primethink.ai/import/agent/34665e41-145d-46f2-9ab0-2660e4e12b4c) |
| Gemini 2.0 Flash Lite |General Agent using Gemini 2.0 Flash Lite |va://c5effddf-4864-47e5-bd76-21a1e0b65074 |[Import Agent](https://app.primethink.ai/import/agent/c5effddf-4864-47e5-bd76-21a1e0b65074) |
| Gemini 2.0 Flash |General Agent using Gemini 2.0 Flash |va://0d1cb534-2740-49c7-b434-f2df28f657d9 |[Import Agent](https://app.primethink.ai/import/agent/0d1cb534-2740-49c7-b434-f2df28f657d9) |
| Claude 3.7 |General Agent using Claude 3.7 |va://aa4eb830-a5c8-4fbc-92ee-7f7857a23952 |[Import Agent](https://app.primethink.ai/import/agent/aa4eb830-a5c8-4fbc-92ee-7f7857a23952) |
| OpenAI O3 |General Agent using OpenAI O3 |va://172d7505-6121-420f-8165-c7e3f36b59e8 |[Import Agent](https://app.primethink.ai/import/agent/172d7505-6121-420f-8165-c7e3f36b59e8) |
| OpenAI Gpt 4.1 |General Agent using GPT-4.1 |va://e8f17d55-f4a5-40ac-80fa-b83061331ab1 |[Import Agent](https://app.primethink.ai/import/agent/e8f17d55-f4a5-40ac-80fa-b83061331ab1) |
| OpenAI Gpt 4.1 Mini |General Agent using GPT-4.1 Mini |va://ebfaa6a1-439b-4959-aa78-ff43433a4990 |[Import Agent](https://app.primethink.ai/import/agent/ebfaa6a1-439b-4959-aa78-ff43433a4990) |
| OpenAI Gpt 4.1 Nano |General Agent using GPT-4.1 Nano |va://b3c4ab7b-4269-42cb-aeab-0c6b4db62b49 |[Import Agent](https://app.primethink.ai/import/agent/b3c4ab7b-4269-42cb-aeab-0c6b4db62b49) |
| OpenAI O4 Mini |General Agent using OpenAI O4 Mini |va://a206466f-81f5-4da9-acb7-95e49532e341 |[Import Agent](https://app.primethink.ai/import/agent/a206466f-81f5-4da9-acb7-95e49532e341) |
| Mistral Small |General Agent using Mistral Small |va://d34093d0-ca47-486c-851b-8e304f8b7451 |[Import Agent](https://app.primethink.ai/import/agent/d34093d0-ca47-486c-851b-8e304f8b7451) |
| Groq Llama 3.1 8b instant |General Agent using Groq Llama 3.1 8b instant |va://93183f83-603b-48ed-b226-f983735f97db |[Import Agent](https://app.primethink.ai/import/agent/93183f83-603b-48ed-b226-f983735f97db) |
| DeepSeek Chat |General Agent using DeepSeek Chat |va://bdd8c0bb-d24f-47f4-aa5b-1c491ea6cca7 |[Import Agent](https://app.primethink.ai/import/agent/bdd8c0bb-d24f-47f4-aa5b-1c491ea6cca7) |
| DeepSeek Reasoner |General Agent using DeepSeek Reasoner |va://928e0b51-0825-4141-a332-6854aad80bd7 |[Import Agent](https://app.primethink.ai/import/agent/928e0b51-0825-4141-a332-6854aad80bd7) |



# Working with AI Agents

Topics:

* Understanding AI agent roles

* Agent specializations (data analysis, coding, writing, etc.)

* Agent configuration and customization

* Context awareness and memory



# Capabilities

Start typing here...



# AI Assistant Tools

Welcome to your AI assistant's comprehensive guide! This document will walk you through all the powerful tools available to help you manage your work efficiently and effectively. We'll explore each tool in detail with practical examples and best practices.

## Understanding Your AI Assistant

Your AI assistant comes equipped with a suite of tools designed to help you manage chats, tasks, and documents. Think of these tools as different capabilities that your assistant can use to help you get your work done. Just like you might use different apps for different tasks on your phone, your assistant uses different tools to help you accomplish various goals.

## Chat Organization Tools

### Working with Memos

Memos serve as your chat's digital notepad, helping you keep track of important information that needs to be readily accessible. Think of them as sticky notes that stay at the top of your conversation.

When to Use Memos:

* During meetings to record key decisions

* To track action items and responsibilities

* To maintain a running list of important points

* To keep reference information easily accessible

Real-World Examples:

1. Meeting Notes:

```
"Create a memo with today's meeting points:
- Project timeline reviewed
- Budget approved at $50,000
- Sarah to lead design team
- Next review: March 15th"
```

1. Project Tracking:

```
"Update the memo to add these action items:
- John to complete wireframes by Friday
- Marketing team needs content by next Tuesday
- Client review scheduled for March 20th"
```

1. Resource Collection:

```
"Add to the memo our key project links:
- Design documents: [link]
- Team roster: [link]
- Budget spreadsheet: [link]"
```

### Setting and Managing Goals

Goals help keep your conversations focused and productive. They act as a compass, ensuring everyone knows what they're working toward.

Effective Goal Setting Strategies:

1. Make goals specific and measurable:

```
"Set the chat goal to: Complete Q2 marketing plan with budget allocation and timeline by March 31st"
```

1. Update goals as projects evolve:

```
"Update our goal to: Finalize website redesign mockups and get client approval by next Friday"
```

1. Break down complex goals:

```
"Change the chat goal to: Phase 1 - Research user preferences and create initial design concepts by March 15th"
```

### Managing Subchats

Subchats help you organize complex projects into manageable pieces. Think of them as creating separate rooms for different discussions within your main project space.

Strategic Subchat Organization:

1. Project Phases:

```
"Create these subchats:
- Research & Planning
- Design & Development
- Testing & QA
- Launch Preparation"
```

1. Team-Based Organization:

```
"Set up subchats for:
- Frontend Team (Goal: UI/UX implementation)
- Backend Team (Goal: API development)
- QA Team (Goal: Testing coordination)
Each with an initial prompt asking for their timeline estimate"
```

1. Topic-Based Division:

```
"Create subchats for our rebranding project:
- Logo Design (Goal: Create new company logo)
- Color Scheme (Goal: Develop brand color palette)
- Typography (Goal: Select brand fonts)
- Brand Guidelines (Goal: Document all brand standards)"
```

### Reporting Between Chats

The reporting feature allows information to flow smoothly between related conversations, ensuring everyone stays informed.

Effective Reporting Strategies:

1. Progress Updates:

```
"Report to parent chat: Design team has completed initial mockups. Ready for review. Key features include responsive layout and dark mode support."
```

1. Decision Communication:

```
"Send to parent chat with analysis: Team has selected Azure for cloud hosting based on cost analysis and scaling capabilities."
```

1. Milestone Completion:

```
"Report to main chat: Phase 1 testing complete. 15 bugs identified, 12 resolved, 3 in progress. On track for release date."
```

## Task Management

### Creating and Managing Tasks

Tasks help you automate recurring activities and ensure important work gets done on schedule.

Task Creation Best Practices:

1. Regular Reports:

```
"Create a task named 'Weekly Status Report'
Description: Generate comprehensive project status report
Goal: Maintain clear project visibility
Schedule: Every Monday at 9am
Initial Prompt: Analyze previous week's progress, blockers, and next steps"
```

1. Monitoring Tasks:

```
"Create a task for 'Daily Performance Check'
Description: Monitor system performance metrics
Goal: Identify and flag any performance issues
Schedule: Daily at 6am
Capabilities: [Memory, Analytics]"
```

1. Content Generation:

```
"Set up a task for 'Social Media Content'
Description: Generate social media post ideas
Goal: Create engaging content calendar
Schedule: Every Wednesday and Friday
Initial Prompt: Generate 5 post ideas based on current trends"
```

## SharePoint Integration

### Effective Document Searching

The SharePoint search tool helps you find documents quickly and efficiently across your organization's content.

Search Strategies:

1. Specific Document Types:

```
"Search for:
- Type: PDF files
- Topic: budget proposals
- Location: Finance/2024 Planning
- Max Results: 10"
```

1. Project Documentation:

```
"Find all presentations and documents about:
- Project: Customer Portal
- Time Frame: Last 3 months
- File Types: pptx, docx"
```

1. Targeted Folder Search:

```
"Look in Marketing/Campaigns/2024 for:
- Query: 'social media strategy'
- File Types: xlsx, pdf
- Max Results: 15"
```

## Best Practices for Tool Usage

### Organizing Your Work

Consider these strategies for maintaining an efficient workspace:

1. Hierarchical Organization
Start with a main chat for the overall project, then create subchats for specific workstreams. Use memos in each chat to track relevant information.

2. Information Flow
Establish a regular rhythm of reporting from subchats to parent chats to keep everyone informed of progress.

3. Task Automation
Identify repetitive work that can be automated through scheduled tasks, freeing up time for more strategic activities.

### Common Scenarios and Solutions

Here are some typical situations and how to handle them effectively:

1. Managing a Complex Project

```
- Create main project chat
- Set overall project goal
- Create subchats for each major component
- Set up weekly status report task
- Use memos to track key decisions
- Configure regular subchat reports
```

1. Document Organization

```
- Use specific search terms
- Leverage file type filters
- Search within relevant folders
- Save important documents in chat memos
```

1. Team Coordination

```
- Create team-specific subchats
- Set up regular report scheduling
- Use memos for shared resources
- Configure automated status updates
```

## Getting Help

Remember, you can always ask your AI assistant for help with any tool or feature. Try these approaches:

1. Ask for examples:

```
"Show me an example of creating a task with a schedule"
```

1. Request clarification:

```
"Can you explain how subchat reporting works?"
```

1. Get specific guidance:

```
"What's the best way to organize a product launch using these tools?"
```

Your AI assistant is here to help you make the most of these tools. Don't hesitate to ask questions or request assistance as you work with these features.



# Manage toolkit

## Matter & Time Management Tools

Welcome! This guide will help you make the most of the available tools for managing matters, tracking time, and handling documents.

## Quick Start Guide

Here are some common tasks you might want to perform:

* [Finding Information](#finding-information)

* [Managing Matters](#managing-matters)

* [Time Tracking](#time-tracking)

* [Document Management](#document-management)

## Finding Information

### Searching Across All Matters

Need to find something but not sure which matter it's in? Use the global search:

```
Search for "client name"
Find matters related to "trademark application"
Look up everything about "contract agreement"
```

### Searching Within a Matter

When you know which matter you're interested in:

```
Search in matter 394965 for "Lucas"
Find documents containing "agreement" in matter 394965
Look for "deadline" in this matter
```

## Managing Matters

### Viewing Matter Details

Get comprehensive information about a matter:

```
Show me matter 394965
Get details for matter 394965
What are the details of this matter?
```

You'll see:

* Basic matter information

* Extra data

* Recent events

* Contact information

### Managing Matter Files

Browse and manage files in a matter:

```
Show files in matter 394965
List documents in folder 8860257
What files are in this matter?
```

### Working with Contacts

View contact relationships:

```
Show contacts for matter 394965
Who is involved in this matter?
List all contacts in matter 394965
```

## Time Tracking

### Starting Time Recording

Several ways to start tracking time:

```
Start timer for matter 394965 with description "Client meeting"
Begin time recording for "Document review" on matter 394965
Start tracking time for "Phone call" in this matter
```

### Managing Active Timers

View Active Timers:

```
Show my active timers
What timers are running?
List current timers
```

Pause a Timer:

```
Pause timer 783635
Pause the current timer
```

Resume a Timer:

```
Resume timer 783635
Continue the previous timer
```

Stop and Save:

```
Stop timer 783635
Finish recording time
Complete the current timer
```

### Editing Time Entries

Modify existing time entries:

```
Update timer 783636 to "Updated description" with duration 5 minutes
Edit the description of timer 783636
Change time duration for timer 783636
```

### Looking Up Time Records

View your time entries:

```
Show my time entries for today
List time records from December 7th
What time did I record yesterday?
```

### Charge Categories

Find appropriate billing categories:

```
Show available charge categories
List charge categories for trademark matters
What billing codes can I use?
```

## Document Management

### Saving Information

Save important information for later:

```
Save this search result as "Trademark Search December 2024"
Store these findings as "Client Research"
Save this list as "Important Deadlines"
```

## Tips & Best Practices

1. Time Tracking Best Practices:

* Start timers as soon as you begin work

* Use descriptive entries that clearly indicate the work performed

* Remember to stop or pause timers when switching tasks

* Review your time entries regularly

2. Search Tips:

* Use specific terms for better results

* Try different variations if you don't find what you're looking for

* Use matter-specific search when you know the matter

3. File Management:

* Use clear, descriptive file names

* Organize files in appropriate folders

* Save important search results for future reference

4. Matter Organization:

* Keep matter details up to date

* Regularly review and update contact information

* Save important findings and documents

## Common Questions

Q: How do I find a specific matter?
A: Use the global search with the client name, matter reference, or any relevant keyword.

Q: Can I have multiple timers running?
A: No, only one timer can be active at a time. Remember to pause or stop your current timer before starting a new one.

Q: How do I edit a time entry?
A: You can modify the description, duration, and charge category of any time entry you've created.

Q: Where can I see my active timer?
A: Use the "Show my active timers" command to see any currently running timers.

Q: How do I find the right charge category?
A: Use the charge categories tool to list available categories. They're filtered based on the matter type to show you relevant options.

## Getting Help

If you need assistance:

* Type "help" for general guidance

* Use "help [tool name]" for specific tool information

* Try "examples" to see common usage patterns

Remember, you can always ask for clarification or more specific examples for any of these features!

## Keyboard Shortcuts and Quick Commands

For frequently used operations:

* `start timer` - Begin time recording

* `pause timer` - Pause current timer

* `stop timer` - Complete time recording

* `search` - Global search

* `files` - View matter files

* `save` - Save current information

## Best Practices for Different Roles

### For Attorneys

* Keep detailed time descriptions

* Use appropriate charge categories

* Save important research findings

### For Paralegals

* Track all matter-related activities

* Maintain organized file structures

* Document important communications

### For Administrative Staff

* Update contact information promptly

* Save important correspondence

* Track matter updates



# Tasks

## What Are Tasks?

Tasks are customizable workflows that help you interact with AI assistants to accomplish specific goals. Think of them as specialized mini-applications designed to handle particular types of work consistently and effectively.

## How Tasks Work

1. Starting a Task

* Select a task from the available list

* The assistant will begin with an initial prompt specific to that task

* Follow the assistant's questions and instructions to proceed

2. Task Components

* Name: Identifies the task's purpose

* Description: Brief overview of what the task does

* Goal: Detailed instructions guiding the assistant's behavior

* Initial Prompt: The first message you'll see when starting the task

* Schedule (optional): For tasks that need to run at specific times

## Available Capabilities

Tasks can utilize various capabilities:

* Memory: Retains context throughout conversations

* Multimodality: Handles different types of inputs (text, files, etc.)

* Memo: Stores and tracks information during the task

* Goal: Manages complex objectives and multi-step processes

* RAG: Analyzes documents and provides relevant information

* Subchats: Creates subtasks for complex workflows

* Web Search: Retrieves information from the internet

* Scheduled Tasks: Runs tasks at specified times

## Common Task Types

1. Document Analysis

* Upload documents for review

* Get structured analysis and insights

* Compare multiple documents

2. Information Processing

* Organize and summarize information

* Extract key details

* Generate reports

3. Scheduled Operations

* Set up recurring tasks

* Get regular updates or reports

* Automate routine processes

## Best Practices

1. Clear Communication

* Provide specific information when requested

* Ask for clarification if unsure

* Review summaries when provided

2. Document Handling

* Ensure documents are in readable formats

* Provide context about uploaded files

* Verify all required documents are included

3. Task Selection

* Choose the most specific task for your needs

* Read the task description carefully

* Use scheduled tasks for recurring needs

## Creating Custom Tasks

Need a specific workflow? You can create custom tasks:

1. Describe your needed workflow

2. Answer questions about specific requirements

3. Review and confirm the task setup

4. Start using your custom task

## Getting Help

* Review task descriptions for specific guidance

* Ask the assistant for clarification at any point

* Use the help command for general assistance

## Tips for Success

* Be specific in your requests

* Provide all relevant information upfront

* Follow the assistant's structured approach

* Review generated outputs carefully

* Save important information when suggested

Remember: Tasks are designed to make complex workflows simpler and more consistent. They work best when you follow the established process and provide clear information.



# Task Library

## Document Processing

| Task Name |Description |Import URL |Import Link |
---------------------------------------------------
| 📑 Summarise Documents |Immediately summarise any document sent by the user as an attachment |`task://b87d9da2-aa3c-4972-87a5-60862bc8954b` |[Import Task](https://app.primethink.ai/import/task/b87d9da2-aa3c-4972-87a5-60862bc8954b) |
| 📌 Extract Keypoints from Documents |Immediately summarise and extract keypoints from any document sent by the user as an attachment |`task://e5030fe7-11b5-48e0-8627-bce922f04ec7` |[Import Task](https://app.primethink.ai/import/task/e5030fe7-11b5-48e0-8627-bce922f04ec7) |
| ✏️ Document Proofreader |Comprehensive document proofreading and editing assistant |`task://7cffb129-5218-492c-b5c6-cee519790bd7` |[Import Task](https://app.primethink.ai/import/task/7cffb129-5218-492c-b5c6-cee519790bd7) |

## Toolkits / Integration

| Task Name |Description |Import URL |Import Link |
---------------------------------------------------
| 🛠️ Obviously Manage Toolkit |Tool for managing various tasks and utilities |`task://fc656be0-b551-4a36-873a-f3e419ef5a97` |[Import Task](https://app.primethink.ai/import/task/fc656be0-b551-4a36-873a-f3e419ef5a97) |**** |

## Communication Tools

| Task Name |Description |Import URL |Import Link |
---------------------------------------------------
| 📝 Style Review - DEMO |Correcting Letters and Contracts to Maintain a Specific Style |`task://e90412e4-8dae-4fcf-a8f6-91bebaf77364` |[Import Task](https://app.primethink.ai/import/task/e90412e4-8dae-4fcf-a8f6-91bebaf77364) |

## Document Analysis

| Task Name |Description |Import URL |Import Link |
---------------------------------------------------
| 🔍 Due Diligence Document Analyzer - DEMO |Analyze documents for due diligence processes |`task://210a4abb-00b9-40de-b907-2d7b92e5c816` |[Import Task](https://app.primethink.ai/import/task/210a4abb-00b9-40de-b907-2d7b92e5c816) |
| ⚖️ Legal Document Summarizer - DEMO |Create comprehensive summaries of legal documents |`task://7764d708-7f9d-45dc-a335-cf0b33e868b5` |[Import Task](https://app.primethink.ai/import/task/7764d708-7f9d-45dc-a335-cf0b33e868b5) |
| 📊 Settlement Terms Analysis - DEMO |Analyze key settlement terms across multiple agreements to identify best practices, manage risks, and enhance negotiating positions |`task://46f19dea-f6c6-4824-a29d-34c264f37f35` |[Import Task](https://app.primethink.ai/import/task/46f19dea-f6c6-4824-a29d-34c264f37f35) |

## Utility Tools

| Task Name |Description |Import URL |Import Link |
---------------------------------------------------
| 📊 Data Collection  Example - DEMO |Collect user information |`task://327f5ba9-eb27-40b0-9458-3a3196d3de15` |[Import Task](https://app.primethink.ai/import/task/327f5ba9-eb27-40b0-9458-3a3196d3de15) |
| ✅ Task Creator Assistant - BETA |Guide users through creating custom tasks through simple questions |`task://06b7fd92-ebbc-4fca-85ce-3565645d7ca0` |[Import Task](https://app.primethink.ai/import/task/06b7fd92-ebbc-4fca-85ce-3565645d7ca0) |



# Memory

It's important to understand that the memory system detailed here primarily enhances standard chats between you and AI assistants. To maintain privacy and appropriate context, these memory features are typically not active within multi-user group chats that include several human participants.

PrimeThink features a sophisticated memory system, similar to human memory, allowing the platform and its AI assistants to recall important information from past interactions and apply it effectively in future conversations during your standard chats. This ensures continuity, personalisation, and adherence to specific guidelines within those one-on-one or AI-only interactions.

The memory system organises information into different types, each serving a distinct purpose and having a specific priority level when accessed by the AI. When you interact with an assistant in a standard chat, the system searches for relevant memories based on your query and adds them to the AI's context.

## Understanding Memory Types

We can think about agent memory in terms of its duration and purpose:

### Working Memory (Temporary - During a response)

* Purpose: This is akin to having a piece of scratch paper when working through a problem or completing multiple tasks simultaneously during a single response. It stores temporary, incremental states or outputs generated during a single task or a sequence of chained tasks within an agent's workflow.

* How it Works: It holds intermediate results needed to compute the next step or the final outcome of an instruction. Once the response is sent, this memory is typically discarded.

### Short-Term Memory (Session-Specific - During a Conversation)

* Purpose: This refers to the context of an existing session or conversation the agent(s) are currently part of. It holds information that is relevant to that specific conversation.

* How it Works: The system needs to recall this context very rapidly to maintain conversational flow and provide relevant, accurate responses within the ongoing interaction. It allows the agent to remember previous prompts and its own answers within the current chat.

* Implementation: It uses a mix of chat summary, chat history, and Retrieval Augmented Generation (RAG) to find relevant messages (older than the history) in the same chat.

### Long-Term Memory (Persistent - Across Sessions)

* Purpose: This holds information that should be remembered for future interactions, even after the current session has concluded. It stores distilled knowledge, key attributes, and summaries from past experiences that inform the agent's behaviour over time.

* How it Works: Creating long-term memory from working or short-term memory requires analysis to extract the key points or important attributes. The extract information is then stored as a memory. Relevant memories can then be supplied to the agent at the commencement of a new session or task to provide essential context or guidance.

* Implementation: This is a dedicated system. It can be used to store various types of persistent information: * Constitutional Memories (Highest Priority - MUST be enforced): Fundamental rules, guidelines, and core preferences that shape the AI's fundamental behaviour and responses (unbreakable laws or directives). Examples: Preferred language, specific response formats, core ethical guidelines, naming conventions the AI must use. * User Personal Memories (High Priority - Should be enforced): User-specific information like preferences, characteristics, history, relationships, likes/dislikes, habits, and other personal circumstances provided by the user. Used to personalise interactions and maintain conversational continuity regarding the user's life. * AI Personal Memories (Medium Priority - Should be referenced for consistency): Define the AI's persona or background characteristics as instructed by the user (distinct from constitutional rules governing behaviour). Examples: A user-defined name, age, origin story, or personality traits for the AI assistant. Helps the AI maintain a consistent identity. * Other Important Memories (General Information - Can be referenced): Stores explicitly saved pieces of information that don't fall into the above categories. This includes facts the user specifically asked the AI to remember, generic information to retain, or details about people other than the primary user. Used for factual recall without interpretation.

## Memory Priority and Enforcement

The system follows specific steps to ensure memories are used correctly, particularly for stored long-term memories:

1. Check: Before forming a response, relevant constitutional memories are checked.

2. Validate: The potential response is validated against these constitutional rules.

3. Allow: The response is only sent if it satisfies all applicable constitutional rules.

Conflicts: When multiple relevant memories exist (excluding Constitutional, which always takes precedence), recency and relevance score matter. More recent memories and those with a lower score (indicating higher similarity/relevance to the query) are prioritised.

Beyond these types, systems with multiple agents also require memory management. Agents need to communicate with one another using mechanisms such as a shared session (potentially utilising short-term memory concepts). However, individual agents might also maintain their own session-specific information depending on the nature of the tasks they are undertaking. One might also opt to employ certain data privacy protocols to confine specific data within a particular assistant (e.g., keeping a credit card number within one agent) and then transmit only the de-identified information to subsequent tasks and assistants instead.

## Viewing and Managing Memories

You can view and manage stored long-term memories through the "Memory" section accessible from the top navigation bar (via the "View Memory" button or potentially within the main menu), typically for standard chats.

* Memory List: The main screen displays a list of memories. Each entry typically shows: * The core memory text. * Associated Topic and Keywords (if generated/added). * The date the memory was created or last updated. * A tag indicating the memory type (e.g., "memory", "constitution", etc.). * Action buttons (like edit or delete).

* Search/Filtering: You can search for specific memories (the system uses semantic search to find relevant entries even if your query uses different wording) and potentially filter by type.

* Adding/Editing: Memories can be added explicitly by asking the assistant to remember something specific ("Save as Memory" option on messages) or may be extracted automatically by the system from conversations (depending on configuration). You can typically edit or delete existing memories via the action buttons.

By effectively utilising these different memory types and management tools, AI agents can provide a highly personalised, context-aware, and reliable experience.



# Collections

Start typing here...



# Scheduled Jobs

Start typing here...



# User Management

Topics to implement:

* Adding and removing users

* Role assignment

* Access control

* User profiles and settings



# Collaboration

PrimeThink offers several ways for users within the same [Group (Organization)](group-management.html) to collaborate effectively. Understanding these methods and the associated permissions is key to managing teamwork and information sharing securely. Collaboration primarily occurs through:

* [Direct Messages (DMs)](#direct-messages-dms)

* [Group Conversations (Multi-User Chats)](#group-conversations-multi-user-chats)

* [Shared Workspaces](#shared-workspaces)

These features allow for seamless communication, resource sharing, and task management between individuals and AI assistants.

## Direct Messages (DMs)

Direct Messages are private, one-on-one or small group conversations between specific users within your organization. They are ideal for:

* Quick questions and discussions.

* Sharing information directly with specific individuals.

DMs exist outside the structured context of shared workspaces and offer a simple way to communicate directly.

## Group Conversations (Multi-User Chats)

Group Conversations, often referred to as Multi-User Chats in the [User Interface](user-interface.html#chat-types), are chats involving multiple human users and potentially one or more AI assistants. These are distinct from standard 1-to-1 chats with only AI assistants.

Key Features:

* Participants: Can include multiple users and AI Virtual Assistants ([VAs](agents.html)).

* Mentions: Use `@` to mention specific members or VAs. Use `@here` or `@all` to notify all chat members.

* Collaboration Tools: Members (depending on permissions) can upload files, manage [Collections](collections.html), schedule [Tasks](tasks.html), and more. See [AI Assistant Tools](ai-assistant-tools.html) for chat organization capabilities like Memos and Subchats.

* Memory: Note that in Multi-User chats, the AI's Memory capability is typically disabled by default to protect privacy; the system will not automatically learn from or store user messages in its persistent memory. ([User Interface Reference](user-interface.html#chat-types)).

Permissions in Group Conversations:

Permissions within a Group Conversation are dictated by the type of Workspace it resides in.

### Group Conversation inside a Non-Shared Workspace

* Owner: * Has full control over the chat, including managing its information (name, goal, etc.). * Cannot leave the chat but can delete it entirely for all members.

* Members: * Can move the chat to one of their own workspaces (effectively creating a copy or link, behaviour might need clarification based on implementation). * Can upload/edit files. * Can add/remove [Collections](collections.html). * Can invite/remove other members or AI assistants. * Can schedule [Tasks](tasks.html). * Can send messages and mention others. * Can leave the chat. (Note: When a member leaves, any private AI assistants they added will also be removed).

### 

Group Conversation inside a Shared Workspace (Type: `Owner Only`)

* Owner: * Can change the workspace the chat belongs to. * Manages all chat information. * Cannot leave the chat but can delete it for everyone.

* Non-Owner Members: * Can send messages and mention others. * Can leave the workspace, which removes them from all chats within it. (Private VAs are removed upon leaving).

### 

Group Conversation inside a Shared Workspace (Type: `Shared`)

* All Members: * Have equal permissions within the chat (e.g., sending messages, managing files/collections, scheduling tasks, inviting/removing members unless the member is part of the shared workspace itself). * Cannot change the workspace the chat belongs to (this is fixed).

* Leaving: Members can leave the workspace, losing access to all its chats. (Private VAs are removed upon leaving).

* Removal Restriction: A member cannot remove another user from the chat if that user is also a member of the underlying shared workspace. Removal must happen at the workspace level.

## Shared Workspaces

Workspaces act as containers for chats, documents, collections, and settings. By default, a workspace is private (`Not Shared`). However, you can share workspaces with other members of your Group (Organization) to facilitate collaboration on projects or topics.

The level of collaboration is controlled by the workspace's `share_type`.

Workspace Share Types:

* `Not Shared`: The default state. Only the owner can access and manage.

* `View Only`: Members can see content but cannot interact or modify anything.

* `Owner Only`: The owner retains primary control, but members can actively participate in chats.

* `Shared`: All members have equal permissions, effectively dissolving the concept of a single owner.

UI Indication: Workspaces shared as `View Only`, `Owner Only`, or `Shared` will display a specific icon in the workspace list within the [User Interface](user-interface.html).

### Workspace Sharing Logic

1. Creation: New workspaces start as `Not Shared`.

2. Adding Members: Adding the first member to a `Not Shared` workspace automatically changes its type to `Owner Only`.

3. Reverting to Not Shared: An `Owner Only` workspace reverts to `Not Shared` automatically only if all members (except the owner) are removed.

4. Changing to Shared:

* The owner of an `Owner Only` workspace can manually change it to `Shared`.

* This change is IRREVERSIBLE. Once `Shared`, it cannot be changed back.

* Caution: There is no confirmation prompt before making this irreversible change.

5. Reverting from Shared: A `Shared` workspace cannot be changed back because:

* Ownership is dissolved.

* Any member can remove any other member, including the original creator.

6. Permissions in `Shared`:

* All members possess equal permissions.

* Any member can remove any other member.

7. Self-Removal Restrictions: The owner cannot leave (`remove themselves`) from `Not Shared` or `Owner Only` workspaces. They must first share it or transfer ownership (if supported).

### Permissions by Workspace Share Type

The `share_type` dictates what owners and members can do within the workspace and its contained chats:

* `Not Shared` Workspace: * Owner: Has full control – manage workspace settings (name, prompt), manage documents/collections, add/remove members (which changes the type), create/delete chats, send messages, delete the workspace.

* `View Only` Workspace: * Owner: * Can edit workspace name and system prompt. * Can manage documents (add/remove/change status). * Can manage collections (add/remove). * Can manage members (add/remove). * Can delete the workspace. * Can create new chats within the workspace. * Can send messages and mention users/agents in chats. * Members: * Can only view chat messages and pages. * Cannot perform any actions (including sending messages, editing, adding content, etc.). * Can leave the workspace.

* `Owner Only` Workspace: * Owner: * Can edit workspace name and system prompt. * Can manage documents (add/remove/change status). * Can manage collections (add/remove). * Can manage members (add/remove). * Can delete the workspace. * Can create new chats within the workspace. * Members: * Can send messages and mention users/agents in chats within the workspace. * Can leave the workspace. * Note: While members can interact within chats, they cannot manage the workspace structure itself (e.g., add collections, documents, or members to the workspace). Compare this with permissions inside a chat within a Non-Shared Workspace where members have more autonomy within that specific chat.

* `Shared` Workspace: * All Members (including the original creator): * Have equal permissions. * Can edit workspace name and system prompt. * Can manage documents (add/remove/change status). * Can manage collections (add/remove). * Can manage members (add/remove any other member). * Can delete the workspace. * Can create new chats within the workspace. * Can send messages and mention users/agents in chats. * Can leave the workspace.

## Related Documentation

* [Group Management](group-management.html): Understanding organizational groups.

* [User Interface Guide](user-interface.html): Navigating workspaces, chats, and member lists.

* [Best Practices for Group Management](best-practices-for-group-management.html): Tips for working across multiple organizational groups.

* [AI Assistant Tools](ai-assistant-tools.html): Details on tools used within chats like Memos, Goals, and Subchats.

* [Quick Start](quick-start.html): Information on inviting team members.



# Notifications in PrimeThink

PrimeThink uses a comprehensive notification system to keep you informed about important events, messages, and mentions. This document outlines how notifications work and how you can customize them to fit your preferences.

## How Notifications Are Sent

Notifications in PrimeThink are delivered through multiple channels to ensure you receive timely updates:

* Push Notifications: Sent immediately to your registered devices when a notification is created.

* Realtime updates using Web Sockets: Real-time updates are sent via Web Sockets. This will instantly update badge counters in the application.

* Email Notifications: * For Notifications: The system checks for unread notifications periodically (e.g., every X minutes). If there are unread notifications from the last Y minutes, an email summary is sent. * For Unread Messages: Similarly, the system checks for unread messages (e.g., every X minutes). If there are unread messages from the last Y minutes, an email summary is sent.

## Notification Settings

You have granular control over how you receive notifications. Settings can be configured globally and on a per-chat basis.

### Global Notification Settings

These settings apply to all your activities within PrimeThink unless overridden by chat-specific settings.

* Notifications - Global Push Settings: * `On`: Receive push notifications for all new notifications. (Default) * `Mentions Only`: Receive push notifications only when you are specifically mentioned. * `Off`: Disable all push notifications.

* Notifications - Global Email Settings: * `On`: Receive email notifications for all unread notifications. (Default) * `Mentions Only`: Receive email notifications only for unread notifications where you are specifically mentioned. * `Off`: Disable all email notifications for general notifications.

* Unread messages notifications - Global Email Settings: * `On`: Receive email notifications for all unread messages. (Default) * `Direct Messages Only`: Receive email notifications only for unread direct messages. * `Off`: Disable all email notifications for unread messages.

You can access and modify these settings in your user profile under the "Notifications" section.

### Chat-Specific Notification Settings

For individual chats, you can override the global notification settings. This allows you to fine-tune how you are notified for specific conversations.

These settings are found within each chat's settings menu (`UserInChat` table).

* Notifications - Chat Push Settings: * `Default`: Use the Global Push Settings. (Default) * `On`: Receive push notifications for all new notifications in this chat. * `Mentions Only`: Receive push notifications only when you are specifically mentioned in this chat. * `Off`: Disable all push notifications for this chat.

* Notifications - Chat Email Settings: * `Default`: Use the Global Email Settings. (Default) * `On`: Receive email notifications for all unread notifications in this chat. * `Mentions Only`: Receive email notifications only for unread notifications where you are specifically mentioned in this chat. * `Off`: Disable all email notifications for general notifications in this chat.

* Unread messages notifications - Chat Email Settings: * `Default`: Use the Global Email Settings for unread messages. (Default) * `On`: Receive email notifications for all unread messages in this chat. * `Off`: Disable all email notifications for unread messages in this chat.

By customizing these settings, you can ensure that you stay informed without being overwhelmed by notifications.



# Settings

The Settings section in PrimeThink provides a comprehensive set of tools for personalizing your experience and managing your account. When you access Settings through the gear icon in the main interface, you'll find a well-organized collection of options divided into distinct categories that help you control various aspects of the platform.

## Navigation and Organization

The Settings interface is organized into three main tabs at the top of the screen: User, User Variables, and Group Variables. This thoughtful organization helps you quickly find the settings you need while maintaining a clear separation between personal preferences and group-level configurations.

## User Settings

The User tab contains all your personal account settings and preferences. This section is where you manage your identity and basic interaction preferences within PrimeThink.

### Profile Information

Your profile settings include several key pieces of information that identify you within the platform:

The First Name and Last Name fields allow you to set how you'll appear to other users. Each field has an edit button (pencil icon) that lets you modify your information when needed.

Your Email address is an important identifier that's used for account management and notifications. Like other profile fields, it can be updated using the edit button when necessary.

The Username field displays your unique identifier in the system. This name is used across the platform to distinguish you from other users.

A Profile Image helps others identify you visually in conversations and group settings. You can either upload a custom image or use the default initials display (shown as "TT" in the example).

The Change Password option allows you to update your security credentials when needed. This is an important feature for maintaining account security.

### Message Preferences

Below your profile information, you'll find several options that control how you interact with messages:

"Always translate speech to English" automatically converts voice input to English text, making communication more accessible across language barriers.

"Send the speech immediately" determines whether voice input is sent right away or waits for your confirmation.

"Push to talk" controls how voice input is activated, letting you choose between continuous listening or manual activation.

## User Interface Preferences

Under the User Interface section, you can customize how PrimeThink looks and behaves:

The "Use bubbles in chat" option determines the visual style of message display in your conversations. When enabled, messages appear in distinctive bubble containers that help separate different pieces of communication.

The "Default Virtual Assistant" setting lets you choose which AI assistant will be your primary helper. This selection affects which assistant automatically responds when you start new conversations.

The Theme selector offers three options for visual appearance:

* Light: A bright theme suitable for well-lit environments

* Dark: A darker theme that reduces eye strain in low-light conditions

* System: Automatically matches your device's theme settings

## Group Settings

When managing a group, you'll find additional options that help you customize the group experience:

The Group Code is a unique identifier that others can use to join your group. This code is automatically generated but can be customized if needed.

The Public Group Name is what others see when they interact with your group. You can edit this name to better reflect your group's purpose or organization.

The Group Image, like your profile image, helps identify your group visually in the interface. You can upload a custom image that represents your group's identity.

## Variables Management

Both User Variables and Group Variables tabs provide powerful customization options:

### User Variables

These are personal settings that affect your individual experience. When you click the "+" button, you can create new variables with custom names and values, allowing you to store preferences or information that can be used across different features.

### Group Variables

Similar to user variables, but these affect the entire group's experience. Group administrators can create and manage these variables to establish consistent behavior across the group.

## Advanced Options

At the bottom of the settings interface, you'll find several important system-level options:

The App Version information helps you stay aware of your current software version, which is important for troubleshooting and support.

The Logout option signs you out of your current session, while "Logout from all groups" signs you out of every group you're currently logged into – a useful security feature when using shared devices.

## Best Practices for Settings Management

When configuring your settings, consider these recommendations:

Take time to review all available options when you first set up your account. Understanding what's available helps you optimize your experience from the start.

Regularly review and update your settings as your needs change. What works well at first might need adjustment as you use different features or join new groups.

Keep your profile information current to help other users identify and communicate with you effectively.

Consider your privacy needs when configuring sharing and visibility options. PrimeThink provides various controls to help you maintain your preferred level of privacy.

The settings section is designed to give you fine-grained control over your PrimeThink experience while remaining approachable and easy to understand. As you become more familiar with these options, you'll be able to customize the platform to better suit your specific needs and preferences.



# Advanced

This section is for advanced topics. Topics that are not essential for getting started with PrimeThink, but are important for mastering the platform and getting the most out of it.



# Creating Live Pages

## Overview

A Live Page is a dynamic HTML page that combines traditional web technologies with AI-powered backend management and a powerful JavaScript data layer. Unlike static pages, Live Pages can dynamically manage data through CRUD operations and adapt their content and functionality based on user interactions.

## Core Architecture

### Canvas and Documents

The Live Page architecture consists of:

* Canvas: Serves as the main HTML content of your page. When creating your Live Page, you only need to paste the body content (starting with a `<div>` instead of `<body>`) into the canvas. The HTML document structure, DOCTYPE, and head elements are automatically provided by the framework.

* Documents: Function as a virtual file system containing configurations and supporting files

* Data Layer: A JavaScript library (`pt`) that provides CRUD operations for managing entities and data

Important: When pasting HTML code into the canvas, start with a `<div>` element instead of `<body>`, and omit the DOCTYPE, `<html>`, `<head>`, and `<title>` tags as these are automatically handled by the Live Pages framework.

## Quick Start

### Basic Data Operations

The `pt` JavaScript library is automatically available in your Live Page. Here's a quick example:

```JAVASCRIPT
// List entities with filters
const tasks = await pt.list({
    entityNames: ['task'],
    filters: { status: 'active' },
    limit: 20
});

// Add new entity
const newTask = await pt.add('task', {
    text: 'Buy groceries',
    completed: false
});

// Get single entity by ID
const task = await pt.get(123);

// Update entity
await pt.edit(123, {
    ...task.data,
    completed: true
});

// Delete entity
await pt.delete(123);

// Send message to chat
await pt.addMessage('Task completed!');

// Upload files to chat
const formData = new FormData();
formData.append('files', fileInput.files[0]);
await pt.uploadFiles(formData, 'Uploaded from Live Page');

// Send push notification
await pt.sendNotification(123, 'New Task', 'You have been assigned a task');

// Search documents
const results = await pt.searchDocuments('project requirements', 'DOCUMENTS_ONLY');

// Get document text
const doc = await pt.getDocumentText(456);

// Save document
await pt.saveDocument('report.pdf', 'PDF', 'application/pdf', '# Report\n\nContent here...');
```

### Simple Todo Example

```HTML
<div class="container mx-auto p-6">
    <h1 class="text-2xl font-bold mb-4">My Tasks</h1>

    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="taskInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="New task..."
        >
        <button
            onclick="addTask()"
            class="bg-blue-500 text-white px-4 py-2 rounded"
        >
            Add
        </button>
    </div>

    <div id="tasksList"></div>
</div>

<script>
async function loadTasks() {
    const entities = await pt.list({
        entityNames: ['task'],
        filters: { completed: false }
    });

    const tasks = entities.filter(e => e.entity_name === 'task');

    document.getElementById('tasksList').innerHTML = tasks.map(task => `
        <div class="bg-white p-4 rounded shadow mb-2 flex justify-between">
            <span>${task.data.text}</span>
            <button onclick="deleteTask(${task.id})" class="text-red-500">
                Delete
            </button>
        </div>
    `).join('');
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    if (!text) return;

    await pt.add('task', {
        text: text,
        completed: false
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function deleteTask(taskId) {
    await pt.delete(taskId);
    await loadTasks();
}

// Load tasks on page load
document.addEventListener('DOMContentLoaded', loadTasks);
</script>
```

## Setup Requirements

### No Special Configuration Needed

The data layer system requires no special chat configuration:

* No need to disable History and Memory: The system doesn't rely on LLM parsing

* No GOAL setup required: Data operations are handled by JavaScript

* No prompt engineering: Direct JavaScript API calls handle all data manipulation

* Automatic timestamps: The system automatically manages `created_at` and `updated_at` timestamps at the entity level

### Important Notes

* Timestamps are automatic: Don't add `created_at` or `updated_at` to your data objects - they're managed at the entity level

* Creator tracking: The `creator_user_id` field is automatically set when you create an entity, tracking who created it

* Entity structure: Your data goes in the `data` property, with system metadata at the entity level

* No initialization needed: The `pt` library is automatically available and initialized

## Entity Structure

Entities returned by `pt.list()` and `pt.get()` have this structure:

```JAVASCRIPT
{
    id: 123,                    // Unique entity ID
    entity_name: 'task',        // Type of entity
    data: {                     // Your actual data
        text: 'Buy groceries',
        completed: false
    },
    creator_user_id: 456,       // User ID who created this entity
    created_at: '2024-03-15T10:30:00+00:00',  // Managed automatically
    updated_at: '2024-03-15T10:35:00+00:00'   // Managed automatically
}
```

## Styling with Tailwind CSS

Live Pages have full support for Tailwind CSS, a utility-first CSS framework. Tailwind CSS is pre-loaded and available for use without any additional setup.

```HTML
<div class="bg-blue-500 text-white p-4 rounded-lg shadow-md">
    <h2 class="text-xl font-bold mb-2">Welcome</h2>
    <p class="text-blue-100">This is styled with Tailwind CSS</p>
</div>
```

### Responsive Design

```HTML
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    <div class="bg-white p-6 rounded-lg shadow">Card 1</div>
    <div class="bg-white p-6 rounded-lg shadow">Card 2</div>
    <div class="bg-white p-6 rounded-lg shadow">Card 3</div>
</div>
```

## Next Steps

Explore these detailed guides to build more sophisticated Live Pages:

### Core Topics

* [Data Management API Reference](data-management-api.html) - Complete guide to all `pt` API methods and operations

* [Filtering and Querying](filtering-and-querying.html) - Advanced server-side filtering with operators like `$contains`, `$in`, `$or`, and more

* [Pagination](pagination.html) - Implement efficient pagination for large datasets

* [Working with Chat Members](working-with-chat-members.html) - Integrate chat members into your Live Pages

### Design and Examples

* [Styling with Tailwind CSS](styling-with-tailwind.html) - Component examples and responsive design patterns

* [Complete Examples](live-pages-examples.html) - Full implementations including todo apps, dashboards, and more

### Advanced Topics

* [Performance and Best Practices](live-pages-best-practices.html) - Error handling, optimization, caching, using Goals for automation, and troubleshooting

## Common Use Cases

Live Pages are ideal for:

* Task Management: Todo lists, project trackers, sprint boards with notifications

* Data Dashboards: Analytics, reporting, monitoring with export to PDF/Excel

* Content Management: Article management, blog systems with document search

* Forms and Surveys: Data collection and processing with automated notifications

* Inventory Systems: Product catalogs, stock management with reports

* Customer Support: Ticket tracking, issue management with user notifications

* Document Library: Search, view, and create documents in various formats

* Knowledge Base: Semantic search across documentation with RAG

* Report Generation: Automated creation of PDFs, Word documents, and spreadsheets

* Team Collaboration: Real-time updates with push notifications to team members

* Invoice Processing: Upload invoices, AI extracts data and stores in database automatically

* Resume Screening: Batch upload resumes, AI parses and creates candidate records

* Expense Management: Upload receipts, AI categorizes and tracks expenses

* Meeting Analysis: Upload meeting notes/recordings, AI extracts action items

* Data Migration: Upload spreadsheets, AI validates and imports data with error handling

* Competitive Research: AI searches web and compiles competitor intelligence automatically

* Lead Generation: AI finds potential customers and stores contact information

## Key Features

* Real-time Updates: Data syncs automatically across the chat

* Server-side Filtering: Efficient querying with MongoDB-style operators

* Pagination Support: Handle large datasets efficiently

* Chat Integration: Access chat members and their information

* Send Messages: Communicate back to the chat from your Live Page

* File Upload: Upload files directly to the chat with drag & drop support

* AI-Powered Database Operations: Send natural language instructions to have AI extract data and manage database entities automatically

* Intelligent File Processing: Upload files with instructions and let AI extract structured data and store it in the database

* Push Notifications: Send notifications to specific users

* Document Search: Semantic search across documents and collections using RAG

* Document Management: View document content and create documents in various formats (TXT, MD, PDF, DOCX, CSV, XLSX)

* No Backend Setup: Everything works out of the box

* Tailwind CSS: Modern, responsive styling without custom CSS



# Data Management API Reference

## Overview

Live Pages use a powerful JavaScript library (`pt`) that provides database-like operations for managing data. The library is automatically initialized and provides simple CRUD operations with server-side filtering.

## Complete API Reference

The PrimeThink API provides a complete set of operations for managing entities and chat context:

| Operation |Method |Description |Performance |
-----------------------------------------------
| Create |`pt.add(entityName, data)` |Add new entity |Fast |
| Read (one) |`pt.get(entityId)` |Get single entity by ID |Very Fast (Primary Key) |
| Read (many) |`pt.list(options)` |Query multiple entities with filters |Fast (Indexed) |
| Update |`pt.edit(entityId, data)` |Update existing entity |Fast |
| Delete |`pt.delete(entityId)` |Remove entity |Fast |
| Members |`pt.getChatMembers()` |Get chat members (users & agents) |Fast |
| Chat Messages |`pt.addMessage(message)` |Add text message to chat |Fast |
| File Upload |`pt.uploadFiles(form, message)` |Upload files with optional message |Fast |
| Notifications |`pt.sendNotification(userId, title, text)` |Send push notification to user |Fast |
| Document Search |`pt.searchDocuments(query, scope)` |Search documents and collections using RAG |Fast |
| Document Read |`pt.getDocumentText(docId, options)` |Get document text content |Fast |
| Document Create |`pt.saveDocument(filename, format, mimetype, content)` |Create and save document |Fast |

## Available Methods

### pt.list(options)

List entities with optional server-side filtering, pagination, and multiple entity types.

Parameters:

* `entityNames`: Array of entity types to include (e.g., `['user', 'product']`)

* `filters`: Object with field-value pairs for server-side filtering

* `limit`: Maximum number of results to return (default: 100, max: 1000)

* `offset`: Number of results to skip for pagination (default: 0)

* `page`: Page number for page-based pagination (1-indexed, mutually exclusive with offset)

* `pageSize`: Items per page for page-based pagination (mutually exclusive with limit)

* `returnMetadata`: Set to `true` to return pagination metadata along with entities

Basic Usage:

```JAVASCRIPT
// List entities with filters
const users = await pt.list({
    entityNames: ['user', 'product'],
    filters: {
        status: 'active',
        age: { $gte: 18 }
    },
    limit: 10,
    offset: 0
});

// List multiple entity types
const entities = await pt.list({
    entityNames: ['task', 'event', 'user'],
    filters: { status: 'active' }
});

// Process results
const tasks = entities.filter(e => e.entity_name === 'task');
const events = entities.filter(e => e.entity_name === 'event');
const users = entities.filter(e => e.entity_name === 'user');
```

Response Structure:

```JAVASCRIPT
// Without metadata (default)
[
    {
        id: 123,
        entity_name: 'task',
        data: { text: 'Buy groceries', completed: false },
        creator_user_id: 456,
        created_at: '2024-03-15T10:30:00+00:00',
        updated_at: '2024-03-15T10:35:00+00:00'
    },
    // ... more entities
]

// With metadata (returnMetadata: true)
{
    entities: [...],    // Array of entity objects
    count: 20,          // Number of items in this response
    pagination: {
        limit: 20,      // Items requested
        offset: 40,     // Starting position
        has_more: true, // true if full page returned (more likely exists)
        page: 3,        // Current page (if page-based)
        page_size: 20   // Items per page (if page-based)
    }
}
```

### pt.get(entityId)

Retrieve a single entity by its ID. This method performs a direct primary key lookup, which is much faster and more semantic than using `pt.list()` with filters.

Parameters:

* `entityId`: Numeric ID of the entity to retrieve

Usage:

```JAVASCRIPT
// Get entity by ID
const task = await pt.get(123);
console.log(task.data.text);
console.log(task.created_at);

// Get and edit pattern
const task = await pt.get(123);
await pt.edit(123, {
    ...task.data,
    completed: !task.data.completed
});
```

Error Handling:

```JAVASCRIPT
try {
    const task = await pt.get(123);
    console.log('Found:', task.data);
} catch (error) {
    console.error('Entity not found or unauthorized:', error);
}
```

When to use pt.get():

* When you know the entity ID

* For direct lookups (much faster than filtering)

* Before editing an entity to get current state

### pt.add(entityName, data)

Create a new entity.

Parameters:

* `entityName`: String identifying the entity type

* `data`: Object containing the entity data

Usage:

```JAVASCRIPT
// Add a task
const result = await pt.add('task', {
    text: 'Buy groceries',
    completed: false
});

// Add an event
const event = await pt.add('event', {
    title: 'Team Meeting',
    date: '2024-03-20',
    time: '14:00',
    description: 'Weekly sync'
});

// The creator_user_id is automatically set to the current user
console.log(result.creator_user_id); // Automatically populated
```

Important Notes:

* `created_at` and `updated_at` are automatically managed

* `creator_user_id` is automatically set to the current user

* Don't include these fields in your data object

### pt.edit(entityId, data)

Update an existing entity. This method replaces all data in the entity.

Parameters:

* `entityId`: Numeric ID of the entity to update

* `data`: Object containing the new entity data

Usage:

```JAVASCRIPT
// Get current data first
const task = await pt.get(taskId);

// Update with merged data
const updated = await pt.edit(taskId, {
    ...task.data,
    completed: true
});

// Toggle a field
async function toggleTaskCompletion(taskId) {
    const task = await pt.get(taskId);
    await pt.edit(taskId, {
        ...task.data,
        completed: !task.data.completed
    });
}
```

Warning: Always merge with existing data unless you want to replace all fields:

```JAVASCRIPT
// ❌ BAD: This removes all other fields
await pt.edit(123, { completed: true });

// ✅ GOOD: This preserves other fields
const task = await pt.get(123);
await pt.edit(123, {
    ...task.data,
    completed: true
});
```

### pt.delete(entityId)

Remove an entity from the database.

Parameters:

* `entityId`: Numeric ID of the entity to delete

Usage:

```JAVASCRIPT
// Simple delete
async function deleteTask(taskId) {
    try {
        await pt.delete(taskId);
        console.log('Task deleted successfully');
    } catch (error) {
        console.error('Error deleting task:', error);
    }
}

// Delete with confirmation
async function deleteTaskSafely(taskId) {
    try {
        const task = await pt.get(taskId);

        if (confirm(`Delete task: "${task.data.text}"?`)) {
            await pt.delete(taskId);
            console.log('Task deleted successfully');
        }
    } catch (error) {
        alert('Task not found or already deleted');
    }
}

// Bulk delete
async function bulkDeleteTasks(taskIds) {
    const results = [];

    for (const id of taskIds) {
        try {
            await pt.delete(id);
            results.push({ id, success: true });
        } catch (error) {
            results.push({ id, success: false, error: error.message });
        }
    }

    return results;
}
```

### pt.getChatMembers()

Get all members of the current chat (users and AI agents).

Returns: Array of member objects with a simplified, clear structure. Each member is either a user or an AI agent.

Response Structure:

```JAVASCRIPT
[
  {
    "id": 123,              // Member ID (either user_id or agent_id)
    "type": "user",         // "user" or "agent"
    "name": "John Doe",     // Display name (full name for users, agent name for agents)

    // User-only fields (null for agents):
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",

    "is_owner": true        // Created the chat
  },
  {
    "id": 456,
    "type": "agent",
    "name": "AI Assistant",

    // User fields are null for agents
    "first_name": null,
    "last_name": null,
    "email": null,

    "is_owner": false
  }
]
```

Field Reference:

| Field |Type |User |Agent |Description |
-----------------------------------------
| id |number |✓ |✓ |Member ID |
| type |string |✓ |✓ |"user" or "agent" |
| name |string |✓ |✓ |Display name (full name or agent name) |
| first_name |string? |✓ |null |First name |
| last_name |string? |✓ |null |Last name |
| email |string? |✓ |null |Email address |
| is_owner |boolean |✓ |✓ |Created the chat |

Usage Examples:

```JAVASCRIPT
// Get and display all members
const members = await pt.getChatMembers();
members.forEach(member => {
    const icon = member.type === 'user' ? '👤' : '🤖';
    console.log(`${icon} ${member.name}`);
});

// Filter by type
const users = members.filter(m => m.type === 'user');
const agents = members.filter(m => m.type === 'agent');
console.log(`Users: ${users.length}, Agents: ${agents.length}`);

// Create task assignment dropdown (users only)
async function createAssigneeDropdown() {
    const members = await pt.getChatMembers();
    const users = members.filter(m => m.type === 'user');

    const options = users
        .map(m => `<option value="${m.id}">${m.name}</option>`)
        .join('');

    return `
        <select id="assignee">
            <option value="">Unassigned</option>
            ${options}
        </select>
    `;
}

// Get current user
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.type === 'user' && m.is_owner);
```

### pt.initDb()

Initialize the database for the current group. This is usually called automatically, but can be called manually if needed.

Usage:

```JAVASCRIPT
await pt.initDb();
```

### pt.reload()

Reload the current Live Page.

Usage:

```JAVASCRIPT
// Reload the page
pt.reload();

// Example: Reload after a major update
async function refreshApplication() {
    await pt.initDb();
    pt.reload();
}
```

### pt.navigate(chatId)

Navigate to a different Live Page.

Parameters:

* `chatId`: ID of the chat/Live Page to navigate to

Usage:

```JAVASCRIPT
// Navigate to another Live Page
pt.navigate(456);

// Example: Navigation menu
function createNavigation(livePages) {
    return livePages.map(page => `
        <button onclick="pt.navigate(${page.id})">
            ${page.title}
        </button>
    `).join('');
}
```

### pt.addMessage(message)

Add a text message to the current chat from your Live Page. This allows your Live Page to communicate back to the chat interface.

Parameters:

* `message`: String containing the message text to send

Returns: Promise that resolves to an object containing:

* `message_id`: ID of the created message

* `chat_id`: ID of the chat

* `created_at`: Timestamp when the message was created

Usage:

```JAVASCRIPT
// Send a simple text message
async function sendNotification() {
    try {
        const result = await pt.addMessage('Task completed successfully!');
        console.log('Message sent with ID:', result.message_id);
    } catch (error) {
        console.error('Error sending message:', error);
    }
}

// Send message from user input
async function sendUserMessage() {
    const input = document.getElementById('messageInput');
    const message = input.value.trim();

    if (!message) {
        alert('Please enter a message');
        return;
    }

    try {
        await pt.addMessage(message);
        input.value = ''; // Clear input
        alert('Message sent!');
    } catch (error) {
        alert('Failed to send message: ' + error.message);
    }
}

// Send notification after an action
async function completeTask(taskId) {
    const task = await pt.get(taskId);
    await pt.edit(taskId, {
        ...task.data,
        completed: true
    });

    // Notify the chat
    await pt.addMessage(`Task "${task.data.text}" has been completed!`);
}
```

Common Use Cases:

* Notify users when an action is completed

* Send alerts or warnings

* Provide feedback from form submissions

* Log important events to the chat

* Create interactive chat-based workflows

AI-Powered Database Operations:

When you send a message to the chat, the AI assistant can process your instructions and automatically manage database entities. This enables powerful automation workflows where you describe what you want, and the AI handles the database operations.

Best Practice: Be Explicit About Tool Usage

To ensure the AI executes your desired operation, explicitly mention which database tool to use:

* Use `chatdb_add` to create new entities

* Use `chatdb_list` to search/query entities

* Use `chatdb_get` to retrieve a specific entity by ID

* Use `chatdb_edit` to update existing entities

* Use `chatdb_delete` to remove entities

```JAVASCRIPT
// Example 1: Extract data and store it (explicit tool usage)
await pt.addMessage(`
Please analyze the sales data from last quarter and use the tool 'chatdb_add' to create database entries:
- entity_name: "sales_record"
- data format: { month: string, revenue: number, region: string, growth_percentage: number }

Extract all quarterly sales information and add each month as a separate record.
`);

// Example 2: Process and categorize information (explicit tool usage)
await pt.addMessage(`
Search for the top 5 competitors in the AI assistant market and use the tool 'chatdb_add' to store each one:
- entity_name: "competitor"
- data: { name: string, website: string, key_features: array, pricing_model: string }
`);

// Example 3: Query and transform existing data
await pt.addMessage(`
Use 'chatdb_list' to find all tasks with status "pending" and high priority.
For each one, use 'chatdb_add' to create a new entity:
- entity_name: "urgent_action"
- data: { task_id: number, title: string, due_date: string, estimated_hours: number }
`);

// Example 4: Update existing entities
await pt.addMessage(`
Use 'chatdb_list' to find all expenses from last month with category "uncategorized".
For each expense, use 'chatdb_edit' to update it with the appropriate category based on the merchant name.
`);

// Example 5: Clean up old data
await pt.addMessage(`
Use 'chatdb_list' to find all tasks with status "completed" that were finished more than 90 days ago.
Use 'chatdb_delete' to remove them from the database.
`);
```

Available Database Tools:

| Tool |Purpose |When to Use |
------------------------------
| `chatdb_add` |Create new entities |Storing new data, adding records |
| `chatdb_list` |Query/search entities |Finding records with filters, getting multiple items |
| `chatdb_get` |Get single entity by ID |Retrieving a specific record when you know its ID |
| `chatdb_edit` |Update existing entities |Modifying data, changing status, updating fields |
| `chatdb_delete` |Remove entities |Cleaning up, removing old data |
| `chatdb_init` |Initialize database schema |First-time setup (rarely needed) |

This feature is particularly powerful because:

* The AI understands natural language instructions

* It can process complex requirements and extract structured data

* Explicit tool mentions ensure the correct operations are executed

* It handles the database operations automatically

* You can combine data processing with storage in one request

### pt.uploadFiles(formOrFormData, message)

Upload files from your Live Page to the chat with an optional text message. The files are processed and attached to the chat.

Parameters:

* `formOrFormData`: Either an HTML `<form>` element or a `FormData` object containing files

* `message`: Optional string message to include with the upload

Returns: Promise that resolves to an object containing:

* `message`: Success message

* `files_count`: Number of files uploaded

* `chat_id`: ID of the chat

Usage:

Example 1: Upload from HTML Form

```HTML
<form id="uploadForm" onsubmit="handleUpload(event)">
    <input type="file" name="files" multiple required>
    <input type="text" name="message" placeholder="Optional message...">
    <button type="submit">Upload Files</button>
</form>

<script>
async function handleUpload(event) {
    event.preventDefault();
    const form = event.target;

    try {
        const result = await pt.uploadFiles(form);
        alert(`Uploaded ${result.files_count} file(s) successfully!`);
        form.reset();
    } catch (error) {
        alert('Upload failed: ' + error.message);
    }
}
</script>
```

Example 2: Programmatic Upload with FormData

```JAVASCRIPT
// Upload files with custom message
async function uploadFilesWithMessage() {
    const fileInput = document.getElementById('fileInput');

    if (fileInput.files.length === 0) {
        alert('Please select files');
        return;
    }

    const formData = new FormData();

    // Add all selected files
    for (const file of fileInput.files) {
        formData.append('files', file);
    }

    try {
        const result = await pt.uploadFiles(
            formData,
            'Uploaded files from Live Page'
        );
        alert(`Success! Uploaded ${result.files_count} files`);
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
```

Example 3: Drag and Drop Upload

```HTML
<div
    id="dropZone"
    ondrop="handleDrop(event)"
    ondragover="event.preventDefault()"
    class="border-2 border-dashed p-8 text-center"
>
    Drop files here to upload
</div>

<script>
async function handleDrop(event) {
    event.preventDefault();

    const files = event.dataTransfer.files;
    if (files.length === 0) return;

    const formData = new FormData();
    for (const file of files) {
        formData.append('files', file);
    }

    try {
        const result = await pt.uploadFiles(formData, 'Files uploaded via drag & drop');
        alert(`Uploaded ${result.files_count} file(s)`);
    } catch (error) {
        console.error('Upload error:', error);
    }
}
</script>
```

Important Notes:

* Files are processed through the existing message processing system

* Uploaded files are automatically attached to the chat

* Supports multiple file uploads in a single request

* File size limits and restrictions apply based on system configuration

* Authentication and rate limiting are automatically handled

Common Use Cases:

* Document upload forms

* Image galleries with upload

* File attachment for support tickets

* Batch file processing interfaces

* Report generation and upload

AI-Powered File Processing with Database Storage:

When you upload files with instructions, the AI can process the files and automatically store extracted information in the database. This is incredibly powerful for automating data entry, document processing, and information extraction.

```JAVASCRIPT
// Example 1: Extract structured data from invoices
const formData = new FormData();
formData.append('files', invoiceFile);

await pt.uploadFiles(formData, `
Please extract invoice information from the uploaded file and use the tool 'chatdb_add' to create database records:
- entity_name: "invoice"
- data format: {
    invoice_number: string,
    date: string,
    vendor: string,
    amount: number,
    due_date: string,
    line_items: array of {item: string, quantity: number, price: number}
  }
`);

// Example 2: Process multiple resumes and extract candidate info
const formData = new FormData();
for (const file of resumeFiles) {
    formData.append('files', file);
}

await pt.uploadFiles(formData, `
Extract candidate information from each resume and use the tool 'chatdb_add' to create entries:
- entity_name: "candidate"
- data: {
    name: string,
    email: string,
    phone: string,
    years_experience: number,
    skills: array,
    education: string,
    previous_companies: array
  }
`);

// Example 3: Analyze meeting recordings and create action items
await pt.uploadFiles(formData, `
Transcribe the meeting recording and extract all action items.
For each action item, use the tool 'chatdb_add' to create:
- entity_name: "action_item"
- data: {
    description: string,
    assigned_to: string,
    due_date: string,
    priority: "high" | "medium" | "low",
    mentioned_at_timestamp: string
  }
`);

// Example 4: Process expense receipts
await pt.uploadFiles(formData, `
Extract expense information from the receipt images and use the tool 'chatdb_add' to store each as:
- entity_name: "expense"
- data: {
    date: string,
    merchant: string,
    category: string,
    amount: number,
    currency: string,
    description: string
  }
`);

// Example 5: Import customer data from spreadsheets
await pt.uploadFiles(formData, `
Parse the customer data spreadsheet and use the tool 'chatdb_add' to create database entries:
- entity_name: "customer"
- data: {
    customer_id: string,
    company_name: string,
    contact_person: string,
    email: string,
    phone: string,
    industry: string,
    contract_value: number,
    renewal_date: string
  }

Skip any rows with missing required fields and create a summary of imported vs skipped records.
`);

// Example 6: Update existing records based on uploaded data
await pt.uploadFiles(formData, `
Parse the uploaded spreadsheet containing updated customer information.
Use 'chatdb_list' to find matching customers by customer_id.
Use 'chatdb_edit' to update each customer record with the new information.
Provide a summary of how many records were updated.
`);
```

How It Works:

1. You upload files using `pt.uploadFiles()`

2. Include clear instructions in the message parameter

3. Specify the entity name and data structure you want

4. The AI processes the files, extracts information, and stores it automatically

5. The AI can validate data, handle errors, and provide feedback

Benefits:

* Automate tedious data entry tasks

* Extract structured data from unstructured files

* Process multiple files in batch

* Combine file analysis with database storage

* Get intelligent parsing with error handling

### pt.sendNotification(userId, title, text)

Send push notifications to specific users in the chat.

Parameters:

* `userId` (number, required): The ID of the user to notify

* `title` (string, required): Notification title

* `text` (string, required): Notification message text

Returns: Promise that resolves to an object with `message` field indicating success

Usage:

```JAVASCRIPT
// Send notification to a specific user
const members = await pt.getChatMembers();
const targetUser = members.find(m => m.email === 'john@example.com');

await pt.sendNotification(
    targetUser.id,
    'Task Assigned',
    'You have been assigned a new task'
);

// Send notification after task assignment
async function assignTask(taskId, userId) {
    const task = await pt.get(taskId);
    await pt.edit(taskId, {
        ...task.data,
        assigned_to: userId
    });

    // Notify the user
    await pt.sendNotification(
        userId,
        'New Task',
        `You've been assigned: ${task.data.title}`
    );
}

// Notify user when task is completed
async function completeTask(taskId) {
    const task = await pt.get(taskId);
    await pt.edit(taskId, { ...task.data, completed: true });

    if (task.data.assigned_to) {
        await pt.sendNotification(
            task.data.assigned_to,
            'Task Completed',
            `Task "${task.data.title}" has been marked as complete`
        );
    }
}
```

Common Use Cases:

* Task assignment notifications

* Alert users about important updates

* Remind users of pending actions

* Notify collaborators of changes

### pt.searchDocuments(query, scope)

Perform semantic search across documents and collections using RAG (Retrieval-Augmented Generation).

Parameters:

* `query` (string, required): Natural language search query

* `scope` (string, optional): Search scope - "ALL", "DOCUMENTS_ONLY", or "COLLECTIONS_ONLY" (default: "ALL")

Returns: Promise that resolves to an object with `results` field containing XML-formatted search results

Usage:

```JAVASCRIPT
// Search all documents and collections
const result = await pt.searchDocuments('quarterly financial results Q3 2024');
console.log(result.results);

// Search only documents
const docsOnly = await pt.searchDocuments('project requirements', 'DOCUMENTS_ONLY');

// Search only collections
const collectionsOnly = await pt.searchDocuments('company policies', 'COLLECTIONS_ONLY');

// Build a search interface
async function searchKnowledgeBase(query) {
    try {
        const result = await pt.searchDocuments(query);
        displaySearchResults(result.results);
    } catch (error) {
        console.error('Search failed:', error);
    }
}

// Search with user input
async function handleSearch() {
    const query = document.getElementById('searchInput').value;
    const scope = document.getElementById('scopeSelect').value;

    if (!query) {
        alert('Please enter a search query');
        return;
    }

    const results = await pt.searchDocuments(query, scope);
    document.getElementById('resultsArea').textContent = results.results;
}
```

Common Use Cases:

* Knowledge base search

* Find relevant documents

* Research and discovery

* Content recommendation

### pt.getDocumentText(docId, options)

Retrieve the text content from a document.

Parameters:

* `docId` (number, required): Document ID

* `options` (object, optional): Options object with: * `from` (number): Start character position (default: 0) * `to` (number): End character position (default: null for full text)

Returns: Promise that resolves to an object with `text` field containing the document content

Usage:

```JAVASCRIPT
// Get full document text
const doc = await pt.getDocumentText(123);
console.log(doc.text);

// Get first 1000 characters (preview)
const preview = await pt.getDocumentText(123, { from: 0, to: 1000 });
console.log(preview.text);

// Get text starting from character 500
const partial = await pt.getDocumentText(123, { from: 500 });
console.log(partial.text);

// Build a document viewer
async function viewDocument(docId) {
    try {
        const result = await pt.getDocumentText(docId);

        if (result.text) {
            document.getElementById('documentContent').textContent = result.text;
        } else {
            alert(result.message || 'No text available');
        }
    } catch (error) {
        console.error('Error loading document:', error);
    }
}

// Load document preview
async function loadPreview(docId) {
    const preview = await pt.getDocumentText(docId, { to: 500 });
    document.getElementById('preview').textContent = preview.text + '...';
}
```

Common Use Cases:

* Document preview

* Content analysis

* Text extraction

* Partial content loading for large documents

### pt.saveDocument(filename, format, mimetype, content)

Create and save content as a document in the chat.

Parameters:

* `filename` (string, required): Filename with extension

* `format` (string, required): Format type - "TXT", "MD", "HTML", "DOCX", "PDF", "CSV", "XLSX", or "CUSTOM"

* `mimetype` (string, required): MIME type (e.g., "text/plain", "application/pdf")

* `content` (string, required): Document content

Format Guidelines:

* TXT: Plain text (no Markdown)

* MD/MARKDOWN: Markdown text

* HTML: HTML content (not Markdown)

* DOCX: Use Markdown text (will be converted to Word format)

* PDF: Use Markdown text (will be converted to PDF)

* CSV: Plain CSV format text

* XLSX: Plain CSV format text (will be converted to Excel)

* CUSTOM: Any non-binary plain text format

Returns: Promise that resolves to an object with `filename` field and success status

Usage:

```JAVASCRIPT
// Save a plain text document
await pt.saveDocument(
    'notes.txt',
    'TXT',
    'text/plain',
    'These are my meeting notes from today.'
);

// Save a Markdown document
await pt.saveDocument(
    'report.md',
    'MD',
    'text/markdown',
    '# Project Report\n\n## Summary\n\nThe project is progressing well.'
);

// Save as Word document (using Markdown)
await pt.saveDocument(
    'proposal.docx',
    'DOCX',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    '# Business Proposal\n\n## Executive Summary\n\nThis proposal outlines...'
);

// Save as PDF (using Markdown)
await pt.saveDocument(
    'invoice.pdf',
    'PDF',
    'application/pdf',
    '# Invoice #12345\n\n**Date:** 2025-01-15\n**Amount:** $1,250.00'
);

// Save CSV data
await pt.saveDocument(
    'data.csv',
    'CSV',
    'text/csv',
    'Name,Email,Age\nJohn Doe,john@example.com,30\nJane Smith,jane@example.com,28'
);

// Generate and save a report
async function generateReport(data) {
    const content = `# Monthly Report

## Summary
Total items: ${data.length}

## Details
${data.map(item => `- ${item.name}: ${item.value}`).join('\n')}
`;

    await pt.saveDocument(
        'monthly-report.pdf',
        'PDF',
        'application/pdf',
        content
    );

    alert('Report generated successfully!');
}

// Helper function for common MIME types
function getMimeType(format) {
    const types = {
        'TXT': 'text/plain',
        'MD': 'text/markdown',
        'HTML': 'text/html',
        'DOCX': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'PDF': 'application/pdf',
        'CSV': 'text/csv',
        'XLSX': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    };
    return types[format] || 'text/plain';
}

// Save with helper
await pt.saveDocument('report.pdf', 'PDF', getMimeType('PDF'), content);
```

MIME Type Reference:

* Plain text: `text/plain`

* Markdown: `text/markdown`

* HTML: `text/html`

* Word: `application/vnd.openxmlformats-officedocument.wordprocessingml.document`

* PDF: `application/pdf`

* CSV: `text/csv`

* Excel: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`

Common Use Cases:

* Report generation

* Export data to various formats

* Create documentation

* Save user-generated content

* Generate invoices and forms

## Entity Structure

All entities follow this consistent structure:

```JAVASCRIPT
{
    // System fields (managed automatically)
    id: 123,                    // Unique entity ID
    entity_name: 'task',        // Type of entity
    creator_user_id: 456,       // User ID who created this entity
    created_at: '2024-03-15T10:30:00+00:00',  // Auto-managed
    updated_at: '2024-03-15T10:35:00+00:00',  // Auto-managed

    // Your data (in the 'data' property)
    data: {
        text: 'Buy groceries',
        completed: false,
        priority: 'high',
        // ... any custom fields
    }
}
```

System-Managed Fields:

* `id`: Unique identifier for the entity

* `entity_name`: Type/category of the entity

* `creator_user_id`: ID of the user who created the entity (automatic)

* `created_at`: Timestamp when entity was created (automatic)

* `updated_at`: Timestamp when entity was last updated (automatic)

Important:

* Never add `created_at`, `updated_at`, or `creator_user_id` to your `data` object

* These fields are managed at the entity level, not in your data

* Your custom fields go inside the `data` object

## Best Practices

### 1. Use pt.get() for Single Entities

When you know the entity ID, always use `pt.get()` instead of `pt.list()` with filters:

```JAVASCRIPT
// ✅ GOOD: Fast primary key lookup
const task = await pt.get(123);

// ❌ AVOID: Slower filtering when ID is known
const tasks = await pt.list({
    entityNames: ['task'],
    filters: { id: 123 }
});
const task = tasks[0];
```

### 2. Always Merge Data When Editing

```JAVASCRIPT
// ✅ GOOD: Preserve existing fields
const task = await pt.get(taskId);
await pt.edit(taskId, {
    ...task.data,
    completed: true
});

// ❌ BAD: Lose all other fields
await pt.edit(taskId, { completed: true });
```

### 3. Handle Errors Gracefully

```JAVASCRIPT
async function robustOperation() {
    try {
        const entity = await pt.get(123);
        return entity;
    } catch (error) {
        console.error('Operation failed:', error);
        return null;
    }
}
```

### 4. Cache getChatMembers() Results

```JAVASCRIPT
// ✅ GOOD: Cache members at app initialization
let allMembers = [];

async function initApp() {
    allMembers = await pt.getChatMembers();
    await loadTasks();
}

function getMemberName(userId) {
    const member = allMembers.find(m => m.id === userId);
    return member ? member.name : 'Unknown';
}

// ❌ AVOID: Calling getChatMembers() repeatedly
async function displayTask(task) {
    const members = await pt.getChatMembers(); // Called for every task!
    const creator = members.find(m => m.id === task.creator_user_id);
    return creator.name;
}
```

### 5. Use Appropriate Limits

```JAVASCRIPT
// ✅ GOOD: Reasonable limits
const recentTasks = await pt.list({
    entityNames: ['task'],
    limit: 50
});

// ❌ AVOID: Requesting too much data
const allTasks = await pt.list({
    entityNames: ['task'],
    limit: 10000 // Too large
});
```

## Performance Tips

1. Primary key lookups: Use `pt.get()` when you know the ID (fastest)

2. Server-side filtering: Use filters in `pt.list()` instead of client-side filtering

3. Pagination: Use `limit` and `offset` for large datasets

4. Caching: Cache `getChatMembers()` and other static data

5. Batch operations: Use `Promise.all()` for multiple independent operations

```JAVASCRIPT
// Batch operations example
async function batchUpdate(taskIds, updates) {
    const promises = taskIds.map(async id => {
        const task = await pt.get(id);
        return pt.edit(id, { ...task.data, ...updates });
    });

    await Promise.all(promises);
}
```

## Next Steps

* [Filtering and Querying](filtering-and-querying.html) - Learn about advanced filtering operators

* [Pagination](pagination.html) - Implement efficient pagination

* [Working with Chat Members](working-with-chat-members.html) - Integrate chat members in your app

* [Complete Examples](live-pages-examples.html) - See full implementations



# Filtering and Querying

## Overview

The filtering system in Live Pages supports MongoDB-style operators for powerful, flexible server-side queries. Server-side filtering reduces data transfer and improves performance by only returning matching records.

## Supported Filter Operators

| Operator |Description |Example |
----------------------------------
| Exact match |Simple value |`{status: 'active'}` |
| `$contains` |Case-insensitive partial match |`{text: {$contains: 'grocery'}}` |
| `$ilike` |Case-insensitive LIKE with patterns |`{text: {$ilike: '%search%'}}` |
| `$like` |Case-sensitive LIKE with patterns |`{text: {$like: '%Search%'}}` |
| `$in` |Matches any value in array |`{status: {$in: ['active', 'pending']}}` |
| `$gt` |Greater than |`{age: {$gt: 25}}` |
| `$gte` |Greater than or equal |`{age: {$gte: 18}}` |
| `$lt` |Less than |`{age: {$lt: 50}}` |
| `$lte` |Less than or equal |`{age: {$lte: 65}}` |
| `$ne` |Not equal |`{status: {$ne: 'deleted'}}` |
| `$or` |OR logic across conditions |`{$or: [{status: 'active'}, {priority: 'high'}]}` |

## Operator Performance

Operators ranked from fastest to slowest:

1. Exact matches: `{status: 'active'}` (fastest)

2. **$in operator**: `{status: {$in: ['active', 'pending']}}`

3. Comparison operators: `$gt`, `$gte`, `$lt`, `$lte`, `$ne`

4. Text search: `$contains`, `$ilike`, `$like`

5. Complex logic: `$or` with multiple conditions (slowest)

## Filter Examples

### 1. Partial Text Search

The most common use case - searching for partial matches:

```JAVASCRIPT
// Search tasks containing "grocery" (case-insensitive)
const tasks = await pt.list({
    entityNames: ['task'],
    filters: {
        text: { $contains: 'grocery' }
    }
});
// Matches: "Buy groceries", "GROCERY shopping", "grocery store"
```

### 2. Pattern Matching with Wildcards

Use `$ilike` or `$like` for more complex patterns:

```JAVASCRIPT
// Find emails ending with @gmail.com
const users = await pt.list({
    entityNames: ['user'],
    filters: {
        email: { $ilike: '%@gmail.com' }
    }
});

// Find tasks starting with "urgent" (case-insensitive)
const urgentTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        text: { $ilike: 'urgent%' }
    }
});

// Case-sensitive pattern matching
const tasks = await pt.list({
    entityNames: ['task'],
    filters: {
        text: { $like: 'URGENT%' }
    }
});
```

### 3. Numeric Comparisons

Filter by numeric ranges:

```JAVASCRIPT
// Find users 18 or older
const adults = await pt.list({
    entityNames: ['user'],
    filters: {
        age: { $gte: 18 }
    }
});

// Find tasks with priority between 1 and 3
const mediumPriority = await pt.list({
    entityNames: ['task'],
    filters: {
        priority: { $gte: 1, $lte: 3 }
    }
});

// Find expensive products
const expensiveProducts = await pt.list({
    entityNames: ['product'],
    filters: {
        price: { $gt: 1000 }
    }
});
```

### 4. Multiple Filters (AND Logic)

All filters at the same level are combined with AND:

```JAVASCRIPT
// Find active tasks containing "urgent"
const urgentActiveTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        status: 'active',
        text: { $contains: 'urgent' }
    }
});

// Find young adult users with gmail accounts
const youngGmailUsers = await pt.list({
    entityNames: ['user'],
    filters: {
        age: { $gte: 18, $lt: 30 },
        email: { $ilike: '%@gmail.com' }
    }
});
```

### 5. Excluding Items

Use `$ne` to exclude specific values:

```JAVASCRIPT
// Find all tasks except deleted ones
const activeTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        status: { $ne: 'deleted' }
    }
});

// Find tasks not assigned to John
const otherTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        assignee: { $ne: 'john' }
    }
});
```

### 6. Multi-Value Matching with $in

Match any value from a list:

```JAVASCRIPT
// Find tasks with multiple priority levels
const importantTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        priority: { $in: ['high', 'medium'] }
    }
});

// Find tasks assigned to multiple team members
const teamTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        assignee: { $in: ['john', 'jane', 'bob'] }
    }
});

// Find products in multiple categories
const products = await pt.list({
    entityNames: ['product'],
    filters: {
        category: { $in: ['electronics', 'computers', 'accessories'] }
    }
});
```

### 7. OR Logic with $or

Match records that satisfy any condition:

```JAVASCRIPT
// Find tasks that are either high priority OR overdue
const urgentTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        $or: [
            { priority: 'high' },
            { status: 'overdue' }
        ]
    }
});

// Complex OR with text search
const searchResults = await pt.list({
    entityNames: ['task'],
    filters: {
        $or: [
            { text: { $contains: 'urgent' } },
            { description: { $contains: 'urgent' } },
            { tags: { $contains: 'urgent' } }
        ]
    }
});

// OR with multiple conditions
const criticalItems = await pt.list({
    entityNames: ['task'],
    filters: {
        $or: [
            { priority: 'critical' },
            { due_date: { $lt: '2024-03-20' } },
            { escalated: "true" }
        ]
    }
});
```

### 8. Combining AND and OR Logic

Mix AND and OR for complex queries:

```JAVASCRIPT
// Find incomplete tasks assigned to specific users
const myTeamTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        completed: "false",  // AND condition
        $or: [               // OR conditions within AND
            { assignee: 'john' },
            { assignee: 'jane' }
        ]
    }
});

// Same result using $in (cleaner for multiple values)
const myTeamTasksSimplified = await pt.list({
    entityNames: ['task'],
    filters: {
        completed: "false",
        assignee: { $in: ['john', 'jane'] }
    }
});

// Complex combination: Active high-priority OR overdue tasks
const actionableItems = await pt.list({
    entityNames: ['task'],
    filters: {
        status: 'active',    // Must be active (AND)
        $or: [               // AND either high priority OR overdue
            { priority: 'high' },
            { due_date: { $lt: '2024-03-20' } }
        ]
    }
});
```

### 9. Boolean and Date Filters

```JAVASCRIPT
// Boolean filters (stored as strings in JSONB)
const completedTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        completed: "true",     // Exact match for boolean values
        status: "active"
    }
});

// Date filters with comparisons
const upcomingTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        due_date: { $gte: "2024-03-15" },  // Tasks due today or later
        due_date: { $lt: "2024-04-01" }    // But before April
    }
});

// Tasks created before a certain date
const oldTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        created_date: { $lt: "2024-03-01" }
    }
});
```

### 10. Filtering by Creator

The `creator_user_id` field is a column-level filter (not a JSONB data field), making it more efficient:

```JAVASCRIPT
// Get entities created by current user (exact match)
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.is_owner);

const myTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: currentUser.id
    }
});

// Get entities created by multiple users ($in operator)
const teamIds = [123, 456, 789];
const teamTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: { $in: teamIds }
    }
});

// Get entities NOT created by specific user ($ne operator)
const othersTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: { $ne: currentUser.id }
    }
});

// Combine creator filter with data filters
const myActiveTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: currentUser.id,  // Column-level filter
        completed: false,                  // JSONB data filter
        priority: { $in: ['high', 'medium'] }  // JSONB data filter
    }
});
```

## Real-World Filtering Examples

### Project Management Dashboard

```JAVASCRIPT
// Get tasks for sprint planning
async function getSprintTasks() {
    return await pt.list({
        entityNames: ['task'],
        filters: {
            // Must be unfinished
            completed: "false",
            // High or medium priority only
            priority: { $in: ['high', 'medium'] },
            // Assigned to active team members
            $or: [
                { assignee: { $in: ['john', 'jane', 'mike'] } },
                { status: 'unassigned' }
            ]
        },
        limit: 100
    });
}

// Find overdue or urgent items
async function getUrgentItems() {
    const today = new Date().toISOString().split('T')[0];

    return await pt.list({
        entityNames: ['task'],
        filters: {
            completed: "false",
            $or: [
                { priority: 'high' },
                { due_date: { $lt: today } },
                { status: 'blocked' }
            ]
        }
    });
}
```

### Content Management System

```JAVASCRIPT
// Advanced content search
async function searchContent(query, filters = {}) {
    const searchFilters = {
        // Search across multiple content fields
        $or: [
            { title: { $contains: query } },
            { content: { $contains: query } },
            { tags: { $contains: query } }
        ]
    };

    // Add optional filters
    if (filters.status) {
        searchFilters.status = { $in: Array.isArray(filters.status) ? filters.status : [filters.status] };
    }

    if (filters.author) {
        searchFilters.author = { $in: Array.isArray(filters.author) ? filters.author : [filters.author] };
    }

    if (filters.dateRange) {
        searchFilters.published_date = {
            $gte: filters.dateRange.start,
            $lte: filters.dateRange.end
        };
    }

    return await pt.list({
        entityNames: ['article', 'blog_post', 'page'],
        filters: searchFilters,
        limit: 50
    });
}

// Usage
const results = await searchContent('javascript', {
    status: ['published', 'featured'],
    author: ['john', 'jane'],
    dateRange: { start: '2024-01-01', end: '2024-12-31' }
});
```

### E-commerce Product Catalog

```JAVASCRIPT
// Product filtering with multiple criteria
async function filterProducts(criteria) {
    const filters = {};

    // Category filter
    if (criteria.categories?.length) {
        filters.category = { $in: criteria.categories };
    }

    // Price range
    if (criteria.minPrice || criteria.maxPrice) {
        filters.price = {};
        if (criteria.minPrice) filters.price.$gte = criteria.minPrice;
        if (criteria.maxPrice) filters.price.$lte = criteria.maxPrice;
    }

    // Text search across multiple fields
    if (criteria.search) {
        filters.$or = [
            { name: { $contains: criteria.search } },
            { description: { $contains: criteria.search } },
            { brand: { $contains: criteria.search } }
        ];
    }

    // Availability and rating filters
    if (criteria.inStock) {
        filters.stock_quantity = { $gt: 0 };
    }

    if (criteria.minRating) {
        filters.rating = { $gte: criteria.minRating };
    }

    return await pt.list({
        entityNames: ['product'],
        filters: filters,
        limit: criteria.limit || 24
    });
}

// Usage
const products = await filterProducts({
    categories: ['electronics', 'computers'],
    minPrice: 100,
    maxPrice: 1000,
    search: 'laptop',
    inStock: true,
    minRating: 4
});
```

### Customer Support System

```JAVASCRIPT
// Find tickets requiring attention
async function getTicketsNeedingAttention() {
    return await pt.list({
        entityNames: ['ticket'],
        filters: {
            $or: [
                // High priority tickets
                { priority: 'high' },
                // Old unresolved tickets
                {
                    status: { $in: ['open', 'pending'] },
                    created_date: { $lt: '2024-03-01' }
                },
                // Escalated tickets
                { escalated: "true" }
            ]
        }
    });
}

// Search tickets by customer or issue
async function searchTickets(query, assignee = null) {
    const filters = {
        $or: [
            { subject: { $contains: query } },
            { description: { $contains: query } },
            { customer_name: { $contains: query } }
        ]
    };

    if (assignee) {
        filters.assignee = assignee;
    }

    return await pt.list({
        entityNames: ['ticket'],
        filters: filters,
        limit: 50
    });
}
```

## Advanced Search Implementation

### Smart Search with Multiple Strategies

```JAVASCRIPT
// Smart search functionality with fallback strategies
async function smartSearch(query, entityTypes = ['task']) {
    // Try different search strategies for best results
    const strategies = [
        // Exact match first (fastest)
        { text: query },
        // Then partial match (most common)
        { text: { $contains: query } },
        // Finally pattern matching (most flexible)
        { text: { $ilike: `%${query}%` } }
    ];

    for (const filter of strategies) {
        const results = await pt.list({
            entityNames: entityTypes,
            filters: filter,
            limit: 20
        });

        if (results.length > 0) {
            return results;
        }
    }

    return [];
}
```

### Live Search with Debouncing

```JAVASCRIPT
// Real-time search with performance optimization
function setupLiveSearch() {
    const searchInput = document.getElementById('searchInput');
    let searchTimeout;

    searchInput.addEventListener('input', (e) => {
        clearTimeout(searchTimeout);
        const query = e.target.value.trim();

        if (query.length < 2) {
            clearResults();
            return;
        }

        searchTimeout = setTimeout(async () => {
            const results = await pt.list({
                entityNames: ['task'],
                filters: {
                    $or: [
                        { text: { $contains: query } },
                        { description: { $contains: query } }
                    ]
                },
                limit: 20
            });
            displayResults(results);
        }, 300); // Debounce for 300ms
    });
}
```

### Multi-Field Advanced Search

```JAVASCRIPT
// Advanced multi-field search using $or
async function advancedSearch(query, options = {}) {
    const searchFields = options.fields || ['text', 'description', 'title'];

    const filters = {
        $or: searchFields.map(field => ({
            [field]: { $contains: query }
        }))
    };

    // Add additional filters alongside the OR condition
    if (options.status) {
        filters.status = { $in: Array.isArray(options.status) ? options.status : [options.status] };
    }

    if (options.priority) {
        filters.priority = { $in: Array.isArray(options.priority) ? options.priority : [options.priority] };
    }

    return await pt.list({
        entityNames: options.entityTypes || ['task'],
        filters: filters,
        limit: options.limit || 50
    });
}

// Usage examples
const results = await advancedSearch('meeting', {
    fields: ['text', 'description', 'notes'],
    status: ['active', 'pending'],
    priority: ['high', 'medium'],
    limit: 20
});
```

## Filter Best Practices

### 

1. Use $in Instead of Multiple$or Conditions

```JAVASCRIPT
// ✅ GOOD: Efficient multi-value filter
const goodFilter = {
    status: { $in: ['active', 'pending', 'in_progress'] },
    assignee: 'john'
};

// ❌ AVOID: Inefficient $or for same field
const inefficientFilter = {
    $or: [
        { status: 'active' },
        { status: 'pending' },
        { status: 'in_progress' }
    ],
    assignee: 'john'
};
```

### 2. Put Most Selective Filters First

```JAVASCRIPT
// ✅ GOOD: Most selective filter first
const efficientFilter = {
    user_id: 123,              // Very selective
    status: 'active',          // Moderately selective
    category: { $in: ['a', 'b', 'c'] }  // Less selective
};
```

### 3. Combine Filters Effectively

```JAVASCRIPT
// ✅ GOOD: Combining filters effectively
const efficientFilter = {
    completed: "false",           // Most selective first
    priority: { $in: ['high', 'medium'] },
    $or: [                        // $or last and limited
        { assignee: 'john' },
        { status: 'unassigned' }
    ]
};
```

### 4. Use Appropriate Operators

```JAVASCRIPT
// ✅ GOOD: Use exact match when possible
const exactMatch = { status: 'active' };

// ❌ AVOID: Unnecessary pattern matching
const unnecessaryPattern = { status: { $contains: 'active' } };
```

## Data Type Notes

* String values: Boolean and numeric values are stored as strings in JSONB

* Boolean matching: Use `"true"` or `"false"` as strings

* Mixed filters: You can combine exact matches with operators in the same query

* Column-level vs JSONB filters: `creator_user_id` is a database column (not JSONB), making it more efficient

```JAVASCRIPT
// Example showing data types
const filters = {
    completed: "true",          // Boolean as string
    priority: "high",           // String
    age: { $gte: 18 },         // Numeric comparison (stored as string)
    email: { $ilike: '%@gmail.com' }  // Pattern matching
};
```

## Security Features

The filtering system includes built-in security:

* Parameterized queries prevent SQL injection

* Server validates allowed filter fields per entity type

* Input validation for field names, value lengths, and number of filters

* Rate limiting for complex queries

## Debugging Filters

Test your filters incrementally:

```JAVASCRIPT
// Test your filters step by step
async function debugFilters() {
    // Start simple
    console.log('All tasks:', await pt.list({ entityNames: ['task'] }));

    // Add basic filter
    console.log('Active tasks:', await pt.list({
        entityNames: ['task'],
        filters: { status: 'active' }
    }));

    // Test $contains operator
    console.log('Tasks with "urgent":', await pt.list({
        entityNames: ['task'],
        filters: { text: { $contains: 'urgent' } }
    }));

    // Test $in operator
    console.log('High/Medium priority tasks:', await pt.list({
        entityNames: ['task'],
        filters: { priority: { $in: ['high', 'medium'] } }
    }));

    // Test $or operator
    console.log('Urgent or high priority tasks:', await pt.list({
        entityNames: ['task'],
        filters: {
            $or: [
                { text: { $contains: 'urgent' } },
                { priority: 'high' }
            ]
        }
    }));
}
```

## Next Steps

* [Data Management API Reference](data-management-api.html) - Learn about all pt API methods

* [Pagination](pagination.html) - Handle large result sets efficiently

* [Complete Examples](live-pages-examples.html) - See filtering in action

* [Performance and Best Practices](live-pages-best-practices.html) - Optimize your queries



# Pagination

## Overview

Live Pages supports server-side pagination to efficiently handle large datasets. Instead of loading all records at once, pagination loads data in smaller chunks, reducing data transfer and improving performance.

## Pagination Methods

There are two main approaches to pagination:

1. Page-based pagination: Uses `page` and `pageSize` parameters (recommended for UIs)

2. Offset-based pagination: Uses `limit` and `offset` parameters (more flexible)

## Page-Based Pagination

Page-based pagination is the most intuitive approach for user interfaces with "Previous" and "Next" buttons.

### Basic Implementation

```JAVASCRIPT
let currentPage = 1;
let pageSize = 20;
let hasMorePages = false;

async function loadPage(page) {
    const result = await pt.list({
        entityNames: ['task'],
        filters: { status: 'active' },
        page: page,
        pageSize: pageSize,
        returnMetadata: true
    });

    currentPage = page;
    hasMorePages = result.pagination.has_more;

    return result.entities.filter(e => e.entity_name === 'task');
}

// Navigation functions
async function goToNextPage() {
    if (hasMorePages) {
        const tasks = await loadPage(currentPage + 1);
        displayTasks(tasks);
    }
}

async function goToPreviousPage() {
    if (currentPage > 1) {
        const tasks = await loadPage(currentPage - 1);
        displayTasks(tasks);
    }
}

async function goToFirstPage() {
    const tasks = await loadPage(1);
    displayTasks(tasks);
}
```

### Response Structure with Metadata

When `returnMetadata: true` is set, the response includes pagination information:

```JAVASCRIPT
{
    entities: [...],    // Array of entity objects
    count: 20,          // Number of items in this response
    pagination: {
        page: 3,        // Current page number
        page_size: 20,  // Items per page
        has_more: true, // true if more pages exist
        limit: 20,      // Same as page_size
        offset: 40      // Starting position (calculated)
    }
}
```

### Complete Pagination UI Example

```HTML
<div class="container mx-auto p-6">
    <!-- Content Area -->
    <div id="tasksList" class="space-y-4 mb-6">
        <!-- Tasks will be displayed here -->
    </div>

    <!-- Pagination Controls -->
    <div class="flex justify-center items-center gap-4 bg-white rounded-lg shadow-md p-4">
        <button
            id="firstPageBtn"
            onclick="goToFirstPage()"
            class="px-3 py-2 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50"
        >
            First
        </button>
        <button
            id="prevPageBtn"
            onclick="goToPreviousPage()"
            class="px-3 py-2 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50"
        >
            Previous
        </button>
        <span id="pageInfo" class="text-gray-700 font-medium">
            Page 1
        </span>
        <button
            id="nextPageBtn"
            onclick="goToNextPage()"
            class="px-3 py-2 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50"
        >
            Next
        </button>
    </div>
</div>

<script>
let currentPage = 1;
let pageSize = 20;
let hasMorePages = false;
let currentFilters = {};

async function loadPage(page, resetFilters = false) {
    if (resetFilters) {
        currentPage = 1;
        page = 1;
    }

    try {
        const result = await pt.list({
            entityNames: ['task'],
            filters: currentFilters,
            page: page,
            pageSize: pageSize,
            returnMetadata: true
        });

        currentPage = page;
        hasMorePages = result.pagination.has_more;

        const tasks = result.entities.filter(e => e.entity_name === 'task');
        displayTasks(tasks);
        updatePaginationControls();

        return tasks;
    } catch (error) {
        console.error('Error loading page:', error);
        return [];
    }
}

function updatePaginationControls() {
    document.getElementById('pageInfo').textContent = `Page ${currentPage}`;
    document.getElementById('firstPageBtn').disabled = currentPage === 1;
    document.getElementById('prevPageBtn').disabled = currentPage === 1;
    document.getElementById('nextPageBtn').disabled = !hasMorePages;
}

async function goToFirstPage() {
    await loadPage(1);
}

async function goToPreviousPage() {
    if (currentPage > 1) {
        await loadPage(currentPage - 1);
    }
}

async function goToNextPage() {
    if (hasMorePages) {
        await loadPage(currentPage + 1);
    }
}

function displayTasks(tasks) {
    const html = tasks.map(task => `
        <div class="bg-white p-4 rounded shadow">
            <p>${task.data.text}</p>
        </div>
    `).join('');
    document.getElementById('tasksList').innerHTML = html;
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    loadPage(1);
});
</script>
```

## Offset-Based Pagination

Offset-based pagination uses `limit` and `offset` for more flexible control.

### Basic Implementation

```JAVASCRIPT
let currentOffset = 0;
const pageSize = 20;

async function loadPageByOffset(offset) {
    const result = await pt.list({
        entityNames: ['task'],
        filters: { status: 'active' },
        limit: pageSize,
        offset: offset,
        returnMetadata: true
    });

    currentOffset = offset;
    return result.entities.filter(e => e.entity_name === 'task');
}

// Navigate by offset
async function nextPage() {
    const tasks = await loadPageByOffset(currentOffset + pageSize);
    displayTasks(tasks);
}

async function previousPage() {
    if (currentOffset >= pageSize) {
        const tasks = await loadPageByOffset(currentOffset - pageSize);
        displayTasks(tasks);
    }
}

async function jumpToPage(pageNumber) {
    const offset = (pageNumber - 1) * pageSize;
    const tasks = await loadPageByOffset(offset);
    displayTasks(tasks);
}
```

## Pagination with Filtering

Maintain pagination state when filters change:

```JAVASCRIPT
class PaginatedList {
    constructor(pageSize = 20) {
        this.pageSize = pageSize;
        this.currentPage = 1;
        this.currentFilters = {};
        this.hasMore = false;
    }

    async search(filters, resetPage = true) {
        if (resetPage) {
            this.currentPage = 1;
        }

        this.currentFilters = filters;
        return await this.loadCurrentPage();
    }

    async loadCurrentPage() {
        const result = await pt.list({
            entityNames: ['task'],
            filters: this.currentFilters,
            page: this.currentPage,
            pageSize: this.pageSize,
            returnMetadata: true
        });

        this.hasMore = result.pagination.has_more;
        return result.entities.filter(e => e.entity_name === 'task');
    }

    async nextPage() {
        if (this.hasMore) {
            this.currentPage++;
            return await this.loadCurrentPage();
        }
        return [];
    }

    async previousPage() {
        if (this.currentPage > 1) {
            this.currentPage--;
            return await this.loadCurrentPage();
        }
        return [];
    }

    async goToPage(page) {
        this.currentPage = page;
        return await this.loadCurrentPage();
    }
}

// Usage
const taskList = new PaginatedList(20);

// Load first page
await taskList.search({ status: 'active' });

// Change filters (resets to page 1)
await taskList.search({ status: 'active', priority: 'high' });

// Navigate pages (maintains filters)
await taskList.nextPage();
await taskList.previousPage();
```

## Infinite Scroll

Implement infinite scrolling for a seamless user experience:

```JAVASCRIPT
class InfiniteScrollManager {
    constructor(pageSize = 20) {
        this.pageSize = pageSize;
        this.currentPage = 0;
        this.loading = false;
        this.hasMore = true;
        this.allData = [];
    }

    async loadMore(filters = {}) {
        if (this.loading || !this.hasMore) return;

        this.loading = true;
        this.currentPage++;

        try {
            const result = await pt.list({
                entityNames: ['task'],
                filters: filters,
                page: this.currentPage,
                pageSize: this.pageSize,
                returnMetadata: true
            });

            const newData = result.entities.filter(e => e.entity_name === 'task');

            this.hasMore = result.pagination.has_more;
            this.allData = [...this.allData, ...newData];
            this.appendToUI(newData);

            return newData;

        } catch (error) {
            console.error('Error loading more data:', error);
            return [];
        } finally {
            this.loading = false;
        }
    }

    appendToUI(newData) {
        const container = document.getElementById('tasksList');
        newData.forEach(task => {
            const taskElement = document.createElement('div');
            taskElement.className = 'bg-white p-4 rounded shadow mb-2';
            taskElement.textContent = task.data.text;
            container.appendChild(taskElement);
        });
    }

    reset() {
        this.currentPage = 0;
        this.hasMore = true;
        this.allData = [];
        document.getElementById('tasksList').innerHTML = '';
    }
}

// Setup infinite scroll
const infiniteScroll = new InfiniteScrollManager(20);

// Load initial data
await infiniteScroll.loadMore({ status: 'active' });

// Setup scroll listener
window.addEventListener('scroll', () => {
    const scrollPosition = window.innerHeight + window.scrollY;
    const threshold = document.body.offsetHeight - 1000;

    if (scrollPosition >= threshold) {
        infiniteScroll.loadMore({ status: 'active' });
    }
});
```

## Pagination with Caching

Improve performance by caching previously loaded pages:

```JAVASCRIPT
class CachedPaginationManager {
    constructor(pageSize = 20) {
        this.pageSize = pageSize;
        this.currentPage = 1;
        this.currentFilters = {};
        this.cache = new Map();
    }

    async loadPage(page, filters = {}) {
        const cacheKey = this.getCacheKey(page, filters);

        // Check cache first
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }

        // Load from server
        const result = await pt.list({
            entityNames: ['task'],
            filters: filters,
            page: page,
            pageSize: this.pageSize,
            returnMetadata: true
        });

        const data = {
            entities: result.entities.filter(e => e.entity_name === 'task'),
            hasMore: result.pagination.has_more
        };

        // Cache the result
        this.cache.set(cacheKey, data);

        return data;
    }

    getCacheKey(page, filters) {
        return `${page}-${JSON.stringify(filters)}`;
    }

    clearCache() {
        this.cache.clear();
    }

    async search(filters, resetPage = true) {
        if (resetPage) {
            this.currentPage = 1;
        }
        this.currentFilters = filters;
        return await this.loadPage(this.currentPage, filters);
    }

    async nextPage() {
        this.currentPage++;
        return await this.loadPage(this.currentPage, this.currentFilters);
    }

    async previousPage() {
        if (this.currentPage > 1) {
            this.currentPage--;
            return await this.loadPage(this.currentPage, this.currentFilters);
        }
        return await this.loadPage(this.currentPage, this.currentFilters);
    }
}
```

## Cursor-Based Pagination

For very large datasets, cursor-based pagination can be more efficient:

```JAVASCRIPT
class CursorPaginationManager {
    constructor(pageSize = 20) {
        this.pageSize = pageSize;
        this.cursors = []; // Store cursors for each page
        this.currentPage = 0;
    }

    async loadFirstPage(filters = {}) {
        this.currentPage = 1;
        this.cursors = [];

        const result = await pt.list({
            entityNames: ['task'],
            filters: filters,
            limit: this.pageSize,
            offset: 0,
            returnMetadata: true
        });

        const data = result.entities.filter(e => e.entity_name === 'task');

        if (data.length > 0) {
            // Store the last item's ID as cursor for next page
            this.cursors[1] = data[data.length - 1].id;
        }

        return {
            data,
            hasMore: result.pagination.has_more
        };
    }

    async loadNextPage(filters = {}) {
        if (this.cursors[this.currentPage]) {
            this.currentPage++;

            // Use the cursor to get items after the last loaded item
            const enhancedFilters = {
                ...filters,
                id: { $gt: this.cursors[this.currentPage - 1] }
            };

            const result = await pt.list({
                entityNames: ['task'],
                filters: enhancedFilters,
                limit: this.pageSize,
                offset: 0,
                returnMetadata: true
            });

            const data = result.entities.filter(e => e.entity_name === 'task');

            if (data.length > 0) {
                this.cursors[this.currentPage] = data[data.length - 1].id;
            }

            return {
                data,
                hasMore: result.pagination.has_more
            };
        }

        return { data: [], hasMore: false };
    }
}

// Usage
const cursor = new CursorPaginationManager(20);
const firstPage = await cursor.loadFirstPage({ status: 'active' });
const secondPage = await cursor.loadNextPage({ status: 'active' });
```

## Pagination Best Practices

### 1. Use Reasonable Page Sizes

```JAVASCRIPT
// ✅ GOOD: Reasonable page size
const OPTIMAL_PAGE_SIZE = 20;

// ❌ AVOID: Too large
const TOO_LARGE = 1000;

// ❌ AVOID: Too small (too many requests)
const TOO_SMALL = 5;
```

### 2. Reset to Page 1 When Filters Change

```JAVASCRIPT
// ✅ GOOD: Reset page when filters change
async function applyFilters(newFilters) {
    currentPage = 1;
    currentFilters = newFilters;
    await loadPage(currentPage);
}
```

### 3. Show Loading States

```JAVASCRIPT
async function loadPage(page) {
    // Show loading indicator
    document.getElementById('loading').style.display = 'block';

    try {
        const result = await pt.list({
            entityNames: ['task'],
            page: page,
            pageSize: 20,
            returnMetadata: true
        });

        displayTasks(result.entities);
    } finally {
        // Hide loading indicator
        document.getElementById('loading').style.display = 'none';
    }
}
```

### 4. Disable Navigation During Loading

```JAVASCRIPT
let isLoading = false;

async function loadPage(page) {
    if (isLoading) return;

    isLoading = true;
    updateButtonStates();

    try {
        const result = await pt.list({
            entityNames: ['task'],
            page: page,
            pageSize: 20,
            returnMetadata: true
        });

        displayTasks(result.entities);
    } finally {
        isLoading = false;
        updateButtonStates();
    }
}

function updateButtonStates() {
    const buttons = document.querySelectorAll('.pagination-btn');
    buttons.forEach(btn => {
        btn.disabled = isLoading;
    });
}
```

### 5. Handle Empty Results

```JAVASCRIPT
function displayTasks(tasks) {
    const container = document.getElementById('tasksList');

    if (tasks.length === 0) {
        container.innerHTML = `
            <div class="text-center py-8 text-gray-500">
                No tasks found
            </div>
        `;
        return;
    }

    container.innerHTML = tasks.map(task => `
        <div class="bg-white p-4 rounded shadow mb-2">
            ${task.data.text}
        </div>
    `).join('');
}
```

## Performance Optimization

### 1. Avoid Offset for Very Large Datasets

For datasets with millions of records, offset-based pagination can become slow:

```JAVASCRIPT
// ❌ SLOW: Large offset
const result = await pt.list({
    entityNames: ['task'],
    limit: 20,
    offset: 1000000  // Very slow for large offsets
});

// ✅ BETTER: Use cursor-based pagination
const result = await pt.list({
    entityNames: ['task'],
    filters: { id: { $gt: lastSeenId } },
    limit: 20
});
```

### 2. Cache Page Results

```JAVASCRIPT
const pageCache = new Map();
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes

async function loadPageWithCache(page, filters) {
    const cacheKey = `${page}-${JSON.stringify(filters)}`;
    const cached = pageCache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
        return cached.data;
    }

    const result = await pt.list({
        entityNames: ['task'],
        filters: filters,
        page: page,
        pageSize: 20,
        returnMetadata: true
    });

    pageCache.set(cacheKey, {
        data: result,
        timestamp: Date.now()
    });

    return result;
}
```

### 3. Prefetch Next Page

```JAVASCRIPT
async function loadPageWithPrefetch(page, filters) {
    // Load current page
    const currentPromise = pt.list({
        entityNames: ['task'],
        filters: filters,
        page: page,
        pageSize: 20,
        returnMetadata: true
    });

    // Prefetch next page in parallel
    const nextPromise = pt.list({
        entityNames: ['task'],
        filters: filters,
        page: page + 1,
        pageSize: 20,
        returnMetadata: true
    });

    // Wait for current page
    const current = await currentPromise;

    // Cache next page result
    nextPromise.then(result => {
        cachePageResult(page + 1, filters, result);
    });

    return current;
}
```

## Next Steps

* [Filtering and Querying](filtering-and-querying.html) - Combine pagination with filters

* [Data Management API Reference](data-management-api.html) - Learn about all pt API methods

* [Complete Examples](live-pages-examples.html) - See pagination in full applications

* [Performance and Best Practices](live-pages-best-practices.html) - Optimize your Live Pages



# Working with Chat Members

## Overview

Live Pages can access information about all members in the current chat, including both human users and AI agents. This enables features like task assignment, creator tracking, and user-specific filtering.

## Getting Chat Members

Use `pt.getChatMembers()` to retrieve all chat members:

```JAVASCRIPT
const members = await pt.getChatMembers();
```

### Response Structure

Each member object has a clear, consistent structure:

```JAVASCRIPT
[
  {
    "id": 123,              // Member ID (user_id or agent_id)
    "type": "user",         // "user" or "agent"
    "name": "John Doe",     // Display name

    // User-specific fields (null for agents)
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",

    "is_owner": true        // Created the chat
  },
  {
    "id": 456,
    "type": "agent",
    "name": "AI Assistant",

    // These are null for agents
    "first_name": null,
    "last_name": null,
    "email": null,

    "is_owner": false
  }
]
```

### Field Reference

| Field |Type |User |Agent |Description |
-----------------------------------------
| id |number |✓ |✓ |Member ID |
| type |string |✓ |✓ |"user" or "agent" |
| name |string |✓ |✓ |Display name (full name or agent name) |
| first_name |string? |✓ |null |First name |
| last_name |string? |✓ |null |Last name |
| email |string? |✓ |null |Email address |
| is_owner |boolean |✓ |✓ |Created the chat |

## Common Use Cases

### 1. Display All Members

```JAVASCRIPT
// Get and display all members
const members = await pt.getChatMembers();
members.forEach(member => {
    const icon = member.type === 'user' ? '👤' : '🤖';
    console.log(`${icon} ${member.name}`);
});

// Filter by type
const users = members.filter(m => m.type === 'user');
const agents = members.filter(m => m.type === 'agent');
console.log(`Users: ${users.length}, Agents: ${agents.length}`);
```

### 2. Create Member List UI

```JAVASCRIPT
async function renderMemberList() {
    const members = await pt.getChatMembers();

    const html = members.map(member => {
        const icon = member.type === 'user' ? '👤' : '🤖';
        const badge = member.is_owner ? '<span class="owner-badge">Owner</span>' : '';
        const details = member.email ? `<small>${member.email}</small>` : '';

        return `
            <div class="member-card ${member.type}">
                ${icon} <strong>${member.name}</strong> ${badge}
                ${details}
            </div>
        `;
    }).join('');

    document.getElementById('memberList').innerHTML = html;
}
```

### 3. Get Current User

```JAVASCRIPT
// Identify the current user (usually the owner)
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.type === 'user' && m.is_owner);

console.log(`Current user: ${currentUser.name}`);
console.log(`User ID: ${currentUser.id}`);
```

### 4. Create Assignment Dropdown

```JAVASCRIPT
async function createAssigneeDropdown() {
    const members = await pt.getChatMembers();
    const users = members.filter(m => m.type === 'user');

    const options = users
        .map(m => `<option value="${m.id}">${m.name}</option>`)
        .join('');

    return `
        <select id="assignee" class="px-3 py-2 border rounded">
            <option value="">Unassigned</option>
            ${options}
        </select>
    `;
}

// Usage
document.getElementById('assigneeContainer').innerHTML = await createAssigneeDropdown();
```

## Task Assignment Example

A complete example of task assignment with chat members:

```HTML
<div class="container mx-auto p-6">
    <h1 class="text-2xl font-bold mb-4">Team Tasks</h1>

    <!-- Filter by Assignee -->
    <div class="mb-4">
        <label class="block text-sm font-medium mb-1">Filter by Assignee:</label>
        <select id="assigneeFilter" onchange="filterTasks()" class="px-3 py-2 border rounded">
            <option value="">All Tasks</option>
            <option value="unassigned">Unassigned</option>
        </select>
    </div>

    <!-- Add New Task -->
    <div class="bg-white rounded-lg shadow p-4 mb-4">
        <input
            type="text"
            id="taskInput"
            placeholder="New task..."
            class="w-full px-3 py-2 border rounded mb-2"
        >
        <div class="flex gap-2">
            <select id="taskAssignee" class="flex-1 px-3 py-2 border rounded">
                <option value="">Unassigned</option>
            </select>
            <button onclick="addTask()" class="bg-blue-500 text-white px-4 py-2 rounded">
                Add Task
            </button>
        </div>
    </div>

    <!-- Task List -->
    <div id="tasksList"></div>
</div>

<script>
let allMembers = [];
let allTasks = [];

// Initialize
async function init() {
    // Load members first
    allMembers = await pt.getChatMembers();

    // Populate dropdowns
    populateAssigneeDropdowns();

    // Load tasks
    await loadTasks();
}

function populateAssigneeDropdowns() {
    const users = allMembers.filter(m => m.type === 'user');

    const options = users
        .map(m => `<option value="${m.id}">${m.name}</option>`)
        .join('');

    // Populate filter dropdown
    const filterSelect = document.getElementById('assigneeFilter');
    const existingOptions = filterSelect.innerHTML;
    filterSelect.innerHTML = existingOptions + options;

    // Populate assignment dropdown
    document.getElementById('taskAssignee').innerHTML =
        '<option value="">Unassigned</option>' + options;
}

async function loadTasks() {
    const entities = await pt.list({
        entityNames: ['task'],
        filters: { completed: false }
    });

    allTasks = entities.filter(e => e.entity_name === 'task');
    displayTasks(allTasks);
}

async function filterTasks() {
    const assigneeId = document.getElementById('assigneeFilter').value;

    if (assigneeId === '') {
        // Show all tasks
        displayTasks(allTasks);
    } else if (assigneeId === 'unassigned') {
        // Show unassigned tasks
        const filtered = allTasks.filter(task => !task.data.assignee_id);
        displayTasks(filtered);
    } else {
        // Show tasks for specific user
        const filtered = allTasks.filter(task =>
            task.data.assignee_id === parseInt(assigneeId)
        );
        displayTasks(filtered);
    }
}

function displayTasks(tasks) {
    const html = tasks.map(task => {
        const assignee = allMembers.find(m => m.id === task.data.assignee_id);
        const assigneeName = assignee ? assignee.name : 'Unassigned';

        const creator = allMembers.find(m => m.id === task.creator_user_id);
        const creatorName = creator ? creator.name : 'Unknown';

        return `
            <div class="bg-white rounded-lg shadow p-4 mb-2">
                <div class="flex justify-between items-start">
                    <div>
                        <p class="font-medium">${task.data.text}</p>
                        <p class="text-sm text-gray-600">
                            Assigned to: ${assigneeName}
                        </p>
                        <p class="text-xs text-gray-400">
                            Created by: ${creatorName}
                        </p>
                    </div>
                    <div class="flex gap-2">
                        <button
                            onclick="reassignTask(${task.id})"
                            class="text-blue-500 text-sm"
                        >
                            Reassign
                        </button>
                        <button
                            onclick="deleteTask(${task.id})"
                            class="text-red-500 text-sm"
                        >
                            Delete
                        </button>
                    </div>
                </div>
            </div>
        `;
    }).join('');

    document.getElementById('tasksList').innerHTML = html ||
        '<p class="text-gray-500 text-center">No tasks found</p>';
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    const assigneeId = document.getElementById('taskAssignee').value;

    if (!text) return;

    await pt.add('task', {
        text: text,
        completed: false,
        assignee_id: assigneeId ? parseInt(assigneeId) : null
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function reassignTask(taskId) {
    const task = await pt.get(taskId);
    const newAssigneeId = prompt('Enter new assignee ID (or leave empty for unassigned):');

    await pt.edit(taskId, {
        ...task.data,
        assignee_id: newAssigneeId ? parseInt(newAssigneeId) : null
    });

    await loadTasks();
}

async function deleteTask(taskId) {
    if (confirm('Delete this task?')) {
        await pt.delete(taskId);
        await loadTasks();
    }
}

// Initialize on page load
document.addEventListener('DOMContentLoaded', init);
</script>
```

## Filtering by Creator

Every entity has a `creator_user_id` field that tracks who created it. This is a column-level filter (not JSONB data), making it efficient:

### Basic Creator Filtering

```JAVASCRIPT
// Get tasks created by current user
const members = await pt.getChatMembers();
const currentUser = members.find(m => m.type === 'user' && m.is_owner);

const myTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: currentUser.id
    }
});
```

### Advanced Creator Filtering

```JAVASCRIPT
// Get tasks created by multiple users
const teamIds = [123, 456, 789];
const teamTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: { $in: teamIds }
    }
});

// Get tasks NOT created by current user
const othersTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: { $ne: currentUser.id }
    }
});

// Combine creator filter with data filters
const myActiveTasks = await pt.list({
    entityNames: ['task'],
    filters: {
        creator_user_id: currentUser.id,  // Column-level filter
        completed: false,                  // JSONB data filter
        priority: { $in: ['high', 'medium'] }
    }
});
```

### "My Tasks" Toggle

```JAVASCRIPT
let allMembers = [];
let currentUser = null;
let showOnlyMyTasks = false;

async function init() {
    allMembers = await pt.getChatMembers();
    currentUser = allMembers.find(m => m.type === 'user' && m.is_owner);
    await loadTasks();
}

async function loadTasks() {
    const filters = { completed: false };

    if (showOnlyMyTasks && currentUser) {
        filters.creator_user_id = currentUser.id;
    }

    const entities = await pt.list({
        entityNames: ['task'],
        filters: filters
    });

    displayTasks(entities.filter(e => e.entity_name === 'task'));
}

function toggleMyTasks() {
    showOnlyMyTasks = !showOnlyMyTasks;
    loadTasks();
}
```

## Per-User Settings Example

Store personal preferences using `creator_user_id`:

```JAVASCRIPT
// Save user-specific settings
async function saveUserSettings(settings) {
    const members = await pt.getChatMembers();
    const currentUser = members.find(m => m.is_owner);

    // Check if user already has settings
    const existing = await pt.list({
        entityNames: ['user_settings'],
        filters: { creator_user_id: currentUser.id }
    });

    if (existing.length > 0) {
        // Update existing settings
        await pt.edit(existing[0].id, settings);
    } else {
        // Create new settings (creator_user_id is set automatically)
        await pt.add('user_settings', settings);
    }
}

// Load user-specific settings
async function loadUserSettings() {
    const members = await pt.getChatMembers();
    const currentUser = members.find(m => m.is_owner);

    const settings = await pt.list({
        entityNames: ['user_settings'],
        filters: { creator_user_id: currentUser.id }
    });

    return settings.length > 0 ? settings[0].data : getDefaultSettings();
}

function getDefaultSettings() {
    return {
        theme: 'light',
        pageSize: 20,
        defaultView: 'list'
    };
}
```

## Best Practices

### 1. Cache Members at Initialization

```JAVASCRIPT
// ✅ GOOD: Load once at app start
let allMembers = [];

async function initApp() {
    allMembers = await pt.getChatMembers();
    await loadTasks();
}

function getMemberName(userId) {
    const member = allMembers.find(m => m.id === userId);
    return member ? member.name : 'Unknown';
}

// ❌ AVOID: Calling getChatMembers() repeatedly
async function displayTask(task) {
    const members = await pt.getChatMembers(); // Called for every task!
    const creator = members.find(m => m.id === task.creator_user_id);
    return creator.name;
}
```

### 2. Handle Missing Members Gracefully

```JAVASCRIPT
function getMemberName(userId) {
    const member = allMembers.find(m => m.id === userId);
    return member ? member.name : 'Unknown User';
}

function displayTaskCreator(task) {
    const creator = allMembers.find(m => m.id === task.creator_user_id);

    if (!creator) {
        return '<span class="text-gray-400">Unknown</span>';
    }

    const icon = creator.type === 'user' ? '👤' : '🤖';
    return `${icon} ${creator.name}`;
}
```

### 3. Separate Users and Agents

```JAVASCRIPT
// Get only human users for assignment
const users = allMembers.filter(m => m.type === 'user');

// Get only agents
const agents = allMembers.filter(m => m.type === 'agent');

// Display differently
function renderMemberBadge(member) {
    if (member.type === 'user') {
        return `
            <div class="user-badge">
                👤 ${member.name}
                ${member.email ? `<small>${member.email}</small>` : ''}
            </div>
        `;
    } else {
        return `
            <div class="agent-badge">
                🤖 ${member.name}
            </div>
        `;
    }
}
```

### 4. Use Meaningful Variable Names

```JAVASCRIPT
// ✅ GOOD: Clear variable names
const chatMembers = await pt.getChatMembers();
const humanUsers = chatMembers.filter(m => m.type === 'user');
const aiAgents = chatMembers.filter(m => m.type === 'agent');
const chatOwner = chatMembers.find(m => m.is_owner);

// ❌ AVOID: Unclear names
const m = await pt.getChatMembers();
const u = m.filter(x => x.type === 'user');
```

## Advanced Patterns

### Team Dashboard

```JAVASCRIPT
async function createTeamDashboard() {
    const members = await pt.getChatMembers();
    const users = members.filter(m => m.type === 'user');

    // Get all tasks
    const allTasks = await pt.list({
        entityNames: ['task'],
        filters: { completed: false }
    });

    // Calculate stats per user
    const stats = users.map(user => {
        const userTasks = allTasks.filter(t => t.data.assignee_id === user.id);
        const createdTasks = allTasks.filter(t => t.creator_user_id === user.id);

        return {
            user: user,
            assigned: userTasks.length,
            created: createdTasks.length,
            highPriority: userTasks.filter(t => t.data.priority === 'high').length
        };
    });

    // Display dashboard
    displayTeamStats(stats);
}

function displayTeamStats(stats) {
    const html = stats.map(stat => `
        <div class="bg-white rounded-lg shadow p-4">
            <h3 class="font-bold">${stat.user.name}</h3>
            <p>Assigned: ${stat.assigned}</p>
            <p>Created: ${stat.created}</p>
            <p>High Priority: ${stat.highPriority}</p>
        </div>
    `).join('');

    document.getElementById('teamStats').innerHTML = html;
}
```

### User Activity Log

```JAVASCRIPT
async function getUserActivity(userId) {
    // Get all entities created by user
    const created = await pt.list({
        entityNames: ['task', 'note', 'event'],
        filters: { creator_user_id: userId },
        limit: 100
    });

    // Get member info
    const members = await pt.getChatMembers();
    const user = members.find(m => m.id === userId);

    return {
        user: user,
        activities: created.map(entity => ({
            type: entity.entity_name,
            created: entity.created_at,
            data: entity.data
        }))
    };
}
```

## Next Steps

* [Data Management API Reference](data-management-api.html) - Learn about all pt API methods

* [Filtering and Querying](filtering-and-querying.html) - Filter entities by creator and other fields

* [Complete Examples](live-pages-examples.html) - See member integration in full apps

* [Performance and Best Practices](live-pages-best-practices.html) - Optimize your Live Pages



# Styling with Tailwind CSS

## Overview

Live Pages have full support for Tailwind CSS, a utility-first CSS framework that allows you to rapidly build modern user interfaces. Tailwind CSS is pre-loaded and available for use without any additional setup.

## Benefits of Tailwind in Live Pages

* Rapid prototyping: Build interfaces quickly without writing custom CSS

* Consistent design: Use Tailwind's design system for cohesive styling

* Responsive by default: Built-in responsive utilities

* No CSS conflicts: Utility classes eliminate CSS specificity issues

* No setup required: Tailwind is automatically available

## Basic Usage

Simply use Tailwind utility classes in your HTML:

```HTML
<div class="bg-blue-500 text-white p-4 rounded-lg shadow-md">
    <h2 class="text-xl font-bold mb-2">Welcome</h2>
    <p class="text-blue-100">This is styled with Tailwind CSS</p>
</div>
```

## Common Utility Classes

### Layout

```HTML
<!-- Container -->
<div class="container mx-auto p-6">
    Content centered with padding
</div>

<!-- Flexbox -->
<div class="flex justify-between items-center gap-4">
    <div>Left</div>
    <div>Right</div>
</div>

<!-- Grid -->
<div class="grid grid-cols-3 gap-4">
    <div>Column 1</div>
    <div>Column 2</div>
    <div>Column 3</div>
</div>

<!-- Spacing -->
<div class="p-4 m-2">      <!-- Padding 1rem, Margin 0.5rem -->
<div class="px-6 py-3">    <!-- Horizontal/Vertical padding -->
<div class="mt-4 mb-2">    <!-- Margin top/bottom -->
```

### Typography

```HTML
<!-- Text Size -->
<h1 class="text-4xl">Large Heading</h1>
<h2 class="text-2xl">Medium Heading</h2>
<p class="text-base">Normal text</p>
<small class="text-sm">Small text</small>

<!-- Text Weight & Style -->
<p class="font-bold">Bold text</p>
<p class="font-semibold">Semi-bold text</p>
<p class="font-normal">Normal text</p>
<p class="italic">Italic text</p>

<!-- Text Color -->
<p class="text-gray-900">Dark gray</p>
<p class="text-blue-500">Blue</p>
<p class="text-red-600">Red</p>

<!-- Text Alignment -->
<p class="text-left">Left aligned</p>
<p class="text-center">Centered</p>
<p class="text-right">Right aligned</p>
```

### Colors

```HTML
<!-- Background Colors -->
<div class="bg-blue-500">Blue background</div>
<div class="bg-gray-100">Light gray background</div>
<div class="bg-red-600">Red background</div>

<!-- Text Colors -->
<p class="text-blue-500">Blue text</p>
<p class="text-gray-700">Gray text</p>

<!-- Border Colors -->
<div class="border border-blue-500">Blue border</div>
```

### Borders & Shadows

```HTML
<!-- Borders -->
<div class="border">Simple border</div>
<div class="border-2">Thicker border</div>
<div class="border-t">Top border only</div>

<!-- Border Radius -->
<div class="rounded">Slightly rounded</div>
<div class="rounded-lg">More rounded</div>
<div class="rounded-full">Fully rounded (pill shape)</div>

<!-- Shadows -->
<div class="shadow">Small shadow</div>
<div class="shadow-md">Medium shadow</div>
<div class="shadow-lg">Large shadow</div>
```

## Component Examples

### Cards

```HTML
<!-- Simple Card -->
<div class="bg-white rounded-lg shadow-md p-6">
    <h3 class="text-lg font-semibold text-gray-800 mb-2">Card Title</h3>
    <p class="text-gray-600">Card content goes here.</p>
</div>

<!-- Card with Image -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
    <img src="image.jpg" alt="Card image" class="w-full h-48 object-cover">
    <div class="p-6">
        <h3 class="text-lg font-semibold mb-2">Card with Image</h3>
        <p class="text-gray-600">Description text here.</p>
    </div>
</div>

<!-- Card with Actions -->
<div class="bg-white rounded-lg shadow-md overflow-hidden">
    <div class="p-6">
        <h3 class="text-lg font-semibold text-gray-800 mb-2">Task Item</h3>
        <p class="text-gray-600 mb-4">Task description here.</p>
        <div class="flex gap-2">
            <button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600">
                Edit
            </button>
            <button class="bg-red-500 text-white px-3 py-1 rounded text-sm hover:bg-red-600">
                Delete
            </button>
        </div>
    </div>
</div>
```

### Buttons

```HTML
<!-- Primary Button -->
<button class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
    Primary Button
</button>

<!-- Secondary Button -->
<button class="bg-gray-200 text-gray-800 px-4 py-2 rounded-md hover:bg-gray-300">
    Secondary Button
</button>

<!-- Danger Button -->
<button class="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700">
    Delete
</button>

<!-- Small Button -->
<button class="bg-blue-500 text-white px-3 py-1 rounded text-sm hover:bg-blue-600">
    Small
</button>

<!-- Disabled Button -->
<button class="bg-gray-400 text-white px-4 py-2 rounded-md cursor-not-allowed opacity-50" disabled>
    Disabled
</button>

<!-- Button Group -->
<div class="flex gap-2">
    <button class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">
        Save
    </button>
    <button class="bg-gray-200 text-gray-800 px-4 py-2 rounded-md hover:bg-gray-300">
        Cancel
    </button>
</div>
```

### Forms

```HTML
<!-- Text Input -->
<div class="mb-4">
    <label class="block text-sm font-medium text-gray-700 mb-1">
        Name
    </label>
    <input
        type="text"
        class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
        placeholder="Enter name"
    >
</div>

<!-- Textarea -->
<div class="mb-4">
    <label class="block text-sm font-medium text-gray-700 mb-1">
        Description
    </label>
    <textarea
        rows="4"
        class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
        placeholder="Enter description"
    ></textarea>
</div>

<!-- Select -->
<div class="mb-4">
    <label class="block text-sm font-medium text-gray-700 mb-1">
        Priority
    </label>
    <select class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
        <option>Low</option>
        <option>Medium</option>
        <option>High</option>
    </select>
</div>

<!-- Checkbox -->
<label class="flex items-center">
    <input type="checkbox" class="h-4 w-4 text-blue-600 rounded">
    <span class="ml-2 text-sm text-gray-700">I agree to the terms</span>
</label>

<!-- Radio Buttons -->
<div class="space-y-2">
    <label class="flex items-center">
        <input type="radio" name="option" class="h-4 w-4 text-blue-600">
        <span class="ml-2 text-sm text-gray-700">Option 1</span>
    </label>
    <label class="flex items-center">
        <input type="radio" name="option" class="h-4 w-4 text-blue-600">
        <span class="ml-2 text-sm text-gray-700">Option 2</span>
    </label>
</div>
```

### Lists

```HTML
<!-- Simple List -->
<ul class="space-y-2">
    <li class="bg-white p-4 rounded shadow">Item 1</li>
    <li class="bg-white p-4 rounded shadow">Item 2</li>
    <li class="bg-white p-4 rounded shadow">Item 3</li>
</ul>

<!-- List with Actions -->
<div class="space-y-2">
    <div class="bg-white p-4 rounded shadow flex justify-between items-center">
        <span>Task item</span>
        <div class="flex gap-2">
            <button class="text-blue-500 text-sm">Edit</button>
            <button class="text-red-500 text-sm">Delete</button>
        </div>
    </div>
</div>

<!-- Striped List -->
<div class="divide-y divide-gray-200">
    <div class="p-4 hover:bg-gray-50">Item 1</div>
    <div class="p-4 hover:bg-gray-50">Item 2</div>
    <div class="p-4 hover:bg-gray-50">Item 3</div>
</div>
```

### Badges & Tags

```HTML
<!-- Status Badges -->
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">
    Active
</span>
<span class="px-2 py-1 text-xs rounded-full bg-red-100 text-red-800">
    Inactive
</span>
<span class="px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800">
    Pending
</span>

<!-- Priority Tags -->
<span class="px-2 py-1 text-xs rounded-full bg-red-100 text-red-800">
    High Priority
</span>
<span class="px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800">
    Medium Priority
</span>
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">
    Low Priority
</span>
```

### Alerts & Notifications

```HTML
<!-- Success Alert -->
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
    <strong class="font-bold">Success!</strong>
    <span class="block sm:inline">Task created successfully.</span>
</div>

<!-- Error Alert -->
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
    <strong class="font-bold">Error!</strong>
    <span class="block sm:inline">Something went wrong.</span>
</div>

<!-- Warning Alert -->
<div class="bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded mb-4">
    <strong class="font-bold">Warning!</strong>
    <span class="block sm:inline">Please review your changes.</span>
</div>

<!-- Info Alert -->
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded mb-4">
    <strong class="font-bold">Info:</strong>
    <span class="block sm:inline">This is an informational message.</span>
</div>
```

### Modal/Dialog

```HTML
<!-- Modal Overlay -->
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
    <!-- Modal Content -->
    <div class="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
        <h3 class="text-lg font-semibold mb-4">Confirm Delete</h3>
        <p class="text-gray-600 mb-6">Are you sure you want to delete this item?</p>
        <div class="flex justify-end gap-2">
            <button class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300">
                Cancel
            </button>
            <button class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
                Delete
            </button>
        </div>
    </div>
</div>
```

## Responsive Design

Tailwind makes responsive design simple with breakpoint prefixes:

* `sm:` - Small screens (640px+)

* `md:` - Medium screens (768px+)

* `lg:` - Large screens (1024px+)

* `xl:` - Extra large screens (1280px+)

```HTML
<!-- Responsive Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    <div class="bg-white p-6 rounded-lg shadow">Card 1</div>
    <div class="bg-white p-6 rounded-lg shadow">Card 2</div>
    <div class="bg-white p-6 rounded-lg shadow">Card 3</div>
</div>

<!-- Responsive Text -->
<h1 class="text-2xl md:text-3xl lg:text-4xl font-bold">
    Responsive Heading
</h1>

<!-- Hide/Show on Different Screens -->
<div class="block md:hidden">
    Mobile only
</div>
<div class="hidden md:block">
    Desktop only
</div>

<!-- Responsive Padding -->
<div class="p-4 md:p-6 lg:p-8">
    Responsive padding
</div>
```

## Layout Patterns

### Two-Column Layout

```HTML
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
    <!-- Sidebar -->
    <div class="lg:col-span-1">
        <div class="bg-white rounded-lg shadow p-6">
            <h2 class="text-lg font-bold mb-4">Sidebar</h2>
            <!-- Sidebar content -->
        </div>
    </div>

    <!-- Main Content -->
    <div class="lg:col-span-2">
        <div class="bg-white rounded-lg shadow p-6">
            <h2 class="text-lg font-bold mb-4">Main Content</h2>
            <!-- Main content -->
        </div>
    </div>
</div>
```

### Header with Navigation

```HTML
<header class="bg-white shadow">
    <div class="container mx-auto px-6 py-4">
        <div class="flex justify-between items-center">
            <h1 class="text-2xl font-bold text-gray-800">My App</h1>
            <nav class="flex gap-4">
                <a href="#" class="text-gray-600 hover:text-gray-900">Home</a>
                <a href="#" class="text-gray-600 hover:text-gray-900">About</a>
                <a href="#" class="text-gray-600 hover:text-gray-900">Contact</a>
            </nav>
        </div>
    </div>
</header>
```

### Full Page Layout

```HTML
<div class="min-h-screen bg-gray-100">
    <!-- Header -->
    <header class="bg-white shadow">
        <div class="container mx-auto px-6 py-4">
            <h1 class="text-2xl font-bold">My Application</h1>
        </div>
    </header>

    <!-- Main Content -->
    <main class="container mx-auto px-6 py-8">
        <div class="bg-white rounded-lg shadow p-6">
            <!-- Your content here -->
        </div>
    </main>

    <!-- Footer -->
    <footer class="bg-gray-800 text-white mt-8">
        <div class="container mx-auto px-6 py-4 text-center">
            <p>&copy; 2024 My Application</p>
        </div>
    </footer>
</div>
```

## Interactive States

### Hover Effects

```HTML
<!-- Hover Background -->
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
    Hover me
</button>

<!-- Hover Scale -->
<div class="transform hover:scale-105 transition duration-200">
    Scales on hover
</div>

<!-- Hover Shadow -->
<div class="shadow hover:shadow-lg transition duration-200">
    Shadow increases on hover
</div>
```

### Focus States

```HTML
<input
    type="text"
    class="border border-gray-300 px-3 py-2 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
```

### Transitions

```HTML
<!-- Smooth Transition -->
<button class="bg-blue-500 hover:bg-blue-600 transition duration-300">
    Smooth transition
</button>

<!-- Multiple Properties -->
<div class="transform hover:scale-110 hover:bg-blue-500 transition-all duration-300">
    Multiple transitions
</div>
```

## Best Practices

### 1. Use Consistent Spacing

Stick to Tailwind's spacing scale:

```HTML
<!-- ✅ GOOD: Consistent spacing -->
<div class="p-4 m-2">
<div class="p-6 m-4">
<div class="p-8 m-6">

<!-- ❌ AVOID: Arbitrary values -->
<div style="padding: 17px; margin: 13px">
```

### 2. Maintain Color Consistency

Use Tailwind's color palette:

```HTML
<!-- ✅ GOOD: Consistent colors -->
<div class="bg-blue-500 text-white">
<div class="bg-blue-600 text-white">
<div class="bg-blue-700 text-white">

<!-- ❌ AVOID: Custom colors -->
<div style="background: #1E3F8F">
```

### 3. Mobile-First Approach

Start with mobile styles, then add breakpoints:

```HTML
<!-- ✅ GOOD: Mobile-first -->
<div class="text-sm md:text-base lg:text-lg">
    Responsive text
</div>

<!-- ❌ AVOID: Desktop-first -->
<div class="text-lg md:text-sm">
    Wrong approach
</div>
```

### 4. Group Related Classes

```HTML
<!-- ✅ GOOD: Logical grouping -->
<button class="
    bg-blue-500 hover:bg-blue-600
    text-white
    px-4 py-2
    rounded-md
    shadow-md hover:shadow-lg
    transition duration-200
">
    Well organized
</button>
```

### 5. Use Semantic Class Names for Complex Components

For reusable components, consider wrapper elements:

```HTML
<div class="task-card">
    <div class="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition">
        <!-- Card content -->
    </div>
</div>
```

## Common Color Schemes

### Blue Theme

```HTML
<div class="bg-blue-500 text-white p-4 rounded">Primary</div>
<div class="bg-blue-600 text-white p-4 rounded">Darker</div>
<div class="bg-blue-100 text-blue-800 p-4 rounded">Light</div>
```

### Success/Error/Warning

```HTML
<!-- Success (Green) -->
<div class="bg-green-500 text-white p-4 rounded">Success</div>

<!-- Error (Red) -->
<div class="bg-red-500 text-white p-4 rounded">Error</div>

<!-- Warning (Yellow) -->
<div class="bg-yellow-500 text-white p-4 rounded">Warning</div>

<!-- Info (Blue) -->
<div class="bg-blue-500 text-white p-4 rounded">Info</div>
```

### Grayscale

```HTML
<div class="bg-gray-50 p-4">Very light gray</div>
<div class="bg-gray-100 p-4">Light gray</div>
<div class="bg-gray-200 p-4">Gray</div>
<div class="bg-gray-700 text-white p-4">Dark gray</div>
<div class="bg-gray-900 text-white p-4">Very dark gray</div>
```

## Quick Reference

### Spacing Scale

* `p-1` = 0.25rem (4px)

* `p-2` = 0.5rem (8px)

* `p-4` = 1rem (16px)

* `p-6` = 1.5rem (24px)

* `p-8` = 2rem (32px)

### Common Combinations

```HTML
<!-- Card -->
<div class="bg-white rounded-lg shadow-md p-6">

<!-- Button -->
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">

<!-- Input -->
<input class="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500">

<!-- Badge -->
<span class="px-2 py-1 text-xs rounded-full bg-blue-100 text-blue-800">
```

## Resources

* Tailwind CSS Documentation: https://tailwindcss.com/docs

* Tailwind UI Components: https://tailwindui.com/components

* Color Reference: https://tailwindcss.com/docs/customizing-colors

## Next Steps

* [Complete Examples](live-pages-examples.html) - See Tailwind styling in full applications

* [Creating Live Pages](creating-live-pages.html) - Back to main guide

* [Data Management API Reference](data-management-api.html) - Learn about data operations



# Basic Live Pages Examples

## Overview

This page contains simple, easy-to-understand examples to help you get started with Live Pages. Each example focuses on one core concept and can be implemented quickly.

## Simple Task List

The most basic Live Page - a task list with add and delete functionality:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">My Tasks</h1>

    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="taskInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="New task..."
        >
        <button
            onclick="addTask()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add
        </button>
    </div>

    <div id="tasksList"></div>
</div>

<script>
async function loadTasks() {
    const entities = await pt.list({
        entityNames: ['task'],
        filters: { completed: false }
    });

    const tasks = entities.filter(e => e.entity_name === 'task');

    document.getElementById('tasksList').innerHTML = tasks.map(task => `
        <div class="bg-white p-4 rounded shadow mb-2 flex justify-between items-center">
            <span>${task.data.text}</span>
            <button onclick="deleteTask(${task.id})" class="text-red-500 hover:text-red-700">
                Delete
            </button>
        </div>
    `).join('');
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    if (!text) return;

    await pt.add('task', {
        text: text,
        completed: false
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function deleteTask(taskId) {
    await pt.delete(taskId);
    await loadTasks();
}

// Load tasks when page loads
document.addEventListener('DOMContentLoaded', loadTasks);
</script>
```

## Task List with Completion Toggle

Add checkbox functionality to mark tasks as complete:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">My Tasks</h1>

    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="taskInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="New task..."
        >
        <button
            onclick="addTask()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add
        </button>
    </div>

    <div id="tasksList"></div>
</div>

<script>
async function loadTasks() {
    const entities = await pt.list({
        entityNames: ['task']
    });

    const tasks = entities.filter(e => e.entity_name === 'task');

    document.getElementById('tasksList').innerHTML = tasks.map(task => {
        const isCompleted = task.data.completed === true;
        return `
            <div class="bg-white p-4 rounded shadow mb-2 flex justify-between items-center ${isCompleted ? 'opacity-50' : ''}">
                <div class="flex items-center gap-3">
                    <input
                        type="checkbox"
                        ${isCompleted ? 'checked' : ''}
                        onchange="toggleTask(${task.id})"
                        class="h-4 w-4"
                    >
                    <span class="${isCompleted ? 'line-through text-gray-500' : ''}">
                        ${task.data.text}
                    </span>
                </div>
                <button onclick="deleteTask(${task.id})" class="text-red-500 hover:text-red-700">
                    Delete
                </button>
            </div>
        `;
    }).join('');
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    if (!text) return;

    await pt.add('task', {
        text: text,
        completed: false
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function toggleTask(taskId) {
    const task = await pt.get(taskId);
    await pt.edit(taskId, {
        ...task.data,
        completed: !task.data.completed
    });
    await loadTasks();
}

async function deleteTask(taskId) {
    await pt.delete(taskId);
    await loadTasks();
}

document.addEventListener('DOMContentLoaded', loadTasks);
</script>
```

## Simple Search

Add search functionality to filter tasks:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">My Tasks</h1>

    <!-- Search Input -->
    <input
        type="text"
        id="searchInput"
        class="w-full px-3 py-2 border rounded mb-4"
        placeholder="Search tasks..."
    >

    <!-- Add Task -->
    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="taskInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="New task..."
        >
        <button
            onclick="addTask()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add
        </button>
    </div>

    <div id="tasksList"></div>
</div>

<script>
let searchTimeout;

async function loadTasks() {
    const searchTerm = document.getElementById('searchInput').value.trim();

    const filters = {};
    if (searchTerm) {
        filters.text = { $contains: searchTerm };
    }

    const entities = await pt.list({
        entityNames: ['task'],
        filters: filters
    });

    const tasks = entities.filter(e => e.entity_name === 'task');

    if (tasks.length === 0) {
        document.getElementById('tasksList').innerHTML = `
            <div class="text-center py-8 text-gray-500">
                No tasks found
            </div>
        `;
        return;
    }

    document.getElementById('tasksList').innerHTML = tasks.map(task => `
        <div class="bg-white p-4 rounded shadow mb-2 flex justify-between items-center">
            <span>${task.data.text}</span>
            <button onclick="deleteTask(${task.id})" class="text-red-500 hover:text-red-700">
                Delete
            </button>
        </div>
    `).join('');
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    if (!text) return;

    await pt.add('task', {
        text: text,
        completed: false
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function deleteTask(taskId) {
    await pt.delete(taskId);
    await loadTasks();
}

// Setup search with debouncing
document.getElementById('searchInput').addEventListener('input', () => {
    clearTimeout(searchTimeout);
    searchTimeout = setTimeout(loadTasks, 300);
});

document.addEventListener('DOMContentLoaded', loadTasks);
</script>
```

## Task List with Priority

Add priority levels to tasks:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">My Tasks</h1>

    <!-- Add Task with Priority -->
    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="taskInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="New task..."
        >
        <select id="priorityInput" class="px-3 py-2 border rounded">
            <option value="low">Low</option>
            <option value="medium">Medium</option>
            <option value="high">High</option>
        </select>
        <button
            onclick="addTask()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add
        </button>
    </div>

    <div id="tasksList"></div>
</div>

<script>
async function loadTasks() {
    const entities = await pt.list({
        entityNames: ['task']
    });

    const tasks = entities.filter(e => e.entity_name === 'task');

    document.getElementById('tasksList').innerHTML = tasks.map(task => {
        const priorityColors = {
            high: 'bg-red-100 text-red-800',
            medium: 'bg-yellow-100 text-yellow-800',
            low: 'bg-green-100 text-green-800'
        };

        const priorityClass = priorityColors[task.data.priority] || priorityColors.low;

        return `
            <div class="bg-white p-4 rounded shadow mb-2 flex justify-between items-center">
                <div class="flex items-center gap-3">
                    <span>${task.data.text}</span>
                    <span class="px-2 py-1 text-xs rounded-full ${priorityClass}">
                        ${task.data.priority}
                    </span>
                </div>
                <button onclick="deleteTask(${task.id})" class="text-red-500 hover:text-red-700">
                    Delete
                </button>
            </div>
        `;
    }).join('');
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    const priority = document.getElementById('priorityInput').value;

    if (!text) return;

    await pt.add('task', {
        text: text,
        priority: priority,
        completed: false
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function deleteTask(taskId) {
    await pt.delete(taskId);
    await loadTasks();
}

document.addEventListener('DOMContentLoaded', loadTasks);
</script>
```

## Simple Counter

A basic counter to demonstrate state management:

```HTML
<div class="container mx-auto p-6 max-w-md">
    <h1 class="text-2xl font-bold mb-4 text-center">Counter</h1>

    <div class="bg-white rounded-lg shadow-lg p-8 text-center">
        <div class="text-6xl font-bold mb-6" id="counterDisplay">0</div>

        <div class="flex gap-2 justify-center">
            <button
                onclick="decrementCounter()"
                class="bg-red-500 text-white px-6 py-3 rounded-lg hover:bg-red-600"
            >
                -
            </button>
            <button
                onclick="resetCounter()"
                class="bg-gray-500 text-white px-6 py-3 rounded-lg hover:bg-gray-600"
            >
                Reset
            </button>
            <button
                onclick="incrementCounter()"
                class="bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600"
            >
                +
            </button>
        </div>
    </div>
</div>

<script>
let counterId = null;

async function loadCounter() {
    const entities = await pt.list({
        entityNames: ['counter']
    });

    if (entities.length === 0) {
        // Create initial counter
        const result = await pt.add('counter', { value: 0 });
        counterId = result.id;
        updateDisplay(0);
    } else {
        const counter = entities[0];
        counterId = counter.id;
        updateDisplay(counter.data.value);
    }
}

function updateDisplay(value) {
    document.getElementById('counterDisplay').textContent = value;
}

async function incrementCounter() {
    const counter = await pt.get(counterId);
    const newValue = counter.data.value + 1;

    await pt.edit(counterId, { value: newValue });
    updateDisplay(newValue);
}

async function decrementCounter() {
    const counter = await pt.get(counterId);
    const newValue = counter.data.value - 1;

    await pt.edit(counterId, { value: newValue });
    updateDisplay(newValue);
}

async function resetCounter() {
    await pt.edit(counterId, { value: 0 });
    updateDisplay(0);
}

document.addEventListener('DOMContentLoaded', loadCounter);
</script>
```

## Simple Notes List

A minimalist notes application:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">My Notes</h1>

    <!-- Add Note -->
    <div class="mb-4">
        <input
            type="text"
            id="noteTitleInput"
            class="w-full px-3 py-2 border rounded mb-2"
            placeholder="Note title..."
        >
        <textarea
            id="noteContentInput"
            rows="3"
            class="w-full px-3 py-2 border rounded mb-2"
            placeholder="Note content..."
        ></textarea>
        <button
            onclick="addNote()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add Note
        </button>
    </div>

    <!-- Notes List -->
    <div id="notesList"></div>
</div>

<script>
async function loadNotes() {
    const entities = await pt.list({
        entityNames: ['note']
    });

    const notes = entities.filter(e => e.entity_name === 'note');

    if (notes.length === 0) {
        document.getElementById('notesList').innerHTML = `
            <div class="text-center py-8 text-gray-500">
                No notes yet
            </div>
        `;
        return;
    }

    document.getElementById('notesList').innerHTML = notes.map(note => `
        <div class="bg-white rounded-lg shadow p-4 mb-3">
            <div class="flex justify-between items-start mb-2">
                <h3 class="font-semibold text-lg">${note.data.title}</h3>
                <button
                    onclick="deleteNote(${note.id})"
                    class="text-red-500 hover:text-red-700"
                >
                    Delete
                </button>
            </div>
            <p class="text-gray-600">${note.data.content}</p>
            <div class="text-xs text-gray-400 mt-2">
                ${new Date(note.created_at).toLocaleDateString()}
            </div>
        </div>
    `).join('');
}

async function addNote() {
    const title = document.getElementById('noteTitleInput').value.trim();
    const content = document.getElementById('noteContentInput').value.trim();

    if (!title || !content) return;

    await pt.add('note', {
        title: title,
        content: content
    });

    document.getElementById('noteTitleInput').value = '';
    document.getElementById('noteContentInput').value = '';
    await loadNotes();
}

async function deleteNote(noteId) {
    if (confirm('Delete this note?')) {
        await pt.delete(noteId);
        await loadNotes();
    }
}

document.addEventListener('DOMContentLoaded', loadNotes);
</script>
```

## Shopping List

A simple shopping list with quantities:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Shopping List</h1>

    <!-- Add Item -->
    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="itemInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="Item name..."
        >
        <input
            type="number"
            id="quantityInput"
            value="1"
            min="1"
            class="w-20 px-3 py-2 border rounded"
        >
        <button
            onclick="addItem()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add
        </button>
    </div>

    <!-- Items List -->
    <div id="itemsList"></div>
</div>

<script>
async function loadItems() {
    const entities = await pt.list({
        entityNames: ['shopping_item']
    });

    const items = entities.filter(e => e.entity_name === 'shopping_item');

    if (items.length === 0) {
        document.getElementById('itemsList').innerHTML = `
            <div class="text-center py-8 text-gray-500">
                No items in your shopping list
            </div>
        `;
        return;
    }

    document.getElementById('itemsList').innerHTML = items.map(item => {
        const isPurchased = item.data.purchased === true;
        return `
            <div class="bg-white p-4 rounded shadow mb-2 flex justify-between items-center ${isPurchased ? 'opacity-50' : ''}">
                <div class="flex items-center gap-3">
                    <input
                        type="checkbox"
                        ${isPurchased ? 'checked' : ''}
                        onchange="togglePurchased(${item.id})"
                        class="h-4 w-4"
                    >
                    <span class="${isPurchased ? 'line-through text-gray-500' : ''}">
                        ${item.data.name}
                    </span>
                    <span class="text-sm text-gray-600">
                        (${item.data.quantity})
                    </span>
                </div>
                <button
                    onclick="deleteItem(${item.id})"
                    class="text-red-500 hover:text-red-700"
                >
                    Delete
                </button>
            </div>
        `;
    }).join('');
}

async function addItem() {
    const name = document.getElementById('itemInput').value.trim();
    const quantity = parseInt(document.getElementById('quantityInput').value);

    if (!name) return;

    await pt.add('shopping_item', {
        name: name,
        quantity: quantity,
        purchased: false
    });

    document.getElementById('itemInput').value = '';
    document.getElementById('quantityInput').value = '1';
    await loadItems();
}

async function togglePurchased(itemId) {
    const item = await pt.get(itemId);
    await pt.edit(itemId, {
        ...item.data,
        purchased: !item.data.purchased
    });
    await loadItems();
}

async function deleteItem(itemId) {
    await pt.delete(itemId);
    await loadItems();
}

document.addEventListener('DOMContentLoaded', loadItems);
</script>
```

## Poll/Voting App

A simple polling application:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Quick Poll</h1>

    <!-- Add Poll Option -->
    <div class="bg-white rounded-lg shadow p-4 mb-4">
        <input
            type="text"
            id="optionInput"
            class="w-full px-3 py-2 border rounded mb-2"
            placeholder="New poll option..."
        >
        <button
            onclick="addOption()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Add Option
        </button>
    </div>

    <!-- Poll Options -->
    <div id="pollOptions"></div>
</div>

<script>
async function loadPoll() {
    const entities = await pt.list({
        entityNames: ['poll_option']
    });

    const options = entities.filter(e => e.entity_name === 'poll_option');

    if (options.length === 0) {
        document.getElementById('pollOptions').innerHTML = `
            <div class="text-center py-8 text-gray-500">
                No poll options yet. Add one to get started!
            </div>
        `;
        return;
    }

    // Calculate total votes
    const totalVotes = options.reduce((sum, opt) => sum + (opt.data.votes || 0), 0);

    document.getElementById('pollOptions').innerHTML = options.map(option => {
        const votes = option.data.votes || 0;
        const percentage = totalVotes > 0 ? Math.round((votes / totalVotes) * 100) : 0;

        return `
            <div class="bg-white rounded-lg shadow p-4 mb-3">
                <div class="flex justify-between items-center mb-2">
                    <span class="font-medium">${option.data.text}</span>
                    <button
                        onclick="deleteOption(${option.id})"
                        class="text-red-500 hover:text-red-700 text-sm"
                    >
                        Delete
                    </button>
                </div>
                <div class="flex items-center gap-3">
                    <button
                        onclick="vote(${option.id})"
                        class="bg-green-500 text-white px-3 py-1 rounded text-sm hover:bg-green-600"
                    >
                        Vote
                    </button>
                    <div class="flex-1">
                        <div class="bg-gray-200 rounded-full h-6">
                            <div
                                class="bg-blue-500 h-6 rounded-full flex items-center justify-center text-white text-sm"
                                style="width: ${percentage}%"
                            >
                                ${percentage > 10 ? percentage + '%' : ''}
                            </div>
                        </div>
                    </div>
                    <span class="text-sm text-gray-600">${votes} votes</span>
                </div>
            </div>
        `;
    }).join('');
}

async function addOption() {
    const text = document.getElementById('optionInput').value.trim();
    if (!text) return;

    await pt.add('poll_option', {
        text: text,
        votes: 0
    });

    document.getElementById('optionInput').value = '';
    await loadPoll();
}

async function vote(optionId) {
    const option = await pt.get(optionId);
    await pt.edit(optionId, {
        ...option.data,
        votes: (option.data.votes || 0) + 1
    });
    await loadPoll();
}

async function deleteOption(optionId) {
    if (confirm('Delete this option?')) {
        await pt.delete(optionId);
        await loadPoll();
    }
}

document.addEventListener('DOMContentLoaded', loadPoll);
</script>
```

## Send Message to Chat

Send messages from your Live Page to the chat interface:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Send Messages</h1>

    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="messageInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="Type a message..."
            onkeypress="if(event.key==='Enter') sendMessage()"
        >
        <button
            onclick="sendMessage()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Send
        </button>
    </div>

    <!-- Quick action buttons -->
    <div class="flex gap-2">
        <button
            onclick="pt.addMessage('Task completed!')"
            class="bg-green-500 text-white px-3 py-1 rounded text-sm hover:bg-green-600"
        >
            Quick: Task Done
        </button>
        <button
            onclick="pt.addMessage('Need help!')"
            class="bg-yellow-500 text-white px-3 py-1 rounded text-sm hover:bg-yellow-600"
        >
            Quick: Need Help
        </button>
    </div>
</div>

<script>
async function sendMessage() {
    const input = document.getElementById('messageInput');
    const message = input.value.trim();

    if (!message) {
        alert('Please enter a message');
        return;
    }

    try {
        await pt.addMessage(message);
        input.value = '';
        alert('Message sent!');
    } catch (error) {
        alert('Failed to send message: ' + error.message);
    }
}
</script>
```

## Upload Files to Chat

Upload files from your Live Page with an optional message:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Upload Files</h1>

    <!-- Simple form upload -->
    <form id="uploadForm" onsubmit="handleUpload(event)" class="bg-white rounded-lg shadow p-6">
        <div class="mb-4">
            <label class="block text-sm font-medium mb-2">Select Files</label>
            <input
                type="file"
                name="files"
                multiple
                class="block w-full text-sm"
                required
            >
        </div>

        <div class="mb-4">
            <label class="block text-sm font-medium mb-2">Message (optional)</label>
            <input
                type="text"
                name="message"
                placeholder="Add a message..."
                class="border rounded px-3 py-2 w-full"
            >
        </div>

        <button
            type="submit"
            class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
        >
            Upload
        </button>
    </form>

    <!-- Drag and drop zone -->
    <div
        id="dropZone"
        ondrop="handleDrop(event)"
        ondragover="event.preventDefault()"
        class="mt-6 border-2 border-dashed border-gray-300 rounded-lg p-8 text-center"
    >
        <p class="text-gray-600">Drop files here to upload</p>
    </div>
</div>

<script>
async function handleUpload(event) {
    event.preventDefault();
    const form = event.target;

    try {
        const result = await pt.uploadFiles(form);
        alert(`Uploaded ${result.files_count} file(s) successfully!`);
        form.reset();
    } catch (error) {
        alert('Upload failed: ' + error.message);
    }
}

async function handleDrop(event) {
    event.preventDefault();

    const files = event.dataTransfer.files;
    if (files.length === 0) return;

    const formData = new FormData();
    for (const file of files) {
        formData.append('files', file);
    }

    try {
        const result = await pt.uploadFiles(formData, 'Drag & drop upload');
        alert(`Uploaded ${result.files_count} file(s)`);
    } catch (error) {
        alert('Upload failed: ' + error.message);
    }
}
</script>
```

## File Upload with Preview

Show file previews before uploading:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Upload with Preview</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <input
            type="file"
            id="fileInput"
            multiple
            onchange="previewFiles()"
            class="mb-4 block w-full"
        >

        <div id="preview" class="mb-4"></div>

        <button
            onclick="uploadFiles()"
            class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
        >
            Upload Selected Files
        </button>
    </div>
</div>

<script>
function previewFiles() {
    const input = document.getElementById('fileInput');
    const preview = document.getElementById('preview');
    const files = input.files;

    if (files.length === 0) {
        preview.innerHTML = '';
        return;
    }

    preview.innerHTML = `
        <div class="bg-gray-50 p-3 rounded">
            <p class="font-medium mb-2">${files.length} file(s) selected:</p>
            ${Array.from(files).map(file => `
                <div class="text-sm text-gray-600">
                    ${file.name} (${(file.size / 1024).toFixed(2)} KB)
                </div>
            `).join('')}
        </div>
    `;
}

async function uploadFiles() {
    const input = document.getElementById('fileInput');

    if (input.files.length === 0) {
        alert('Please select files');
        return;
    }

    const formData = new FormData();
    for (const file of input.files) {
        formData.append('files', file);
    }

    try {
        const result = await pt.uploadFiles(formData, 'Files uploaded from preview');
        alert(`Success! Uploaded ${result.files_count} files`);
        input.value = '';
        document.getElementById('preview').innerHTML = '';
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
</script>
```

## Send Push Notification

Send push notifications to specific users in your chat:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Send Notification</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <select id="userSelect" class="border rounded px-3 py-2 w-full mb-3">
            <option value="">Select a user...</option>
        </select>

        <input
            type="text"
            id="notifTitle"
            placeholder="Notification title"
            class="border rounded px-3 py-2 w-full mb-3"
        >

        <textarea
            id="notifText"
            rows="3"
            placeholder="Notification message"
            class="border rounded px-3 py-2 w-full mb-3"
        ></textarea>

        <button
            onclick="sendNotification()"
            class="bg-blue-500 text-white px-4 py-2 rounded w-full hover:bg-blue-600"
        >
            Send Notification
        </button>
    </div>
</div>

<script>
// Load users on page load
async function loadUsers() {
    const members = await pt.getChatMembers();
    const select = document.getElementById('userSelect');

    members
        .filter(m => m.type === 'user')
        .forEach(member => {
            const option = document.createElement('option');
            option.value = member.id;
            option.textContent = member.name;
            select.appendChild(option);
        });
}

async function sendNotification() {
    const userId = parseInt(document.getElementById('userSelect').value);
    const title = document.getElementById('notifTitle').value.trim();
    const text = document.getElementById('notifText').value.trim();

    if (!userId || !title || !text) {
        alert('Please fill all fields');
        return;
    }

    try {
        await pt.sendNotification(userId, title, text);

        alert('Notification sent!');
        document.getElementById('notifTitle').value = '';
        document.getElementById('notifText').value = '';
    } catch (error) {
        alert('Error: ' + error.message);
    }
}

document.addEventListener('DOMContentLoaded', loadUsers);
</script>
```

## Search Documents

Search across documents and collections using semantic search:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Document Search</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <input
            type="text"
            id="searchQuery"
            placeholder="What are you looking for?"
            class="border rounded px-3 py-2 w-full mb-3"
        >

        <select id="searchScope" class="border rounded px-3 py-2 w-full mb-3">
            <option value="ALL">All (Documents & Collections)</option>
            <option value="DOCUMENTS_ONLY">Documents Only</option>
            <option value="COLLECTIONS_ONLY">Collections Only</option>
        </select>

        <button
            onclick="searchDocs()"
            class="bg-green-500 text-white px-4 py-2 rounded w-full hover:bg-green-600"
        >
            Search
        </button>

        <div id="results" class="mt-4 hidden">
            <h3 class="font-bold mb-2">Results:</h3>
            <div class="bg-gray-50 rounded p-3 max-h-96 overflow-auto">
                <pre id="resultsText" class="text-sm whitespace-pre-wrap"></pre>
            </div>
        </div>
    </div>
</div>

<script>
async function searchDocs() {
    const query = document.getElementById('searchQuery').value.trim();
    const scope = document.getElementById('searchScope').value;

    if (!query) {
        alert('Please enter a search query');
        return;
    }

    try {
        const result = await pt.searchDocuments(query, scope);

        document.getElementById('resultsText').textContent = result.results;
        document.getElementById('results').classList.remove('hidden');
    } catch (error) {
        alert('Search failed: ' + error.message);
    }
}
</script>
```

## View Document Text

Retrieve and display text from a document:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">View Document</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <input
            type="number"
            id="docId"
            placeholder="Document ID"
            class="border rounded px-3 py-2 w-full mb-3"
        >

        <button
            onclick="viewDoc()"
            class="bg-purple-500 text-white px-4 py-2 rounded w-full hover:bg-purple-600"
        >
            View Text
        </button>

        <div id="docContent" class="mt-4 hidden">
            <h3 class="font-bold mb-2">Document Content:</h3>
            <div class="bg-gray-50 rounded p-3 max-h-96 overflow-auto">
                <pre id="docText" class="text-sm whitespace-pre-wrap"></pre>
            </div>
        </div>
    </div>
</div>

<script>
async function viewDoc() {
    const docId = parseInt(document.getElementById('docId').value);

    if (!docId) {
        alert('Please enter a document ID');
        return;
    }

    try {
        const result = await pt.getDocumentText(docId);

        if (result.text) {
            document.getElementById('docText').textContent = result.text;
            document.getElementById('docContent').classList.remove('hidden');
        } else {
            alert(result.message || 'No text available');
        }
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
</script>
```

## Create and Save Documents

Create documents in various formats:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">Create Document</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <input
            type="text"
            id="filename"
            placeholder="Filename (e.g., report.txt)"
            class="border rounded px-3 py-2 w-full mb-3"
        >

        <select id="format" class="border rounded px-3 py-2 w-full mb-3">
            <option value="TXT">Plain Text (.txt)</option>
            <option value="MD">Markdown (.md)</option>
            <option value="HTML">HTML (.html)</option>
            <option value="PDF">PDF (use Markdown)</option>
            <option value="DOCX">Word (use Markdown)</option>
            <option value="CSV">CSV (.csv)</option>
        </select>

        <textarea
            id="content"
            rows="8"
            placeholder="Enter content..."
            class="border rounded px-3 py-2 w-full mb-3 font-mono text-sm"
        ></textarea>

        <button
            onclick="saveDoc()"
            class="bg-indigo-500 text-white px-4 py-2 rounded w-full hover:bg-indigo-600"
        >
            Save Document
        </button>
    </div>
</div>

<script>
const mimeTypes = {
    'TXT': 'text/plain',
    'MD': 'text/markdown',
    'HTML': 'text/html',
    'PDF': 'application/pdf',
    'DOCX': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'CSV': 'text/csv'
};

async function saveDoc() {
    const filename = document.getElementById('filename').value.trim();
    const format = document.getElementById('format').value;
    const content = document.getElementById('content').value;

    if (!filename || !content) {
        alert('Please provide filename and content');
        return;
    }

    try {
        await pt.saveDocument(filename, format, mimeTypes[format], content);

        alert('Document saved successfully!');
        document.getElementById('filename').value = '';
        document.getElementById('content').value = '';
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
</script>
```

## AI-Powered Data Extraction

Combine file uploads with AI instructions to automatically extract and store data:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">AI Data Extractor</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <select id="taskType" class="border rounded px-3 py-2 w-full mb-3">
            <option value="invoice">Extract Invoice Data</option>
            <option value="resume">Parse Resume</option>
            <option value="receipt">Process Expense Receipt</option>
        </select>

        <input
            type="file"
            id="fileUpload"
            multiple
            class="block w-full mb-3"
        >

        <button
            onclick="processWithAI()"
            class="bg-purple-500 text-white px-4 py-2 rounded w-full hover:bg-purple-600"
        >
            Process with AI
        </button>
    </div>
</div>

<script>
const instructions = {
    invoice: `Extract invoice information and use the tool 'chatdb_add' to create database records:
- entity_name: "invoice"
- data: { invoice_number, date, vendor, amount, due_date }`,

    resume: `Extract candidate information and use the tool 'chatdb_add' to create entries:
- entity_name: "candidate"
- data: { name, email, phone, skills: array, experience: number }`,

    receipt: `Extract expense data and use the tool 'chatdb_add' to store each:
- entity_name: "expense"
- data: { date, merchant, amount, category }`
};

async function processWithAI() {
    const task = document.getElementById('taskType').value;
    const input = document.getElementById('fileUpload');

    if (input.files.length === 0) {
        alert('Please select files');
        return;
    }

    const formData = new FormData();
    for (const file of input.files) {
        formData.append('files', file);
    }

    try {
        await pt.uploadFiles(formData, instructions[task]);
        alert('Files uploaded! AI is processing and storing data...');
        input.value = '';
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
</script>
```

## Direct AI Commands

Send natural language commands to have AI manage the database:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">AI Assistant</h1>

    <div class="bg-white rounded-lg shadow p-6">
        <!-- Quick Actions -->
        <div class="grid grid-cols-2 gap-2 mb-4">
            <button
                onclick="aiCommand('competitors')"
                class="bg-blue-100 text-blue-800 px-3 py-2 rounded hover:bg-blue-200"
            >
                Research Competitors
            </button>
            <button
                onclick="aiCommand('analyze')"
                class="bg-green-100 text-green-800 px-3 py-2 rounded hover:bg-green-200"
            >
                Analyze Tasks
            </button>
        </div>

        <!-- Custom Command -->
        <textarea
            id="aiCommand"
            rows="4"
            placeholder="Enter AI command..."
            class="border rounded px-3 py-2 w-full mb-3"
        ></textarea>

        <button
            onclick="sendCommand()"
            class="bg-indigo-500 text-white px-4 py-2 rounded w-full hover:bg-indigo-600"
        >
            Execute
        </button>
    </div>
</div>

<script>
const commands = {
    competitors: `Search for top 3 AI assistant competitors and use the tool 'chatdb_add' to store:
- entity_name: "competitor"
- data: { name, website, key_features: array }`,

    analyze: `Use the tool 'chatdb_list' to find all pending tasks.
Use the tool 'chatdb_add' to create urgency report for each:
- entity_name: "urgent_task"
- data: { task_id, title, days_old, priority }`
};

async function aiCommand(type) {
    try {
        await pt.addMessage(commands[type]);
        alert('AI is processing your request...');
    } catch (error) {
        alert('Error: ' + error.message);
    }
}

async function sendCommand() {
    const command = document.getElementById('aiCommand').value.trim();

    if (!command) {
        alert('Please enter a command');
        return;
    }

    try {
        await pt.addMessage(command);
        alert('Command sent to AI assistant!');
        document.getElementById('aiCommand').value = '';
    } catch (error) {
        alert('Error: ' + error.message);
    }
}
</script>
```

## Key Concepts Demonstrated

These examples cover:

1. Basic CRUD Operations: Create, read, update, and delete entities

2. Filtering: Using server-side filters to search data

3. State Management: Managing application state with entities

4. User Interface: Building interactive UIs with Tailwind CSS

5. Event Handling: Responding to user interactions

6. Data Display: Rendering lists and cards

7. Form Handling: Collecting and validating user input

8. Chat Integration: Sending messages to chat from Live Pages

9. File Upload: Uploading files with drag & drop support

10. Push Notifications: Send notifications to specific users

11. Document Search: Semantic search across documents and collections

12. Document Management: View and create documents in various formats

13. AI-Powered Automation: Let AI extract and store data automatically

14. Natural Language Commands: Control database with AI using plain language

## Next Steps

Now that you've seen these basic examples, you can:

* [Complete Examples](live-pages-examples.html) - See more complex, production-ready applications

* [Data Management API Reference](data-management-api.html) - Learn about all available methods

* [Filtering and Querying](filtering-and-querying.html) - Advanced filtering techniques

* [Styling with Tailwind CSS](styling-with-tailwind.html) - Improve your UI design



# Complete Live Pages Examples

## Overview

This page contains complete, production-ready examples demonstrating various Live Pages features and patterns.

## Advanced Todo Application

This comprehensive example demonstrates:

* Multiple entity types (`task` and `filter_config`)

* Per-user filter preferences using `creator_user_id`

* Server-side filtering and pagination

* Chat member integration

* Task assignment with default assignee

* Inline task editing

* Enhanced UX features

### Key Features

* Multiple Entity Types: Uses both `task` entities (shared tasks) and `filter_config` entities (personal settings)

* Per-User Configuration: Each user has their own filter preferences automatically saved

* Task Assignment: New tasks are assigned to current user by default, with dropdown to change assignee

* Inline Editing: Click on any task to edit its details (text, priority, due date, assignee)

* Creator Tracking: Shows who created each task using the `creator_user_id` field

* Member Integration: Displays creator names and assignees from chat members

* Server-Side Pagination: Efficient loading with page-based pagination

### Complete Implementation

```HTML
<div class="container mx-auto p-6 max-w-4xl">
    <h1 class="text-3xl font-bold text-gray-800 mb-8">Advanced Todo List</h1>

    <!-- Search and Filter Controls -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
        <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
            <!-- Text Search with partial matching -->
            <input
                type="text"
                id="searchInput"
                class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500"
                placeholder="Search tasks (partial match)..."
            >

            <!-- Priority Filter (Multi-select) -->
            <div class="relative">
                <button
                    id="priorityDropdown"
                    onclick="togglePriorityDropdown()"
                    class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 text-left bg-white"
                >
                    <span id="priorityLabel">All Priorities</span>
                    <svg class="float-right mt-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
                    </svg>
                </button>
                <div id="priorityOptions" class="hidden absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg">
                    <label class="flex items-center px-3 py-2 hover:bg-gray-50">
                        <input type="checkbox" value="high" onchange="updatePriorityFilter()" class="mr-2"> High
                    </label>
                    <label class="flex items-center px-3 py-2 hover:bg-gray-50">
                        <input type="checkbox" value="medium" onchange="updatePriorityFilter()" class="mr-2"> Medium
                    </label>
                    <label class="flex items-center px-3 py-2 hover:bg-gray-50">
                        <input type="checkbox" value="low" onchange="updatePriorityFilter()" class="mr-2"> Low
                    </label>
                </div>
            </div>

            <!-- Status Filter with default to hide completed -->
            <select id="statusFilter" class="px-3 py-2 border border-gray-300 rounded-md">
                <option value="pending" selected>Pending Only</option>
                <option value="all">All Tasks</option>
                <option value="completed">Completed</option>
                <option value="overdue">Overdue</option>
            </select>
        </div>

        <!-- Quick Filter Buttons & Bulk Actions -->
        <div class="flex flex-wrap justify-between items-center gap-2">
            <div class="flex flex-wrap gap-2">
                <button onclick="quickFilter('urgent')" class="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm">
                    Urgent Tasks
                </button>
                <button onclick="quickFilter('today')" class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm">
                    Due Today
                </button>
                <button onclick="clearFilters()" class="bg-gray-100 text-gray-800 px-3 py-1 rounded-full text-sm">
                    Clear Filters
                </button>
            </div>

            <button
                onclick="clearCompletedTasks()"
                class="bg-red-500 text-white px-4 py-2 rounded-md text-sm hover:bg-red-600"
            >
                Clear Completed Tasks
            </button>
        </div>
    </div>

    <!-- Add New Task (with assignment dropdown) -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
        <div class="grid grid-cols-1 gap-3">
            <input
                type="text"
                id="taskInput"
                class="w-full px-3 py-2 border border-gray-300 rounded-md"
                placeholder="Enter new task..."
            >
            <div class="grid grid-cols-1 md:grid-cols-4 gap-3">
                <select id="assigneeInput" class="px-3 py-2 border border-gray-300 rounded-md">
                    <!-- Populated dynamically -->
                </select>
                <select id="priorityInput" class="px-3 py-2 border border-gray-300 rounded-md">
                    <option value="low">Low</option>
                    <option value="medium">Medium</option>
                    <option value="high">High</option>
                </select>
                <input type="date" id="dueDateInput" class="px-3 py-2 border border-gray-300 rounded-md">
                <button onclick="addTask()" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
                    Add Task
                </button>
            </div>
        </div>
    </div>

    <!-- Edit Task Modal -->
    <div id="editModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
        <div class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 p-6">
            <h2 class="text-xl font-bold mb-4">Edit Task</h2>
            <div class="space-y-4">
                <div>
                    <label class="block text-sm font-medium mb-1">Task Description</label>
                    <input
                        type="text"
                        id="editTaskText"
                        class="w-full px-3 py-2 border rounded-md"
                    >
                </div>
                <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                    <div>
                        <label class="block text-sm font-medium mb-1">Assigned To</label>
                        <select id="editAssigneeInput" class="w-full px-3 py-2 border rounded-md">
                            <!-- Populated dynamically -->
                        </select>
                    </div>
                    <div>
                        <label class="block text-sm font-medium mb-1">Priority</label>
                        <select id="editPriorityInput" class="w-full px-3 py-2 border rounded-md">
                            <option value="low">Low</option>
                            <option value="medium">Medium</option>
                            <option value="high">High</option>
                        </select>
                    </div>
                    <div>
                        <label class="block text-sm font-medium mb-1">Due Date</label>
                        <input type="date" id="editDueDateInput" class="w-full px-3 py-2 border rounded-md">
                    </div>
                </div>
            </div>
            <div class="flex justify-end gap-2 mt-6">
                <button
                    onclick="closeEditModal()"
                    class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300"
                >
                    Cancel
                </button>
                <button
                    onclick="saveTaskEdit()"
                    class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
                >
                    Save Changes
                </button>
            </div>
        </div>
    </div>

    <!-- Tasks List -->
    <div id="tasksList" class="space-y-4">
        <!-- Tasks will be displayed here -->
    </div>

    <!-- Pagination Controls -->
    <div id="paginationControls" class="mt-6 flex justify-center items-center gap-4 bg-white rounded-lg shadow-md p-4">
        <button
            id="firstPageBtn"
            onclick="goToFirstPage()"
            class="px-3 py-2 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
        >
            First
        </button>
        <button
            id="prevPageBtn"
            onclick="goToPreviousPage()"
            class="px-3 py-2 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
        >
            Previous
        </button>
        <span id="pageInfo" class="text-gray-700 font-medium">
            Page 1
        </span>
        <button
            id="nextPageBtn"
            onclick="goToNextPage()"
            class="px-3 py-2 bg-gray-200 rounded-md hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
        >
            Next
        </button>
    </div>
</div>

<script>
let allMembers = [];
let currentUserId = null;
let selectedPriorities = [];
let searchTimeout;
let currentPage = 1;
let pageSize = 20;
let hasMorePages = false;
let editingTaskId = null;

// Initialize the application
document.addEventListener('DOMContentLoaded', async () => {
    // Load chat members first
    allMembers = await pt.getChatMembers();

    // Identify current user (simplified - you may need different logic)
    const currentUser = allMembers.find(m => m.type === 'user' && m.is_owner);
    currentUserId = currentUser?.id;

    // Populate assignee dropdowns
    populateAssigneeDropdowns();

    // Load user's saved filter configuration
    await loadFilterConfig();

    // Apply filters and load tasks
    await applyFilters();
    setupEventListeners();
});

// Populate assignee dropdowns with chat members
function populateAssigneeDropdowns() {
    const users = allMembers.filter(m => m.type === 'user');

    const options = users.map(user => {
        const selected = user.id === currentUserId ? 'selected' : '';
        return `<option value="${user.id}" ${selected}>${user.name}</option>`;
    }).join('');

    // Populate new task dropdown (current user selected by default)
    document.getElementById('assigneeInput').innerHTML = options;

    // Populate edit modal dropdown
    document.getElementById('editAssigneeInput').innerHTML = options;
}

// Load user's saved filter preferences from filter_config entity
async function loadFilterConfig() {
    if (!currentUserId) return;

    try {
        const configs = await pt.list({
            entityNames: ['filter_config'],
            filters: { creator_user_id: currentUserId }
        });

        if (configs.length > 0) {
            const config = configs[0].data;

            if (config.searchTerm) {
                document.getElementById('searchInput').value = config.searchTerm;
            }

            if (config.statusFilter) {
                document.getElementById('statusFilter').value = config.statusFilter;
            }

            if (config.selectedPriorities && Array.isArray(config.selectedPriorities)) {
                selectedPriorities = config.selectedPriorities;
                const checkboxes = document.querySelectorAll('#priorityOptions input[type="checkbox"]');
                checkboxes.forEach(cb => {
                    cb.checked = selectedPriorities.includes(cb.value);
                });
                updatePriorityLabel();
            }

            if (config.pageSize) {
                pageSize = config.pageSize;
            }
        }
    } catch (error) {
        console.error('Error loading filter config:', error);
    }
}

// Save current filter preferences to filter_config entity
async function saveFilterConfig() {
    if (!currentUserId) return;

    try {
        const searchTerm = document.getElementById('searchInput').value.trim();
        const statusFilter = document.getElementById('statusFilter').value;

        const configs = await pt.list({
            entityNames: ['filter_config'],
            filters: { creator_user_id: currentUserId }
        });

        const configData = {
            searchTerm: searchTerm,
            statusFilter: statusFilter,
            selectedPriorities: selectedPriorities,
            pageSize: pageSize
        };

        if (configs.length > 0) {
            await pt.edit(configs[0].id, configData);
        } else {
            await pt.add('filter_config', configData);
        }
    } catch (error) {
        console.error('Error saving filter config:', error);
    }
}

function setupEventListeners() {
    const searchInput = document.getElementById('searchInput');
    searchInput.addEventListener('input', () => {
        clearTimeout(searchTimeout);
        searchTimeout = setTimeout(() => applyFilters(), 300);
    });

    const statusFilter = document.getElementById('statusFilter');
    statusFilter.addEventListener('change', () => applyFilters());

    document.addEventListener('click', (e) => {
        const dropdown = document.getElementById('priorityOptions');
        const button = document.getElementById('priorityDropdown');
        if (!dropdown.contains(e.target) && !button.contains(e.target)) {
            dropdown.classList.add('hidden');
        }
    });
}

function togglePriorityDropdown() {
    document.getElementById('priorityOptions').classList.toggle('hidden');
}

function updatePriorityLabel() {
    const label = document.getElementById('priorityLabel');
    if (selectedPriorities.length === 0 || selectedPriorities.length === 3) {
        label.textContent = 'All Priorities';
    } else {
        label.textContent = selectedPriorities.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(', ');
    }
}

function updatePriorityFilter() {
    const checkboxes = document.querySelectorAll('#priorityOptions input[type="checkbox"]');
    selectedPriorities = Array.from(checkboxes)
        .filter(cb => cb.checked)
        .map(cb => cb.value);

    updatePriorityLabel();
    applyFilters();
}

async function applyFilters(resetPage = true) {
    if (resetPage) {
        currentPage = 1;
    }

    const searchTerm = document.getElementById('searchInput').value.trim();
    const statusFilter = document.getElementById('statusFilter').value;

    await saveFilterConfig();

    let serverFilters = {};

    if (searchTerm) {
        serverFilters.text = { $contains: searchTerm };
    }

    if (selectedPriorities.length > 0 && selectedPriorities.length < 3) {
        serverFilters.priority = { $in: selectedPriorities };
    }

    if (statusFilter === 'pending') {
        serverFilters.completed = { $ne: "true" };
    } else if (statusFilter === 'completed') {
        serverFilters.completed = "true";
    }

    try {
        const result = await pt.list({
            entityNames: ['task'],
            filters: serverFilters,
            page: currentPage,
            pageSize: pageSize,
            returnMetadata: true
        });

        let filteredTasks = result.entities.filter(entity => entity.entity_name === 'task');

        if (statusFilter === 'overdue') {
            const today = new Date().toISOString().split('T')[0];
            filteredTasks = filteredTasks.filter(task =>
                task.data.due_date &&
                task.data.due_date < today &&
                task.data.completed !== "true"
            );
        }

        hasMorePages = result.pagination.has_more;
        updatePaginationControls();

        displayTasks(filteredTasks);
    } catch (error) {
        console.error('Error filtering tasks:', error);
    }
}

function updatePaginationControls() {
    document.getElementById('pageInfo').textContent = `Page ${currentPage}`;
    document.getElementById('firstPageBtn').disabled = currentPage === 1;
    document.getElementById('prevPageBtn').disabled = currentPage === 1;
    document.getElementById('nextPageBtn').disabled = !hasMorePages;
}

async function goToFirstPage() {
    currentPage = 1;
    await applyFilters(false);
}

async function goToPreviousPage() {
    if (currentPage > 1) {
        currentPage--;
        await applyFilters(false);
    }
}

async function goToNextPage() {
    if (hasMorePages) {
        currentPage++;
        await applyFilters(false);
    }
}

function displayTasks(tasks) {
    const tasksList = document.getElementById('tasksList');

    if (tasks.length === 0) {
        tasksList.innerHTML = '<div class="text-center py-8 text-gray-500">No tasks found</div>';
        return;
    }

    tasksList.innerHTML = tasks.map(task => {
        const taskData = task.data;
        const isCompleted = taskData.completed === "true";
        const today = new Date().toISOString().split('T')[0];
        const isOverdue = taskData.due_date && taskData.due_date < today && !isCompleted;

        const creator = allMembers.find(m => m.id === task.creator_user_id);
        const creatorName = creator ? creator.name : 'Unknown';

        const assignee = allMembers.find(m => m.id === taskData.assignee_id);
        const assigneeName = assignee ? assignee.name : 'Unassigned';

        let dueDateDisplay = '';
        if (taskData.due_date) {
            const formattedDate = formatDate(taskData.due_date);
            dueDateDisplay = isOverdue ? `Due: ${formattedDate} (Overdue)` : `Due: ${formattedDate}`;
        }

        return `
            <div class="bg-white rounded-lg shadow-md p-4 ${isCompleted ? 'opacity-75' : ''} cursor-pointer hover:shadow-lg transition-shadow">
                <div class="flex items-center justify-between">
                    <div class="flex items-center space-x-3 flex-1" onclick="openEditModal(${task.id})">
                        <input
                            type="checkbox"
                            ${isCompleted ? 'checked' : ''}
                            onchange="toggleTask(${task.id})"
                            onclick="event.stopPropagation()"
                            class="h-4 w-4 text-blue-600"
                        >
                        <span class="${isCompleted ? 'line-through text-gray-500' : 'text-gray-800'} flex-1">
                            ${escapeHtml(taskData.text)}
                        </span>
                        <span class="px-2 py-1 text-xs rounded-full ${getPriorityColor(taskData.priority)}">
                            ${taskData.priority}
                        </span>
                        ${dueDateDisplay ? `<span class="text-sm ${isOverdue ? 'text-red-500' : 'text-gray-500'}">${dueDateDisplay}</span>` : ''}
                    </div>
                    <div class="flex items-center gap-3 ml-3">
                        <div class="text-xs text-gray-500">
                            <div>Assigned: <span class="font-medium">${escapeHtml(assigneeName)}</span></div>
                            <div class="text-gray-400 italic">By: ${escapeHtml(creatorName)}</div>
                        </div>
                        <button
                            onclick="deleteTask(${task.id}); event.stopPropagation()"
                            class="text-red-500 hover:text-red-700"
                        >
                            Delete
                        </button>
                    </div>
                </div>
            </div>
        `;
    }).join('');
}

// Open edit modal with task data
async function openEditModal(taskId) {
    editingTaskId = taskId;
    const task = await pt.get(taskId);

    document.getElementById('editTaskText').value = task.data.text || '';
    document.getElementById('editAssigneeInput').value = task.data.assignee_id || '';
    document.getElementById('editPriorityInput').value = task.data.priority || 'low';
    document.getElementById('editDueDateInput').value = task.data.due_date || '';

    document.getElementById('editModal').classList.remove('hidden');
}

// Close edit modal
function closeEditModal() {
    editingTaskId = null;
    document.getElementById('editModal').classList.add('hidden');
}

// Save edited task
async function saveTaskEdit() {
    if (!editingTaskId) return;

    try {
        const task = await pt.get(editingTaskId);

        const updatedData = {
            ...task.data,
            text: document.getElementById('editTaskText').value.trim(),
            assignee_id: parseInt(document.getElementById('editAssigneeInput').value),
            priority: document.getElementById('editPriorityInput').value,
            due_date: document.getElementById('editDueDateInput').value || null
        };

        await pt.edit(editingTaskId, updatedData);
        closeEditModal();
        await applyFilters(false);
    } catch (error) {
        console.error('Error saving task:', error);
        alert('Failed to save changes');
    }
}

function quickFilter(type) {
    if (type === 'urgent') {
        document.getElementById('searchInput').value = 'urgent';
    } else if (type === 'today') {
        const today = new Date().toISOString().split('T')[0];
        document.getElementById('dueDateInput').value = today;
    }
    applyFilters();
}

function clearFilters() {
    document.getElementById('searchInput').value = '';
    document.getElementById('statusFilter').value = 'pending';
    selectedPriorities = [];

    const checkboxes = document.querySelectorAll('#priorityOptions input[type="checkbox"]');
    checkboxes.forEach(cb => cb.checked = false);

    document.getElementById('priorityLabel').textContent = 'All Priorities';

    applyFilters();
}

async function clearCompletedTasks() {
    const completedEntities = await pt.list({
        entityNames: ['task'],
        filters: { completed: "true" },
        limit: 1000
    });

    const completedTasks = completedEntities.filter(entity => entity.entity_name === 'task');

    if (completedTasks.length === 0) {
        alert('No completed tasks to clear.');
        return;
    }

    const confirmMessage = `Delete ${completedTasks.length} completed task${completedTasks.length > 1 ? 's' : ''}? This cannot be undone.`;

    if (confirm(confirmMessage)) {
        const deletePromises = completedTasks.map(task => pt.delete(task.id));
        await Promise.all(deletePromises);
        await applyFilters();
        alert(`Deleted ${completedTasks.length} completed task${completedTasks.length > 1 ? 's' : ''}.`);
    }
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    const assigneeId = document.getElementById('assigneeInput').value;
    const priority = document.getElementById('priorityInput').value;
    const dueDate = document.getElementById('dueDateInput').value;

    if (!text) return;

    try {
        await pt.add('task', {
            text: text,
            completed: "false",
            assignee_id: parseInt(assigneeId),
            priority: priority,
            due_date: dueDate || null
        });

        // Only clear task text - preserve other fields for easier batch entry
        document.getElementById('taskInput').value = '';
        await applyFilters();
    } catch (error) {
        console.error('Error adding task:', error);
    }
}

async function toggleTask(taskId) {
    try {
        const task = await pt.get(taskId);
        await pt.edit(taskId, {
            ...task.data,
            completed: task.data.completed === "true" ? "false" : "true"
        });
        await applyFilters();
    } catch (error) {
        console.error('Error toggling task:', error);
    }
}

async function deleteTask(taskId) {
    if (confirm('Delete this task? This cannot be undone.')) {
        try {
            await pt.delete(taskId);
            await applyFilters();
        } catch (error) {
            console.error('Error deleting task:', error);
        }
    }
}

function formatDate(dateString) {
    const date = new Date(dateString);
    const today = new Date();

    if (date.toDateString() === today.toDateString()) {
        return 'Today';
    } else if (date.toDateString() === new Date(today.getTime() + 86400000).toDateString()) {
        return 'Tomorrow';
    } else {
        return date.toLocaleDateString();
    }
}

function getPriorityColor(priority) {
    const colors = {
        high: 'bg-red-100 text-red-800',
        medium: 'bg-yellow-100 text-yellow-800',
        low: 'bg-green-100 text-green-800'
    };
    return colors[priority] || colors.low;
}

function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}
</script>
```

### Key UX Improvements

1. Efficient Batch Entry: Only the description field is cleared after adding a task

2. Smart Date Display: Tasks due today show "Due: Today" without confusing suffixes

3. Multi-Select Priority Filter: Select multiple priorities with efficient server-side filtering

4. Default Pending Filter: Interface defaults to showing only pending tasks

5. Bulk Operations: Clear all completed tasks at once with confirmation

6. Performance Optimization: Server-side filtering for text search, status, and priorities

7. Server-Side Pagination: Loads only 20 tasks per page for faster performance

## Simple Todo Application

A minimal todo app for quick implementation:

```HTML
<div class="container mx-auto p-6 max-w-2xl">
    <h1 class="text-2xl font-bold mb-4">My Tasks</h1>

    <div class="flex gap-2 mb-4">
        <input
            type="text"
            id="taskInput"
            class="flex-1 px-3 py-2 border rounded"
            placeholder="New task..."
        >
        <button
            onclick="addTask()"
            class="bg-blue-500 text-white px-4 py-2 rounded"
        >
            Add
        </button>
    </div>

    <div id="tasksList"></div>
</div>

<script>
async function loadTasks() {
    const entities = await pt.list({
        entityNames: ['task'],
        filters: { completed: false }
    });

    const tasks = entities.filter(e => e.entity_name === 'task');

    document.getElementById('tasksList').innerHTML = tasks.map(task => `
        <div class="bg-white p-4 rounded shadow mb-2 flex justify-between items-center">
            <div class="flex items-center gap-3">
                <input
                    type="checkbox"
                    onchange="toggleTask(${task.id})"
                    class="h-4 w-4"
                >
                <span>${task.data.text}</span>
            </div>
            <button onclick="deleteTask(${task.id})" class="text-red-500">
                Delete
            </button>
        </div>
    `).join('');
}

async function addTask() {
    const text = document.getElementById('taskInput').value.trim();
    if (!text) return;

    await pt.add('task', {
        text: text,
        completed: false
    });

    document.getElementById('taskInput').value = '';
    await loadTasks();
}

async function toggleTask(taskId) {
    const task = await pt.get(taskId);
    await pt.edit(taskId, {
        ...task.data,
        completed: !task.data.completed
    });
    await loadTasks();
}

async function deleteTask(taskId) {
    await pt.delete(taskId);
    await loadTasks();
}

document.addEventListener('DOMContentLoaded', loadTasks);
</script>
```

## Note-Taking Application

A simple note-taking app with markdown support:

```HTML
<div class="container mx-auto p-6">
    <h1 class="text-2xl font-bold mb-4">My Notes</h1>

    <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
        <!-- Notes List -->
        <div class="md:col-span-1">
            <button onclick="createNewNote()" class="w-full bg-blue-500 text-white px-4 py-2 rounded mb-4">
                New Note
            </button>
            <div id="notesList" class="space-y-2"></div>
        </div>

        <!-- Note Editor -->
        <div class="md:col-span-2">
            <div id="noteEditor" class="bg-white rounded-lg shadow p-6">
                <input
                    type="text"
                    id="noteTitle"
                    placeholder="Note title..."
                    class="w-full text-xl font-bold mb-4 px-3 py-2 border rounded"
                >
                <textarea
                    id="noteContent"
                    rows="20"
                    placeholder="Start typing..."
                    class="w-full px-3 py-2 border rounded"
                ></textarea>
                <div class="flex gap-2 mt-4">
                    <button onclick="saveNote()" class="bg-green-500 text-white px-4 py-2 rounded">
                        Save
                    </button>
                    <button onclick="deleteCurrentNote()" class="bg-red-500 text-white px-4 py-2 rounded">
                        Delete
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
let currentNoteId = null;

async function loadNotes() {
    const entities = await pt.list({
        entityNames: ['note'],
        limit: 100
    });

    const notes = entities.filter(e => e.entity_name === 'note');

    document.getElementById('notesList').innerHTML = notes.map(note => `
        <div
            onclick="selectNote(${note.id})"
            class="bg-white p-3 rounded shadow cursor-pointer hover:bg-gray-50 ${currentNoteId === note.id ? 'border-2 border-blue-500' : ''}"
        >
            <div class="font-semibold">${note.data.title || 'Untitled'}</div>
            <div class="text-xs text-gray-500">${new Date(note.updated_at).toLocaleDateString()}</div>
        </div>
    `).join('');
}

async function selectNote(noteId) {
    const note = await pt.get(noteId);
    currentNoteId = noteId;

    document.getElementById('noteTitle').value = note.data.title || '';
    document.getElementById('noteContent').value = note.data.content || '';

    await loadNotes();
}

async function createNewNote() {
    const result = await pt.add('note', {
        title: 'New Note',
        content: ''
    });

    currentNoteId = result.id;
    await loadNotes();
    await selectNote(result.id);
}

async function saveNote() {
    if (!currentNoteId) {
        await createNewNote();
        return;
    }

    const title = document.getElementById('noteTitle').value.trim();
    const content = document.getElementById('noteContent').value;

    const note = await pt.get(currentNoteId);
    await pt.edit(currentNoteId, {
        ...note.data,
        title: title,
        content: content
    });

    await loadNotes();
}

async function deleteCurrentNote() {
    if (!currentNoteId) return;

    if (confirm('Delete this note?')) {
        await pt.delete(currentNoteId);
        currentNoteId = null;
        document.getElementById('noteTitle').value = '';
        document.getElementById('noteContent').value = '';
        await loadNotes();
    }
}

document.addEventListener('DOMContentLoaded', loadNotes);

// Auto-save every 30 seconds
setInterval(() => {
    if (currentNoteId) {
        saveNote();
    }
}, 30000);
</script>
```

## Contact Management

A simple CRM for managing contacts:

```HTML
<div class="container mx-auto p-6">
    <h1 class="text-2xl font-bold mb-4">Contacts</h1>

    <!-- Add Contact Form -->
    <div class="bg-white rounded-lg shadow p-6 mb-6">
        <h2 class="text-lg font-semibold mb-4">Add Contact</h2>
        <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
            <input type="text" id="contactName" placeholder="Name" class="px-3 py-2 border rounded">
            <input type="email" id="contactEmail" placeholder="Email" class="px-3 py-2 border rounded">
            <input type="tel" id="contactPhone" placeholder="Phone" class="px-3 py-2 border rounded">
            <input type="text" id="contactCompany" placeholder="Company" class="px-3 py-2 border rounded">
        </div>
        <button onclick="addContact()" class="mt-4 bg-blue-500 text-white px-4 py-2 rounded">
            Add Contact
        </button>
    </div>

    <!-- Search -->
    <input
        type="text"
        id="searchInput"
        placeholder="Search contacts..."
        class="w-full px-3 py-2 border rounded mb-4"
    >

    <!-- Contacts Grid -->
    <div id="contactsGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
</div>

<script>
let searchTimeout;

async function loadContacts() {
    const searchTerm = document.getElementById('searchInput').value.trim();

    const filters = searchTerm ? {
        $or: [
            { name: { $contains: searchTerm } },
            { email: { $contains: searchTerm } },
            { company: { $contains: searchTerm } }
        ]
    } : {};

    const entities = await pt.list({
        entityNames: ['contact'],
        filters: filters,
        limit: 100
    });

    const contacts = entities.filter(e => e.entity_name === 'contact');

    document.getElementById('contactsGrid').innerHTML = contacts.map(contact => `
        <div class="bg-white rounded-lg shadow p-4">
            <h3 class="font-semibold text-lg">${contact.data.name}</h3>
            <p class="text-sm text-gray-600">${contact.data.email || ''}</p>
            <p class="text-sm text-gray-600">${contact.data.phone || ''}</p>
            <p class="text-sm text-gray-500">${contact.data.company || ''}</p>
            <button onclick="deleteContact(${contact.id})" class="mt-2 text-red-500 text-sm">
                Delete
            </button>
        </div>
    `).join('');
}

async function addContact() {
    const name = document.getElementById('contactName').value.trim();
    const email = document.getElementById('contactEmail').value.trim();
    const phone = document.getElementById('contactPhone').value.trim();
    const company = document.getElementById('contactCompany').value.trim();

    if (!name) return;

    await pt.add('contact', {
        name: name,
        email: email,
        phone: phone,
        company: company
    });

    document.getElementById('contactName').value = '';
    document.getElementById('contactEmail').value = '';
    document.getElementById('contactPhone').value = '';
    document.getElementById('contactCompany').value = '';

    await loadContacts();
}

async function deleteContact(contactId) {
    if (confirm('Delete this contact?')) {
        await pt.delete(contactId);
        await loadContacts();
    }
}

document.getElementById('searchInput').addEventListener('input', () => {
    clearTimeout(searchTimeout);
    searchTimeout = setTimeout(loadContacts, 300);
});

document.addEventListener('DOMContentLoaded', loadContacts);
</script>
```

## Chat Integration - Messages and File Upload

This example demonstrates how to send messages to the chat and upload files from your Live Page.

### Features

* Send text messages to the chat from your Live Page

* Upload files with optional messages

* Drag and drop file upload support

* File preview and validation

* Status notifications

### Complete Implementation

```HTML
<div class="container mx-auto p-6 max-w-4xl">
    <h1 class="text-3xl font-bold text-gray-800 mb-8">Chat Integration Demo</h1>

    <!-- Send Message Section -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
        <h2 class="text-xl font-semibold mb-4">Send Message to Chat</h2>

        <div class="flex gap-2 mb-4">
            <input
                type="text"
                id="chatInput"
                placeholder="Type a message..."
                class="flex-1 border rounded px-3 py-2"
                onkeypress="if(event.key==='Enter') sendChatMessage()"
            >
            <button
                onclick="sendChatMessage()"
                class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
            >
                Send
            </button>
        </div>

        <!-- Quick Message Buttons -->
        <div class="flex flex-wrap gap-2">
            <button
                onclick="pt.addMessage('Task completed!')"
                class="bg-green-100 text-green-800 px-3 py-1 rounded text-sm hover:bg-green-200"
            >
                Quick: Task Completed
            </button>
            <button
                onclick="pt.addMessage('Need assistance with this task')"
                class="bg-yellow-100 text-yellow-800 px-3 py-1 rounded text-sm hover:bg-yellow-200"
            >
                Quick: Need Help
            </button>
            <button
                onclick="pt.addMessage('Review requested')"
                class="bg-purple-100 text-purple-800 px-3 py-1 rounded text-sm hover:bg-purple-200"
            >
                Quick: Review Requested
            </button>
        </div>
    </div>

    <!-- File Upload Section -->
    <div class="bg-white rounded-lg shadow-md p-6 mb-6">
        <h2 class="text-xl font-semibold mb-4">Upload Files to Chat</h2>

        <!-- Standard Form Upload -->
        <form id="uploadForm" onsubmit="handleUpload(event)" class="mb-6">
            <div class="mb-4">
                <label class="block text-sm font-medium mb-2">Select Files</label>
                <input
                    type="file"
                    name="files"
                    multiple
                    class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
                    onchange="previewFiles(this)"
                >
            </div>

            <div class="mb-4">
                <label class="block text-sm font-medium mb-2">Message (optional)</label>
                <input
                    type="text"
                    name="message"
                    placeholder="Add a message with your files..."
                    class="border rounded px-3 py-2 w-full"
                >
            </div>

            <div id="filePreview" class="mb-4"></div>

            <button
                type="submit"
                class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
            >
                Upload Files
            </button>
        </form>

        <!-- Drag and Drop Zone -->
        <div
            id="dropZone"
            ondrop="handleDrop(event)"
            ondragover="handleDragOver(event)"
            ondragleave="handleDragLeave(event)"
            class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center transition-colors"
        >
            <svg class="mx-auto h-12 w-12 text-gray-400 mb-3" stroke="currentColor" fill="none" viewBox="0 0 48 48">
                <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
            <p class="text-gray-600 font-medium mb-1">Drop files here to upload</p>
            <p class="text-gray-400 text-sm">or use the file selector above</p>
        </div>
    </div>

    <!-- Status Messages -->
    <div id="statusMessage" class="hidden rounded-lg p-4 mb-4"></div>

    <!-- Recent Actions Log -->
    <div class="bg-white rounded-lg shadow-md p-6">
        <h2 class="text-xl font-semibold mb-4">Recent Actions</h2>
        <div id="actionsLog" class="space-y-2 max-h-64 overflow-y-auto"></div>
    </div>
</div>

<script>
const actionsLog = [];

// Send message from input
async function sendChatMessage() {
    const input = document.getElementById('chatInput');
    const message = input.value.trim();

    if (!message) {
        showStatus('Please enter a message', 'error');
        return;
    }

    try {
        const result = await pt.addMessage(message);
        input.value = '';
        showStatus('Message sent successfully!', 'success');
        logAction('Message sent', message);
    } catch (error) {
        showStatus('Failed to send message: ' + error.message, 'error');
        console.error('Error:', error);
    }
}

// Handle form upload
async function handleUpload(event) {
    event.preventDefault();
    const form = event.target;
    const fileInput = form.querySelector('input[type="file"]');

    if (fileInput.files.length === 0) {
        showStatus('Please select at least one file', 'error');
        return;
    }

    try {
        const result = await pt.uploadFiles(form);
        showStatus(`Successfully uploaded ${result.files_count} file(s)!`, 'success');
        logAction('File upload', `${result.files_count} file(s) uploaded`);
        form.reset();
        document.getElementById('filePreview').innerHTML = '';
    } catch (error) {
        showStatus('Upload failed: ' + error.message, 'error');
        console.error('Upload error:', error);
    }
}

// Handle drag and drop
async function handleDrop(event) {
    event.preventDefault();
    const dropZone = document.getElementById('dropZone');
    dropZone.classList.remove('border-blue-500', 'bg-blue-50');

    const files = event.dataTransfer.files;
    if (files.length === 0) return;

    const formData = new FormData();
    for (const file of files) {
        formData.append('files', file);
    }

    try {
        const result = await pt.uploadFiles(formData, 'Files uploaded via drag & drop');
        showStatus(`Uploaded ${result.files_count} file(s) via drag & drop`, 'success');
        logAction('Drag & drop upload', `${result.files_count} file(s)`);
    } catch (error) {
        showStatus('Upload failed: ' + error.message, 'error');
        console.error('Upload error:', error);
    }
}

function handleDragOver(event) {
    event.preventDefault();
    const dropZone = document.getElementById('dropZone');
    dropZone.classList.add('border-blue-500', 'bg-blue-50');
}

function handleDragLeave(event) {
    const dropZone = document.getElementById('dropZone');
    dropZone.classList.remove('border-blue-500', 'bg-blue-50');
}

// Preview selected files
function previewFiles(input) {
    const preview = document.getElementById('filePreview');
    const files = input.files;

    if (files.length === 0) {
        preview.innerHTML = '';
        return;
    }

    const fileList = Array.from(files).map(file => {
        const sizeKB = (file.size / 1024).toFixed(2);
        return `
            <div class="flex items-center gap-2 text-sm text-gray-600 bg-gray-50 p-2 rounded">
                <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
                </svg>
                <span class="flex-1">${file.name}</span>
                <span class="text-gray-400">${sizeKB} KB</span>
            </div>
        `;
    }).join('');

    preview.innerHTML = `
        <div class="space-y-1">
            <p class="text-sm font-medium text-gray-700 mb-2">${files.length} file(s) selected:</p>
            ${fileList}
        </div>
    `;
}

// Show status message
function showStatus(message, type = 'success') {
    const statusDiv = document.getElementById('statusMessage');
    const bgColor = type === 'success' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';

    statusDiv.className = `rounded-lg p-4 mb-4 ${bgColor}`;
    statusDiv.textContent = message;
    statusDiv.classList.remove('hidden');

    setTimeout(() => {
        statusDiv.classList.add('hidden');
    }, 5000);
}

// Log action
function logAction(action, details) {
    const timestamp = new Date().toLocaleTimeString();
    actionsLog.unshift({ action, details, timestamp });

    // Keep only last 10 actions
    if (actionsLog.length > 10) {
        actionsLog.pop();
    }

    updateActionsLog();
}

function updateActionsLog() {
    const logDiv = document.getElementById('actionsLog');

    if (actionsLog.length === 0) {
        logDiv.innerHTML = '<p class="text-gray-400 text-sm">No actions yet</p>';
        return;
    }

    logDiv.innerHTML = actionsLog.map(log => `
        <div class="flex items-start gap-3 p-3 bg-gray-50 rounded">
            <div class="flex-1">
                <div class="font-medium text-sm">${log.action}</div>
                <div class="text-sm text-gray-600">${log.details}</div>
            </div>
            <div class="text-xs text-gray-400">${log.timestamp}</div>
        </div>
    `).join('');
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    updateActionsLog();
});
</script>
```

### Key Features Explained

Send Messages:

* Text input with Enter key support

* Quick message buttons for common actions

* Instant feedback on message delivery

File Upload:

* Traditional file selector with multiple file support

* Optional message to accompany uploads

* File preview with size information

* Drag and drop upload zone

* Visual feedback during drag operations

Status Notifications:

* Success and error messages

* Auto-hide after 5 seconds

* Clear visual distinction

Actions Log:

* Tracks recent actions (messages and uploads)

* Displays timestamps

* Limited to last 10 actions

### Use Cases

1. Project Management: Send status updates and upload deliverables

2. Support Systems: Submit issues with file attachments

3. Collaboration: Share files and communicate progress

4. Reporting: Upload reports and notify team members

5. Feedback Collection: Submit feedback with supporting documents

## Document Management and Notifications

This comprehensive example demonstrates document search, viewing, creation, and push notifications.

### Features

* Search documents and collections using semantic search

* View document content with optional range selection

* Create and save documents in multiple formats (TXT, MD, PDF, DOCX, CSV, XLSX)

* Send push notifications to team members

* Format-specific MIME type handling

* Real-time status updates

### Complete Implementation

```HTML
<div class="container mx-auto p-6 max-w-6xl">
    <h1 class="text-3xl font-bold text-gray-800 mb-8">Document Management System</h1>

    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <!-- Search Documents -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold mb-4 flex items-center">
                <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
                </svg>
                Search Documents
            </h2>

            <input
                type="text"
                id="searchQuery"
                placeholder="What are you looking for?"
                class="border rounded px-3 py-2 w-full mb-3"
            >

            <select id="searchScope" class="border rounded px-3 py-2 w-full mb-3">
                <option value="ALL">All (Documents & Collections)</option>
                <option value="DOCUMENTS_ONLY">Documents Only</option>
                <option value="COLLECTIONS_ONLY">Collections Only</option>
            </select>

            <button
                onclick="searchDocuments()"
                class="bg-blue-500 text-white px-4 py-2 rounded w-full hover:bg-blue-600"
            >
                Search
            </button>

            <div id="searchResults" class="mt-4 hidden">
                <h3 class="font-semibold mb-2">Results:</h3>
                <div class="bg-gray-50 rounded p-3 max-h-80 overflow-auto">
                    <pre id="resultsContent" class="text-sm whitespace-pre-wrap"></pre>
                </div>
            </div>
        </div>

        <!-- View Document -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold mb-4 flex items-center">
                <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
                </svg>
                View Document
            </h2>

            <input
                type="number"
                id="viewDocId"
                placeholder="Document ID"
                class="border rounded px-3 py-2 w-full mb-3"
            >

            <div class="grid grid-cols-2 gap-2 mb-3">
                <input
                    type="number"
                    id="fromChar"
                    placeholder="From (optional)"
                    class="border rounded px-3 py-2"
                >
                <input
                    type="number"
                    id="toChar"
                    placeholder="To (optional)"
                    class="border rounded px-3 py-2"
                >
            </div>

            <button
                onclick="viewDocument()"
                class="bg-green-500 text-white px-4 py-2 rounded w-full hover:bg-green-600"
            >
                View Text
            </button>

            <div id="docView" class="mt-4 hidden">
                <h3 class="font-semibold mb-2">Document Content:</h3>
                <div class="bg-gray-50 rounded p-3 max-h-80 overflow-auto">
                    <pre id="docText" class="text-sm whitespace-pre-wrap"></pre>
                </div>
            </div>
        </div>

        <!-- Create Document -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold mb-4 flex items-center">
                <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
                </svg>
                Create Document
            </h2>

            <input
                type="text"
                id="docFilename"
                placeholder="Filename (e.g., report.txt)"
                class="border rounded px-3 py-2 w-full mb-3"
            >

            <select id="docFormat" class="border rounded px-3 py-2 w-full mb-3" onchange="updateMimetype()">
                <option value="TXT">TXT - Plain Text</option>
                <option value="MD">MD - Markdown</option>
                <option value="HTML">HTML</option>
                <option value="DOCX">DOCX - Word Document (use Markdown)</option>
                <option value="PDF">PDF (use Markdown)</option>
                <option value="CSV">CSV</option>
                <option value="XLSX">XLSX - Excel (use CSV format)</option>
                <option value="CUSTOM">CUSTOM</option>
            </select>

            <input
                type="text"
                id="docMimetype"
                value="text/plain"
                class="border rounded px-3 py-2 w-full mb-3 text-sm text-gray-600"
                readonly
            >

            <textarea
                id="docContent"
                rows="8"
                placeholder="Enter document content..."
                class="border rounded px-3 py-2 w-full mb-3 font-mono text-sm"
            ></textarea>

            <button
                onclick="saveDocument()"
                class="bg-purple-500 text-white px-4 py-2 rounded w-full hover:bg-purple-600"
            >
                Save Document
            </button>
        </div>

        <!-- Send Notification -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold mb-4 flex items-center">
                <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
                </svg>
                Send Notification
            </h2>

            <select id="userSelect" class="border rounded px-3 py-2 w-full mb-3">
                <option value="">Select a user...</option>
            </select>

            <input
                type="text"
                id="notifTitle"
                placeholder="Notification title"
                class="border rounded px-3 py-2 w-full mb-3"
            >

            <textarea
                id="notifText"
                rows="4"
                placeholder="Notification message"
                class="border rounded px-3 py-2 w-full mb-3"
            ></textarea>

            <button
                onclick="sendNotification()"
                class="bg-orange-500 text-white px-4 py-2 rounded w-full hover:bg-orange-600"
            >
                Send Notification
            </button>
        </div>
    </div>

    <!-- Status Messages -->
    <div id="statusMessage" class="hidden mt-6 rounded-lg p-4"></div>
</div>

<script>
// MIME type mapping
const mimeTypes = {
    'TXT': 'text/plain',
    'MD': 'text/markdown',
    'HTML': 'text/html',
    'DOCX': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'PDF': 'application/pdf',
    'CSV': 'text/csv',
    'XLSX': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'CUSTOM': 'text/plain'
};

// Load users on page load
async function loadUsers() {
    try {
        const members = await pt.getChatMembers();
        const select = document.getElementById('userSelect');

        members
            .filter(m => m.type === 'user')
            .forEach(member => {
                const option = document.createElement('option');
                option.value = member.id;
                option.textContent = member.name;
                select.appendChild(option);
            });
    } catch (error) {
        console.error('Error loading users:', error);
    }
}

function updateMimetype() {
    const format = document.getElementById('docFormat').value;
    document.getElementById('docMimetype').value = mimeTypes[format] || 'text/plain';
}

// Search documents
async function searchDocuments() {
    const query = document.getElementById('searchQuery').value.trim();
    const scope = document.getElementById('searchScope').value;

    if (!query) {
        showStatus('Please enter a search query', 'error');
        return;
    }

    try {
        const result = await pt.searchDocuments(query, scope);

        document.getElementById('resultsContent').textContent = result.results;
        document.getElementById('searchResults').classList.remove('hidden');
        showStatus('Search completed successfully', 'success');
    } catch (error) {
        showStatus('Search failed: ' + error.message, 'error');
        console.error('Search error:', error);
    }
}

// View document
async function viewDocument() {
    const docId = parseInt(document.getElementById('viewDocId').value);
    const fromChar = document.getElementById('fromChar').value;
    const toChar = document.getElementById('toChar').value;

    if (!docId) {
        showStatus('Please enter a document ID', 'error');
        return;
    }

    try {
        const options = {};
        if (fromChar) options.from = parseInt(fromChar);
        if (toChar) options.to = parseInt(toChar);

        const result = await pt.getDocumentText(docId, options);

        if (result.text) {
            document.getElementById('docText').textContent = result.text;
            document.getElementById('docView').classList.remove('hidden');
            showStatus('Document loaded successfully', 'success');
        } else {
            showStatus(result.message || 'No text available', 'error');
        }
    } catch (error) {
        showStatus('Error loading document: ' + error.message, 'error');
        console.error('View error:', error);
    }
}

// Save document
async function saveDocument() {
    const filename = document.getElementById('docFilename').value.trim();
    const format = document.getElementById('docFormat').value;
    const mimetype = document.getElementById('docMimetype').value;
    const content = document.getElementById('docContent').value;

    if (!filename || !content) {
        showStatus('Please provide filename and content', 'error');
        return;
    }

    try {
        const result = await pt.saveDocument(filename, format, mimetype, content);

        showStatus('Document saved successfully: ' + result.filename, 'success');

        // Clear form
        document.getElementById('docFilename').value = '';
        document.getElementById('docContent').value = '';
    } catch (error) {
        showStatus('Error saving document: ' + error.message, 'error');
        console.error('Save error:', error);
    }
}

// Send notification
async function sendNotification() {
    const userId = parseInt(document.getElementById('userSelect').value);
    const title = document.getElementById('notifTitle').value.trim();
    const text = document.getElementById('notifText').value.trim();

    if (!userId || !title || !text) {
        showStatus('Please fill all notification fields', 'error');
        return;
    }

    try {
        await pt.sendNotification(userId, title, text);

        showStatus('Notification sent successfully!', 'success');

        // Clear form
        document.getElementById('notifTitle').value = '';
        document.getElementById('notifText').value = '';
    } catch (error) {
        showStatus('Error sending notification: ' + error.message, 'error');
        console.error('Notification error:', error);
    }
}

// Show status message
function showStatus(message, type = 'success') {
    const statusDiv = document.getElementById('statusMessage');
    const bgColor = type === 'success' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';

    statusDiv.className = `mt-6 rounded-lg p-4 ${bgColor}`;
    statusDiv.textContent = message;
    statusDiv.classList.remove('hidden');

    setTimeout(() => {
        statusDiv.classList.add('hidden');
    }, 5000);
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    loadUsers();
});
</script>
```

### Key Features Explained

Document Search:

* Semantic search across documents and collections

* Scope selection (all, documents only, collections only)

* XML-formatted search results display

Document Viewer:

* Load full document text or specific character ranges

* Useful for previewing large documents

* Text-based content display

Document Creator:

* Multiple format support (TXT, MD, HTML, DOCX, PDF, CSV, XLSX)

* Automatic MIME type selection

* Markdown to Word/PDF conversion

* CSV to Excel conversion

Push Notifications:

* Send notifications to specific users

* Custom title and message

* User selection from chat members

### Practical Use Cases

1. Knowledge Base: Search documentation and create new articles

2. Report Generation: Create PDF/Word reports from data

3. Team Collaboration: Notify team members of important updates

4. Document Library: Browse and view document contents

5. Data Export: Export data to CSV/Excel formats

## AI-Powered Database Automation

This example demonstrates how to combine Live Page actions with AI-powered database operations. The AI can process your instructions and automatically manage database entities.

### Features

* Upload files and have AI extract structured data automatically

* Send instructions to process information and store in database

* Combine multiple operations in a single AI request

* Get intelligent data validation and error handling

### Complete Implementation: Intelligent Document Processor

```HTML
<div class="container mx-auto p-6 max-w-6xl">
    <h1 class="text-3xl font-bold text-gray-800 mb-8">AI-Powered Data Processor</h1>

    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <!-- Upload Files for AI Processing -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold mb-4">Upload & Extract Data</h2>

            <div class="mb-4">
                <label class="block text-sm font-medium mb-2">Select Processing Type</label>
                <select id="processingType" class="border rounded px-3 py-2 w-full" onchange="updateInstructions()">
                    <option value="invoice">Invoice Processing</option>
                    <option value="resume">Resume Parsing</option>
                    <option value="receipt">Expense Receipts</option>
                    <option value="meeting">Meeting Notes</option>
                    <option value="contacts">Contact Import</option>
                    <option value="custom">Custom Instructions</option>
                </select>
            </div>

            <div class="mb-4">
                <label class="block text-sm font-medium mb-2">Upload Files</label>
                <input
                    type="file"
                    id="fileInput"
                    multiple
                    class="block w-full text-sm"
                    onchange="previewFiles()"
                >
                <div id="filePreview" class="mt-2 text-sm text-gray-600"></div>
            </div>

            <div class="mb-4">
                <label class="block text-sm font-medium mb-2">AI Instructions</label>
                <textarea
                    id="aiInstructions"
                    rows="8"
                    class="border rounded px-3 py-2 w-full font-mono text-sm"
                    placeholder="Enter custom instructions for the AI..."
                ></textarea>
            </div>

            <button
                onclick="processFiles()"
                class="bg-blue-500 text-white px-6 py-2 rounded w-full hover:bg-blue-600"
            >
                Process with AI
            </button>
        </div>

        <!-- Direct AI Commands -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold mb-4">Direct AI Commands</h2>

            <div class="space-y-4">
                <!-- Quick Actions -->
                <div>
                    <h3 class="font-medium mb-2">Quick Actions</h3>
                    <div class="grid grid-cols-2 gap-2">
                        <button
                            onclick="quickAction('competitors')"
                            class="bg-green-100 text-green-800 px-3 py-2 rounded text-sm hover:bg-green-200"
                        >
                            Research Competitors
                        </button>
                        <button
                            onclick="quickAction('tasks')"
                            class="bg-blue-100 text-blue-800 px-3 py-2 rounded text-sm hover:bg-blue-200"
                        >
                            Analyze Tasks
                        </button>
                        <button
                            onclick="quickAction('summary')"
                            class="bg-purple-100 text-purple-800 px-3 py-2 rounded text-sm hover:bg-purple-200"
                        >
                            Generate Summary
                        </button>
                        <button
                            onclick="quickAction('export')"
                            class="bg-orange-100 text-orange-800 px-3 py-2 rounded text-sm hover:bg-orange-200"
                        >
                            Export Data
                        </button>
                    </div>
                </div>

                <!-- Custom Command -->
                <div>
                    <h3 class="font-medium mb-2">Custom AI Command</h3>
                    <textarea
                        id="customCommand"
                        rows="6"
                        placeholder="Enter your command for the AI assistant..."
                        class="border rounded px-3 py-2 w-full text-sm"
                    ></textarea>
                    <button
                        onclick="sendCustomCommand()"
                        class="bg-indigo-500 text-white px-4 py-2 rounded w-full mt-2 hover:bg-indigo-600"
                    >
                        Execute Command
                    </button>
                </div>
            </div>

            <div class="mt-6 p-4 bg-gray-50 rounded">
                <h3 class="font-medium mb-2">Example Commands:</h3>
                <ul class="text-sm text-gray-700 space-y-1">
                    <li>• "Find all overdue tasks and create urgent reminders"</li>
                    <li>• "Search for industry trends and save as research notes"</li>
                    <li>• "Analyze recent expenses and categorize them"</li>
                    <li>• "Extract key contacts from meeting notes"</li>
                </ul>
            </div>
        </div>
    </div>

    <!-- Status Messages -->
    <div id="statusMessage" class="hidden mt-6 rounded-lg p-4"></div>
</div>

<script>
// Predefined instruction templates
const instructionTemplates = {
    invoice: `Extract invoice information and use the tool 'chatdb_add' to create database records:
- entity_name: "invoice"
- data: {
    invoice_number: string,
    date: string,
    vendor: string,
    amount: number,
    due_date: string,
    line_items: array
  }`,

    resume: `Extract candidate information from resumes and use the tool 'chatdb_add' to create entries:
- entity_name: "candidate"
- data: {
    name: string,
    email: string,
    phone: string,
    years_experience: number,
    skills: array,
    education: string,
    previous_companies: array
  }`,

    receipt: `Extract expense data from receipts and use the tool 'chatdb_add' to store each:
- entity_name: "expense"
- data: {
    date: string,
    merchant: string,
    category: string,
    amount: number,
    currency: string
  }`,

    meeting: `Extract action items from meeting notes and use the tool 'chatdb_add' to create entries:
- entity_name: "action_item"
- data: {
    description: string,
    assigned_to: string,
    due_date: string,
    priority: string
  }`,

    contacts: `Parse contact information and use the tool 'chatdb_add' to create entries:
- entity_name: "contact"
- data: {
    name: string,
    email: string,
    phone: string,
    company: string,
    role: string
  }`
};

function updateInstructions() {
    const type = document.getElementById('processingType').value;
    const textarea = document.getElementById('aiInstructions');

    if (type !== 'custom' && instructionTemplates[type]) {
        textarea.value = instructionTemplates[type];
    } else if (type === 'custom') {
        textarea.value = '';
        textarea.placeholder = 'Enter your custom instructions...';
    }
}

function previewFiles() {
    const input = document.getElementById('fileInput');
    const preview = document.getElementById('filePreview');

    if (input.files.length === 0) {
        preview.textContent = '';
        return;
    }

    const fileList = Array.from(input.files)
        .map(f => f.name)
        .join(', ');

    preview.textContent = `${input.files.length} file(s) selected: ${fileList}`;
}

async function processFiles() {
    const input = document.getElementById('fileInput');
    const instructions = document.getElementById('aiInstructions').value.trim();

    if (input.files.length === 0) {
        showStatus('Please select files to process', 'error');
        return;
    }

    if (!instructions) {
        showStatus('Please provide AI instructions', 'error');
        return;
    }

    try {
        const formData = new FormData();
        for (const file of input.files) {
            formData.append('files', file);
        }

        await pt.uploadFiles(formData, instructions);

        showStatus(
            `Files uploaded successfully! The AI is processing ${input.files.length} file(s) and will store the data according to your instructions.`,
            'success'
        );

        // Clear form
        input.value = '';
        document.getElementById('filePreview').textContent = '';
    } catch (error) {
        showStatus('Error processing files: ' + error.message, 'error');
    }
}

async function quickAction(type) {
    const commands = {
        competitors: `Search the web for the top 5 competitors in the AI assistant market and use the tool 'chatdb_add' to create database entries:
- entity_name: "competitor"
- data: { name: string, website: string, key_features: array, pricing_model: string, market_position: string }

Research each competitor and provide comprehensive information.`,

        tasks: `Use the tool 'chatdb_list' to find all tasks with status "pending" or "in_progress".
For any task that is overdue or high priority, use the tool 'chatdb_add' to create:
- entity_name: "urgent_action"
- data: { task_id: number, title: string, days_overdue: number, priority: string, recommended_action: string }`,

        summary: `Use the tool 'chatdb_list' to review all activities from the past week.
Then use the tool 'chatdb_add' to create a weekly summary:
- entity_name: "weekly_summary"
- data: {
    week_ending: string,
    tasks_completed: number,
    documents_created: number,
    key_highlights: array,
    upcoming_priorities: array
  }`,

        export: `Use the tool 'chatdb_list' to get all contacts, then export them to a CSV file with columns: name, email, phone, company, tags.
Save it as "contacts_export_[date].csv" using the appropriate document creation tool and notify me when complete.`
    };

    try {
        await pt.addMessage(commands[type]);
        showStatus('AI command sent successfully! Processing...', 'success');
    } catch (error) {
        showStatus('Error: ' + error.message, 'error');
    }
}

async function sendCustomCommand() {
    const command = document.getElementById('customCommand').value.trim();

    if (!command) {
        showStatus('Please enter a command', 'error');
        return;
    }

    try {
        await pt.addMessage(command);
        showStatus('Custom command sent to AI assistant!', 'success');
        document.getElementById('customCommand').value = '';
    } catch (error) {
        showStatus('Error: ' + error.message, 'error');
    }
}

function showStatus(message, type = 'success') {
    const statusDiv = document.getElementById('statusMessage');
    const bgColor = type === 'success' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700';

    statusDiv.className = `mt-6 rounded-lg p-4 ${bgColor}`;
    statusDiv.textContent = message;
    statusDiv.classList.remove('hidden');

    setTimeout(() => {
        statusDiv.classList.add('hidden');
    }, 8000);
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    updateInstructions();
});
</script>
```

### Key Features Explained

AI-Powered File Processing:

* Upload files with specific extraction instructions

* AI automatically parses content and creates database records

* Supports multiple file types and batch processing

* Intelligent error handling and data validation

Direct AI Commands:

* Send natural language instructions to the AI

* AI can search, analyze, and store data autonomously

* Quick action buttons for common workflows

* Custom command interface for flexibility

Real-World Applications:

1. Invoice Processing: Upload invoices, AI extracts data, stores in database

2. Resume Screening: Batch process resumes, extract candidate info

3. Expense Management: Upload receipts, AI categorizes and stores expenses

4. Meeting Notes: Upload recordings/notes, AI extracts action items

5. Data Migration: Upload spreadsheets, AI imports and validates data

6. Competitive Research: AI searches and compiles competitor information

7. Task Analysis: AI reviews tasks and creates urgency reports

8. Report Generation: AI compiles data and generates formatted reports

### Benefits

* Automation: Eliminate manual data entry and processing

* Intelligence: AI understands context and validates data

* Flexibility: Use natural language to describe what you need

* Batch Processing: Handle multiple files/records at once

* Error Handling: AI catches and reports data issues

* Structured Storage: Data automatically organized in database

## Document Processing with Parallel Upload

This advanced example demonstrates how to handle multiple file uploads concurrently with AI-powered topic extraction. Files are processed in parallel for maximum performance, with the AI extracting topics and returning structured JSON responses.

### Features

* Upload multiple PDF/DOCX files concurrently (parallel processing)

* AI extracts topics and returns structured JSON

* Store file metadata with processing status

* "Check" button for files still waiting for processing

* Auto-refresh table every 10 seconds

* Document text preview modal

* Handles large document processing delays gracefully

* Includes both parallel and serial upload examples

### Use Case

Perfect for applications where:

* You need to upload and process multiple documents at once

* AI should extract information (topics, metadata, etc.) from documents

* You want fast, concurrent processing instead of sequential

* Documents may or may not have text immediately available

* Users can manually re-check documents that weren't ready on first upload

### Complete Implementation

```HTML
<div class="container mx-auto p-6 max-w-4xl">
  <!-- Header -->
  <div class="flex items-center justify-between mb-6">
    <div class="flex items-center gap-3 text-gray-700">
      <h1 class="text-xl md:text-2xl font-bold">Topic extractor</h1>
    </div>
    <button class="px-3 py-2 rounded bg-gray-100 hover:bg-gray-200 text-sm" id="refreshBtn">Refresh</button>
  </div>

  <!-- Uploader -->
  <div class="bg-white rounded-xl shadow p-4">
    <p class="text-gray-600 text-sm mb-3">Upload .pdf or .docx (≤ 1MB). The AI will wait up to 30s for text extraction. If ready, it will write a Topic and add a DB row (entity: <code>file_topic</code>). If not ready in time, it will add a row with upload_status="waiting".</p>
    <div class="flex flex-col md:flex-row md:items-center gap-3">
      <input accept=".pdf,.docx" class="block w-full text-sm text-gray-700" id="fileInput" multiple type="file" />
      <button class="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 flex items-center gap-2" id="uploadBtn">
        <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1M12 12V4m0 0l-3 3m3-3l3 3" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
        </svg>
        Upload
      </button>
    </div>
    <div class="hidden mt-3 rounded p-3 text-sm" id="uploadNotice"></div>
  </div>

  <!-- Table -->
  <div class="mt-6 bg-white rounded-xl shadow p-4">
    <div class="flex items-center justify-between mb-3">
      <div class="text-sm text-gray-500">Auto-refreshes every 10s</div>
    </div>
    <div class="overflow-x-auto">
      <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
          <tr>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">File Name</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Topic</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Uploaded At</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Upload Status</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Actions</th>
          </tr>
        </thead>
        <tbody class="divide-y divide-gray-100 bg-white" id="rows"></tbody>
      </table>
    </div>
    <div class="hidden text-center text-gray-500 text-sm py-8" id="emptyState">No uploads yet</div>
  </div>

  <!-- Viewer Modal -->
  <div class="hidden fixed inset-0 bg-black/50 z-50 items-center justify-center" id="viewerModal">
    <div class="bg-white rounded-lg shadow-xl max-w-3xl w-full mx-4">
      <div class="flex items-center justify-between p-4 border-b">
        <h3 class="font-semibold text-gray-800">Document Preview</h3>
        <button class="text-gray-500 hover:text-gray-700" id="closeViewer">✕</button>
      </div>
      <div class="p-4 max-h-[60vh] overflow-auto">
        <pre class="whitespace-pre-wrap text-sm text-gray-800" id="viewerText"></pre>
      </div>
    </div>
  </div>

  <script>
  (function(){
    const rowsEl = document.getElementById('rows');
    const emptyEl = document.getElementById('emptyState');
    const refreshBtn = document.getElementById('refreshBtn');
    const uploadBtn = document.getElementById('uploadBtn');
    const fileInput = document.getElementById('fileInput');
    const noticeEl = document.getElementById('uploadNotice');
    const viewerModal = document.getElementById('viewerModal');
    const closeViewer = document.getElementById('closeViewer');
    const viewerText = document.getElementById('viewerText');

    const MAX_SIZE = 1024 * 1024; // 1MB
    let autoTimer = null;

    function setNotice(text, type){
      if(!text){ noticeEl.classList.add('hidden'); noticeEl.textContent = ''; return; }
      const base = 'mt-3 rounded p-3 text-sm ';
      if(type==='success') noticeEl.className = base + 'bg-green-50 border border-green-200 text-green-800';
      else if(type==='warn') noticeEl.className = base + 'bg-yellow-50 border border-yellow-200 text-yellow-800';
      else noticeEl.className = base + 'bg-red-50 border border-red-200 text-red-800';
      noticeEl.textContent = text; noticeEl.classList.remove('hidden');
    }

    function badge(status){
      const map = {
        success: 'bg-green-100 text-green-800',
        waiting: 'bg-yellow-100 text-yellow-800',
        error:   'bg-red-100 text-red-800',
        pending: 'bg-gray-100 text-gray-800'
      };
      const cls = map[status] || map.pending;
      const label = (status||'pending').toUpperCase();
      return `<span class="px-2 py-1 text-xs rounded ${cls}">${label}</span>`;
    }

    function fmtDate(s){ try { return new Date(s).toLocaleString(); } catch(e){ return s || ''; } }

    function esc(str){
      const d = document.createElement('div'); d.textContent = String(str ?? '');
      return d.innerHTML;
    }

    function rowHTML(item){
      const d = item.data || {};
      const file = d.file_name || '';
      const topic = (d.topic === null || d.topic === undefined || d.topic === '') ? 'Pending' : d.topic;
      const status = d.upload_status || 'pending';
      const canView = !!d.document_id;
      const viewBtnCls = 'px-3 py-1.5 rounded ' + (canView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-gray-200 text-gray-500 cursor-not-allowed');
      const viewDisabled = canView ? '' : 'disabled';
      const docId = d.document_id ? String(d.document_id) : '';

      const checkBtn = status === 'waiting'
        ? `<button class="px-2.5 py-1.5 rounded bg-amber-600 hover:bg-amber-700 text-white text-xs" data-entity-id="${item.id}" data-document-id="${docId}" data-file-name="${esc(file)}" onclick="window.__checkWaiting(event)">Check</button>`
        : '';

      return `
        <tr>
          <td class="px-4 py-2 text-sm text-gray-800">${esc(file)}</td>
          <td class="px-4 py-2 text-sm text-gray-700">${esc(topic)}</td>
          <td class="px-4 py-2 text-sm text-gray-500">${fmtDate(item.created_at)}</td>
          <td class="px-4 py-2 text-sm">${badge(status)}</td>
          <td class="px-4 py-2 text-sm flex items-center gap-2">
            <button class="${viewBtnCls}" ${viewDisabled} onclick="window.__openViewer(${docId || 'null'})">View</button>
            ${checkBtn}
          </td>
        </tr>
`;
}

    async function loadRows(){
      try{
        const entities = await pt.list({ entityNames: ['file_topic'], limit: 200 });
        const items = (entities || []).filter(e => e.entity_name === 'file_topic');
        if(items.length === 0){
          rowsEl.innerHTML = '';
          emptyEl.classList.remove('hidden');
          return;
        }
        emptyEl.classList.add('hidden');
        rowsEl.innerHTML = items.map(rowHTML).join('');
      }catch(err){
        setNotice('Failed to load rows. Please try Refresh.', 'error');
      }
    }

    function setUploading(on){
      uploadBtn.disabled = !!on;
      if(on){ uploadBtn.classList.add('opacity-60','cursor-not-allowed'); }
      else { uploadBtn.classList.remove('opacity-60','cursor-not-allowed'); }
    }

    // NEW: upload multiple files concurrently
    async function uploadFiles(){
      const files = Array.from(fileInput.files || []);
      if(files.length === 0){ setNotice('Please select one or more files.', 'warn'); return; }

      const allowed = ['pdf','docx'];
      let skipped = 0;

      // Build parallel jobs for valid files
      const jobs = files.map(file => {
        const ext = (file.name.split('.').pop() || '').toLowerCase();
        if(!allowed.includes(ext) || file.size > MAX_SIZE){
          skipped++;
          return null; // mark as skipped
        }

        const formData = new FormData();
        formData.append('files', file);

        // Instruction message (kept as in current flow)
        const msg = `AI Processing Request

You are given a file uploaded from a PrimeThink Live Page as an attachment to THIS message. Follow EXACTLY:

1) If the documents attached have text: derive a concise topic (<= 6 words, noun phrase) for each one. Then create EXACTLY ONE database record for each using 'chatdb_add' with:
   - entity_name: "file_topic"
   - data: {
       "file_name": "${file.name}",
       "topic": "<PUT_TOPIC>",
       "document_id": <ID of the just-uploaded document in THIS message's attachments>,
       "upload_status": "success"
     }
3) If all or some of the documents have no text: create EXACTLY ONE database record for the ones with no text using 'chatdb_add' with:
   - entity_name: "file_topic"
   - data: {
       "file_name": "${file.name}",
       "topic": "Pending",
       "document_id": <ID of the just-uploaded document in THIS message's attachments>,
       "upload_status": "waiting"
     }
4) You MUST call 'chatdb_add' exactly once per uploaded file.
5) Respond with the JUST JSON, with the format:

[
  {
    "document_id": <document_id>,
    "document_filename": ""<filename>",
    "document_topic": "<topic>"
    "extraction_status": "<success|error>
  }
]`;

        return pt.uploadFiles(formData, msg);
      }).filter(Boolean);

      if(jobs.length === 0){
        setNotice(`No valid files to upload. Skipped: ${skipped}.`, 'warn');
        return;
      }

      setUploading(true);
      setNotice(`Uploading ${jobs.length} file(s) in parallel...`, 'warn');

      const results = await Promise.allSettled(jobs);
      const ok = results.filter(r => r.status === 'fulfilled').length;
      const fail = results.filter(r => r.status === 'rejected').length;

      setUploading(false);
      setNotice(`Upload finished. Success: ${ok}, Error: ${fail}. Skipped: ${skipped}.`, fail ? 'error' : 'success');

      // clear input and refresh table once
      fileInput.value = '';
      await loadRows();
    }

    // Expose a global check handler for waiting rows
    window.__checkWaiting = async function(e){
      const btn = e.currentTarget;
      const entityId = parseInt(btn.getAttribute('data-entity-id'));
      const docId = parseInt(btn.getAttribute('data-document-id'));
      const fileName = btn.getAttribute('data-file-name') || '';

      try{
        btn.disabled = true; btn.classList.add('opacity-60','cursor-not-allowed');
        const msg = `Re-check uploaded document readiness and update the existing DB row.

Document info:
- document_id: ${docId}
- file_name: "${fileName}"
- entity_id: ${entityId}

Instructions:
1) try to read the document text, if the document has extracted text:
   - Derive a concise topic (<= 6 words).
   - Use 'chatdb_edit' with entity_id and set data to:
     {
       "file_name": "${fileName}",
       "topic": "<PUT_TOPIC>",
       "document_id": ${docId},
       "upload_status": "success"
     }
2) If still not Ready or no text: make no DB changes.
3) Respond with the JUST JSON, with the format:

[
  {
    "document_id": <document_id>,
    "document_filename": ""<filename>",
    "document_topic": "<topic>"
    "extraction_status": "<success|error>
  }
]`;
        await pt.addMessage(msg);
        setNotice('Check requested. Give it a moment, then Refresh.', 'success');
        await loadRows();
      }catch(err){
        setNotice('Check failed. Please try again.', 'error');
      }finally{
        btn.disabled = false; btn.classList.remove('opacity-60','cursor-not-allowed');
      }
    }

    // Viewer helpers
    window.__openViewer = async function(docId){
      if(!docId){ return; }
      try{
        const res = await pt.getDocumentText(docId, { to: 2000 });
        const txt = (res && res.text) ? res.text : (res && res.message) ? res.message : 'No text available.';
        viewerText.textContent = txt;
        viewerModal.classList.remove('hidden');
        viewerModal.classList.add('flex');
      }catch(err){
        viewerText.textContent = 'Failed to load document text.';
        viewerModal.classList.remove('hidden');
        viewerModal.classList.add('flex');
      }
    }

    function closeModal(){
      viewerModal.classList.add('hidden');
      viewerModal.classList.remove('flex');
      viewerText.textContent = '';
    }

    closeViewer.addEventListener('click', closeModal);
    viewerModal.addEventListener('click', (e)=>{ if(e.target === viewerModal) closeModal(); });

    // Bindings
    refreshBtn.addEventListener('click', loadRows);
    uploadBtn.addEventListener('click', uploadFiles);

    // Initial load + auto refresh
    (async function init(){
      await loadRows();
      if(autoTimer) clearInterval(autoTimer);
      autoTimer = setInterval(loadRows, 10000);
    })();
  })();
  </script>
</div>
```

### Key Features Explained

Parallel File Upload:

* Uploads multiple files concurrently using `Promise.allSettled()`

* Significantly faster when uploading multiple files

* Each file is processed independently by the AI

* Provides aggregate success/failure counts

* Recommended for better user experience

JSON Response Format:

* AI returns structured JSON with extraction results

* Easy to parse and validate

* Includes document_id, filename, topic, and status

* Better for programmatic handling of responses

Status Management:

* `success` - Document processed and topic extracted

* `waiting` - Document still being processed

* `pending` - Initial state

* `error` - Processing failed

Check Later Functionality:

* "Check" button appears for waiting documents

* Sends AI command to re-check document readiness

* Updates database record when document becomes ready

* User can manually trigger checks anytime

Auto-Refresh:

* Table refreshes every 10 seconds

* Shows latest processing status automatically

* No manual refresh needed for most operations

### Parallel vs Serial Upload Processing

The example above uses parallel upload (concurrent processing) for better performance. However, you may want serial upload (one at a time) in certain scenarios.

When to Use Parallel Upload (Recommended):

* Default choice for most use cases

* Faster overall completion time

* Better user experience

* Files are independent of each other

* You want to maximize throughput

When to Use Serial Upload:

* You need to process files in specific order

* You want to limit concurrent AI requests

* Each file depends on previous file's results

* You need more predictable resource usage

Serial Upload Implementation:

Replace the `uploadFiles` function with this serial version:

```JAVASCRIPT
// Serial upload: process one file at a time
async function uploadFiles(){
  const files = Array.from(fileInput.files || []);
  if(files.length === 0){ setNotice('Please select one or more files.', 'warn'); return; }

  const allowed = ['pdf','docx'];
  let ok = 0, fail = 0, skipped = 0;

  setUploading(true);
  setNotice(`Uploading ${files.length} file(s) one at a time...`, 'warn');

  // Process files sequentially
  for(const file of files){
    const ext = (file.name.split('.').pop() || '').toLowerCase();
    if(!allowed.includes(ext) || file.size > MAX_SIZE){
      skipped++;
      continue;
    }

    try{
      const formData = new FormData();
      formData.append('files', file);

      const msg = `AI Processing Request

You are given a file uploaded from a PrimeThink Live Page as an attachment to THIS message. Follow EXACTLY:

1) If the documents attached have text: derive a concise topic (<= 6 words, noun phrase) for each one. Then create EXACTLY ONE database record for each using 'chatdb_add' with:
   - entity_name: "file_topic"
   - data: {
       "file_name": "${file.name}",
       "topic": "<PUT_TOPIC>",
       "document_id": <ID of the just-uploaded document in THIS message's attachments>,
       "upload_status": "success"
     }
3) If all or some of the documents have no text: create EXACTLY ONE database record for the ones with no text using 'chatdb_add' with:
   - entity_name: "file_topic"
   - data: {
       "file_name": "${file.name}",
       "topic": "Pending",
       "document_id": <ID of the just-uploaded document in THIS message's attachments>,
       "upload_status": "waiting"
     }
4) You MUST call 'chatdb_add' exactly once per uploaded file.
5) Respond with the JUST JSON, with the format:

[
  {
    "document_id": <document_id>,
    "document_filename": ""<filename>",
    "document_topic": "<topic>"
    "extraction_status": "<success|error>
  }
]`;

      await pt.uploadFiles(formData, msg);
      ok++;

      // Update status after each file
      setNotice(`Uploaded ${ok} of ${files.length - skipped} file(s)...`, 'warn');
    }catch(e){
      fail++;
    }
  }

  setUploading(false);
  setNotice(`Upload finished. Success: ${ok}, Error: ${fail}. Skipped: ${skipped}.`, fail ? 'error' : 'success');

  fileInput.value = '';
  await loadRows();
}
```

Key Differences:

| Aspect |Parallel (Recommended) |Serial |
------------------------------------------
| Speed |Fast - all files at once |Slower - one at a time |
| Syntax |`Promise.allSettled()` |`for...of` loop |
| Order |No guarantee |Strict order |
| Progress |Bulk complete |Per-file updates |
| Resource Usage |Higher concurrent load |Lower concurrent load |

## Alternative Pattern: Immediate Feedback with Processing Status

This alternative pattern provides immediate visual feedback by creating database rows with `PROCESSING` status before AI processes the files. The AI then updates these rows via `chatdb_edit` once processing completes.

### Pattern Overview

How it works:

1. User selects files

2. App immediately creates database row per file with `upload_status: 'PROCESSING'`

3. File is uploaded to chat with AI instructions

4. AI reads the uploaded file and uses `chatdb_edit` to update the existing row

5. If text is ready: AI updates to `SUCCESS` with topic and document_id

6. If text isn't ready: Row remains `PROCESSING` (user can refresh later)

Key Advantage: Users see files appearing in the table immediately with `PROCESSING` status instead of waiting with no feedback.

### Complete Implementation

```HTML
<div class="container mx-auto p-6 max-w-4xl">
  <!-- Header -->
  <div class="flex items-center justify-between mb-6">
    <div class="flex items-center gap-3 text-gray-700">
      <h1 class="text-xl md:text-2xl font-bold">Topic extractor</h1>
    </div>
    <button class="px-3 py-2 rounded bg-gray-100 hover:bg-gray-200 text-sm" id="refreshBtn" type="button">Refresh</button>
  </div>

  <!-- Uploader -->
  <div class="bg-white rounded-xl shadow p-4">
    <p class="text-gray-600 text-sm mb-3">
      When you upload files, the app immediately creates a database row per file with status <code>PROCESSING</code>. The AI will then read the uploaded file and update the same row via <code>chatdb_edit</code> to set the topic, attach the <code>document_id</code>, and switch status to <code>SUCCESS</code> once text is ready. If the text isn't ready yet, the row remains <code>PROCESSING</code>. The table auto-refreshes every 10s.
    </p>

    <div class="flex flex-col md:flex-row md:items-center gap-3">
      <input accept=".pdf,.docx" class="block w-full text-sm text-gray-700" id="fileInput" multiple type="file" />
      <button class="px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 flex items-center gap-2" id="uploadBtn" type="button">
        <svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1M12 12V4m0 0l-3 3m3-3l3 3" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path></svg>
        Upload
      </button>
    </div>
    <div class="hidden mt-3 rounded p-3 text-sm" id="uploadNotice"></div>
  </div>

  <!-- Table -->
  <div class="mt-6 bg-white rounded-xl shadow p-4">
    <div class="flex items-center justify-between mb-3">
      <div class="text-sm text-gray-500">Auto-refreshes every 10s</div>
    </div>

    <div class="overflow-x-auto">
      <table class="min-w-full divide-y divide-gray-200">
        <thead class="bg-gray-50">
          <tr>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">File Name</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Topic</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Uploaded At</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Upload Status</th>
            <th class="px-4 py-2 text-left text-xs font-semibold text-gray-600">Actions</th>
          </tr>
        </thead>
        <tbody class="divide-y divide-gray-100 bg-white" id="rows"></tbody>
      </table>
    </div>

    <div class="hidden text-center text-gray-500 text-sm py-8" id="emptyState">No uploads yet</div>
  </div>

  <!-- Viewer Modal -->
  <div class="hidden fixed inset-0 bg-black/50 z-50 items-center justify-center" id="viewerModal">
    <div class="bg-white rounded-lg shadow-xl max-w-3xl w-full mx-4">
      <div class="flex items-center justify-between p-4 border-b">
        <h3 class="font-semibold text-gray-800">Document Preview</h3>
        <button class="text-gray-500 hover:text-gray-700" id="closeViewer" type="button">✕</button>
      </div>
      <div class="p-4 max-h-[60vh] overflow-auto">
        <pre class="whitespace-pre-wrap text-sm text-gray-800" id="viewerText"></pre>
      </div>
    </div>
  </div>

  <script>
    (function(){
      // Elements
      const rowsEl = document.getElementById('rows');
      const emptyEl = document.getElementById('emptyState');
      const refreshBtn = document.getElementById('refreshBtn');
      const uploadBtn = document.getElementById('uploadBtn');
      const fileInput = document.getElementById('fileInput');
      const noticeEl = document.getElementById('uploadNotice');
      const viewerModal = document.getElementById('viewerModal');
      const closeViewer = document.getElementById('closeViewer');
      const viewerText = document.getElementById('viewerText');

      const MAX_SIZE = 1024 * 1024; // 1MB
      let autoTimer = null;

      function setNotice(text, type){
        if(!text){
          noticeEl.classList.add('hidden');
          noticeEl.textContent = '';
          return;
        }
        const base = 'mt-3 rounded p-3 text-sm ';
        if(type==='success') noticeEl.className = base + 'bg-green-50 border border-green-200 text-green-800';
        else if(type==='warn') noticeEl.className = base + 'bg-yellow-50 border border-yellow-200 text-yellow-800';
        else noticeEl.className = base + 'bg-red-50 border border-red-200 text-red-800';
        noticeEl.textContent = text; noticeEl.classList.remove('hidden');
      }

      function badge(status){
        const norm = String(status || 'PROCESSING').toUpperCase();
        const map = {
          'SUCCESS': 'bg-green-100 text-green-800',
          'PROCESSING': 'bg-yellow-100 text-yellow-800',
          'ERROR': 'bg-red-100 text-red-800'
        };
        const cls = map[norm] || 'bg-gray-100 text-gray-800';
        return `<span class="px-2 py-1 text-xs rounded ${cls}">${norm}</span>`;
      }

      function fmtDate(s){ try { return new Date(s).toLocaleString(); } catch(e){ return s || ''; } }

      function esc(str){
        const d = document.createElement('div'); d.textContent = String(str ?? '');
        return d.innerHTML;
      }

      function rowHTML(item){
        const d = item.data || {};
        const file = d.file_name || '';
        const topic = (d.topic === null || d.topic === undefined || d.topic === '') ? 'Pending' : d.topic;
        const status = d.upload_status || 'PROCESSING';
        const canView = !!d.document_id;
        const viewBtnCls = 'px-3 py-1.5 rounded ' + (canView ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-gray-200 text-gray-500 cursor-not-allowed');
        const viewDisabled = canView ? '' : 'disabled';
        const docId = d.document_id ? String(d.document_id) : '';

        return `
          <tr>
            <td class="px-4 py-2 text-sm text-gray-800">${esc(file)}</td>
            <td class="px-4 py-2 text-sm text-gray-700">${esc(topic)}</td>
            <td class="px-4 py-2 text-sm text-gray-500">${fmtDate(item.created_at)}</td>
            <td class="px-4 py-2 text-sm">${badge(status)}</td>
            <td class="px-4 py-2 text-sm flex items-center gap-2">
              <button class="${viewBtnCls}" ${viewDisabled} onclick="window.__openViewer(${docId ? docId : 'null'})" type="button">View</button>
            </td>
          </tr>
        `;
      }

      async function loadRows(){
        try{
          const entities = await pt.list({ entityNames: ['file_topic'], limit: 200 });
          const items = (entities || []).filter(e => e.entity_name === 'file_topic');
          if(items.length === 0){
            rowsEl.innerHTML = '';
            emptyEl.classList.remove('hidden');
            return;
          }
          emptyEl.classList.add('hidden');
          rowsEl.innerHTML = items.map(rowHTML).join('');
        }catch(err){
          setNotice('Failed to load rows. Please try Refresh.', 'error');
        }
      }

      function setUploading(on){
        uploadBtn.disabled = !!on;
        if(on){ uploadBtn.classList.add('opacity-60','cursor-not-allowed'); }
        else { uploadBtn.classList.remove('opacity-60','cursor-not-allowed'); }
      }

      // NEW: create PROCESSING row first, then upload & let LLM chatdb_edit it to SUCCESS
      async function uploadFiles(){
        const files = Array.from(fileInput.files || []);
        if(files.length === 0){ setNotice('Please select one or more files.', 'warn'); return; }

        const allowed = ['pdf','docx'];
        let skipped = 0;

        // Build parallel jobs for valid files
        const jobs = files.map(file => {
          const ext = (file.name.split('.').pop() || '').toLowerCase();
          if(!allowed.includes(ext) || file.size > MAX_SIZE){
            skipped++;
            return null; // skipped
          }

          return (async () => {
            // 1) Create PROCESSING row immediately
            const created = await pt.add('file_topic', {
              file_name: file.name,
              topic: null,
              document_id: null,
              upload_status: 'PROCESSING'
            });
            const entityId = created.id;

            // 2) Upload file with instructions to EDIT that row via chatdb_edit
            const formData = new FormData();
            formData.append('files', file);

            const msg = `AI Processing Request (Edit Existing Row)

A file has been uploaded from a PrimeThink Live Page as an attachment to THIS message. A database row has ALREADY been created for this file.

You MUST follow EXACTLY:

1) If the uploaded document has extracted text:
   - Derive a concise topic (<= 6 words, noun phrase).
   - Call the tool 'chatdb_edit' EXACTLY ONCE with:
     - entity_id: ${entityId}
     - data: {
         "file_name": "${file.name.replace(/"/g, '\\"')}",
         "topic": "<PUT_TOPIC>",
         "document_id": <ID of the just-uploaded document in THIS message's attachments>,
         "upload_status": "SUCCESS"
       }

3) If the document has NO text yet or cannot be extracted in time:
   - Do NOT create new rows.
   - Do NOT change the existing row. Leave it as "PROCESSING".

4) You MUST NOT call 'chatdb_add'. Only use 'chatdb_edit' with entity_id ${entityId}.

5) Respond with JUST JSON, in the format:
[
  {
    "document_id": <document_id>,
    "document_filename": "${file.name.replace(/"/g, '\\"')}",
    "document_topic": "<topic>",
    "extraction_status": "<success|error>"
  }
]`;

            await pt.uploadFiles(formData, msg);
          })();
        }).filter(Boolean);

        if(jobs.length === 0){
          setNotice(`No valid files to upload. Allowed: .pdf, .docx; Max size 1MB. Skipped: ${skipped}.`, 'warn');
          return;
        }

        setUploading(true);
        setNotice(`Uploading file(s) and initializing PROCESSING rows...`, 'warn');

        const results = await Promise.allSettled(jobs);
        const ok = results.filter(r => r.status === 'fulfilled').length;
        const fail = results.filter(r => r.status === 'rejected').length;

        setUploading(false);
        setNotice(`Upload initialized. Success: ${ok}, Error: ${fail}, Skipped: ${skipped}.`, fail ? 'error' : 'success');

        // clear input and refresh once
        fileInput.value = '';
        await loadRows();
      }

      // Viewer helpers
      window.__openViewer = async function(docId){
        if(!docId){ return; }
        try{
          const res = await pt.getDocumentText(docId, { to: 2000 });
          const txt = (res && res.text) ? res.text : (res && res.message) ? res.message : 'No text available.';
          viewerText.textContent = txt;
          viewerModal.classList.remove('hidden');
          viewerModal.classList.add('flex');
        }catch(err){
          viewerText.textContent = 'Failed to load document text.';
          viewerModal.classList.remove('hidden');
          viewerModal.classList.add('flex');
        }
      }

      function closeModal(){
        viewerModal.classList.add('hidden');
        viewerModal.classList.remove('flex');
        viewerText.textContent = '';
      }

      closeViewer.addEventListener('click', closeModal);
      viewerModal.addEventListener('click', (e)=>{ if(e.target === viewerModal) closeModal(); });

      // Clear notice when selecting files
      fileInput.addEventListener('change', () => setNotice('', ''));

      // Bindings
      refreshBtn.addEventListener('click', loadRows);
      uploadBtn.addEventListener('click', uploadFiles);

      // Initial load + auto refresh
      (async function init(){
        await loadRows();
        if(autoTimer) clearInterval(autoTimer);
        autoTimer = setInterval(loadRows, 10000);
      })();
    })();
  </script>
</div>
```

### Pattern Comparison: Add-Then-Update vs Upload-Then-Add

| Aspect |Upload-Then-Add (Previous) |Add-Then-Update (This Pattern) |
----------------------------------------------------------------------
| User Feedback |Delayed - waits for AI |Immediate - shows PROCESSING |
| Database Operations |1 operation (chatdb_add) |2 operations (pt.add + chatdb_edit) |
| Orphaned Rows |None |Possible if upload fails after pt.add |
| Complexity |Simpler |More complex |
| UX Quality |Good |Excellent |
| Error Handling |Easier |Needs cleanup logic |
| Best For |Background automation |Interactive user interfaces |

### When to Use Each Pattern

Use Upload-Then-Add (chatdb_add only):

* Goals and automation (chat uploads, email)

* Background processing where immediate feedback isn't needed

* Simpler implementation with fewer failure modes

* When you want to avoid orphaned database rows

Use Add-Then-Update (pt.add + chatdb_edit):

* Interactive Live Page interfaces

* When immediate visual feedback is critical

* Dashboard-style applications

* When users need to see upload progress in real-time

* Applications where perceived performance matters

### Key Implementation Details

1. Creating the PROCESSING Row:

```JAVASCRIPT
const created = await pt.add('file_topic', {
  file_name: file.name,
  topic: null,
  document_id: null,
  upload_status: 'PROCESSING'
});
const entityId = created.id; // Save this to pass to AI
```

2. AI Instructions for Editing:

```JAVASCRIPT
const msg = `AI Processing Request (Edit Existing Row)

A database row has ALREADY been created for this file.

1) If the uploaded document has extracted text:
   - Use 'chatdb_edit' with entity_id: ${entityId}
   - Set upload_status: "SUCCESS"

3) If the document has NO text:
   - Leave the row as "PROCESSING"

4) You MUST NOT call 'chatdb_add'. Only use 'chatdb_edit'.`;
```

3. Status Badge Styling:

```JAVASCRIPT
function badge(status){
  const map = {
    'SUCCESS': 'bg-green-100 text-green-800',
    'PROCESSING': 'bg-yellow-100 text-yellow-800',
    'ERROR': 'bg-red-100 text-red-800'
  };
  // ...
}
```

### Best Practices for This Pattern

1. Prevent Orphaned Rows:

```JAVASCRIPT
try {
  const created = await pt.add('file_topic', { /* ... */ });
  await pt.uploadFiles(formData, msg);
} catch (error) {
  // If upload fails, consider deleting the row
  await pt.delete(created.id);
  throw error;
}
```

2. Add Retry Mechanism:

* Add "Re-process" button for PROCESSING rows that stay stuck

* Allow users to manually trigger chatdb_edit attempts

3. Set Reasonable Timeouts:

* Auto-refresh every 10 seconds is reasonable

* Consider showing "stuck" indicator for rows PROCESSING > 2 minutes

4. Clear Status Indicators:

* Use distinct colors: yellow for PROCESSING, green for SUCCESS

* Add icons or spinners for better visual feedback

### Important Note About Goals

Goals should always use `chatdb_add`, not this add-then-update pattern, because:

* Goals are for automation (chat uploads, email forwarding)

* No user is waiting for immediate visual feedback

* Simpler implementation with fewer failure modes

* Avoids orphaned PROCESSING rows when automation fails

The add-then-update pattern is specifically for interactive Live Page uploads where immediate user feedback is valuable.

### Using Goals for Direct File Upload

For an even better user experience, you can set a Goal in the chat settings so that when users upload files through any channel, they are automatically processed without needing the Live Page interface.

File Upload Channels:

* Direct chat messages: Users upload files in the conversation

* Email forwarding: Files sent to the chat's email address

* API uploads: Files uploaded programmatically via the PrimeThink API

* Chat mentions: Files uploaded when the chat is mentioned in another conversation

When to Use Goals:

* Users upload files through multiple channels (chat, email, API, chat mentions)

* You want automatic processing without user interaction

* You want consistent processing across all upload methods

* You need unified experience regardless of upload source

Setting Up the Goal:

Navigate to your chat settings and add this goal:

```
If a user just uploads a pdf file, execute the following prompt:

AI Processing Request

You are given a file uploaded from a PrimeThink Live Page as an attachment to THIS message. Follow EXACTLY:

1) If the documents attached have text: derive a concise topic (<= 6 words, noun phrase) for each one. Then create EXACTLY ONE database record for each using 'chatdb_add' with:
   - entity_name: "file_topic"
   - data: {
       "file_name": "<actual filename>",
       "topic": "<PUT_TOPIC>",
       "document_id": <ID of the just-uploaded document in THIS message's attachments>,
       "upload_status": "success"
     }

3) If all or some of the documents have no text: create EXACTLY ONE database record for the ones with no text using 'chatdb_add' with:
   - entity_name: "file_topic"
   - data: {
       "file_name": "<actual filename>",
       "topic": "Pending",
       "document_id": <ID of the just-uploaded document in THIS message's attachments>,
       "upload_status": "waiting"
     }

4) You MUST call 'chatdb_add' exactly once per uploaded file.
5) Respond with the JUST JSON, with the format:

[
  {
    "document_id": <document_id>,
    "document_filename": ""<filename>",
    "document_topic": "<topic>"
    "extraction_status": "<success|error>
  }
]
```

How Goals Work:

* Goals are instructions that the AI automatically follows when certain conditions are met

* When a file is uploaded through any channel (chat, email, API, chat mentions), the goal triggers

* The AI processes the file and stores results in the database using `chatdb_add`

* The Live Page will show the results on next refresh

* Users get a unified experience regardless of upload source

Goal Benefits:

* Unified Processing: Same logic for chat, email, API, and chat mention uploads

* No UI Required: Files can be processed without visiting the Live Page

* Automatic Execution: Background processing with zero user intervention

* Consistent Data: All uploads create identical database structures

* Multi-Channel Support: Works seamlessly across all upload methods

### Real-World Applications

1. Multi-Channel Document Inbox: Automatically categorize documents from chat, email, API, and chat mentions

2. Receipt Processing: Extract data from receipts sent via email or uploaded through API

3. Contract Management: Process contracts uploaded via any channel and extract key terms

4. Research Library: Organize academic papers by topic, accepting uploads from multiple sources

5. Customer Support: Categorize support documents from email, chat mentions, or API integrations

6. Invoice Processing: Extract invoice details from PDFs uploaded via email, API, or chat

7. Legal Documents: Categorize and index legal filings from any upload source

8. API-Driven Workflows: Automated document processing from third-party systems via API

9. Mention-Based Analysis: Process documents shared when the chat is mentioned in other conversations

### Best Practices

Upload Strategy:

* Use parallel upload as default for best performance

* Switch to serial if you need ordered processing

* Validate file types and sizes before uploading

* Provide immediate feedback during upload

* Handle cases where documents don't have text yet

Error Handling:

* Always create a database record, even if processing fails

* Use status field to track processing state

* Provide clear feedback to users

* Allow retry mechanisms

Performance:

* Use parallel upload (`Promise.allSettled()`) for faster processing

* Use serial upload when order matters or to limit concurrent requests

* Use appropriate file size limits (1MB recommended)

* Implement auto-refresh for updated statuses

* Cache document text when possible

* Consider AI response format (JSON is easier to parse)

User Experience:

* Show immediate feedback when uploading

* Display clear status indicators

* Provide document preview functionality

* Allow manual re-check for waiting documents

## Next Steps

* [Data Management API Reference](data-management-api.html) - Learn about all pt API methods

* [Filtering and Querying](filtering-and-querying.html) - Advanced filtering techniques

* [Pagination](pagination.html) - Handle large datasets

* [Styling with Tailwind CSS](styling-with-tailwind.html) - Improve your UI

* [Performance and Best Practices](live-pages-best-practices.html) - Optimize your applications and learn about using Goals for automation



# Performance and Best Practices

## Overview

This guide covers best practices for building efficient, maintainable Live Pages applications.

## Performance Optimization

### 1. Use pt.get() for Single Entities

When you know the entity ID, always use `pt.get()` for the fastest retrieval:

```JAVASCRIPT
// ✅ GOOD: Fast primary key lookup
const task = await pt.get(123);

// ❌ AVOID: Slower filtering when ID is known
const tasks = await pt.list({
    entityNames: ['task'],
    filters: { id: 123 }
});
const task = tasks[0];
```

### 2. Implement Server-Side Filtering

Always filter on the server rather than loading all data and filtering client-side:

```JAVASCRIPT
// ✅ GOOD: Server-side filtering
const activeTasks = await pt.list({
    entityNames: ['task'],
    filters: { status: 'active' },
    limit: 50
});

// ❌ AVOID: Client-side filtering of large datasets
const allTasks = await pt.list({
    entityNames: ['task'],
    limit: 10000
});
const activeTasks = allTasks.filter(t => t.data.status === 'active');
```

### 3. Use Appropriate Operators

Choose the most efficient operator for your use case:

```JAVASCRIPT
// ✅ GOOD: Use exact match when possible (fastest)
const task = await pt.list({
    entityNames: ['task'],
    filters: { status: 'active' }
});

// ✅ GOOD: Use $in for multiple values
const tasks = await pt.list({
    entityNames: ['task'],
    filters: { priority: { $in: ['high', 'medium'] } }
});

// ❌ AVOID: Unnecessary $or for same field
const tasks = await pt.list({
    entityNames: ['task'],
    filters: {
        $or: [
            { priority: 'high' },
            { priority: 'medium' }
        ]
    }
});
```

### 4. Cache Static Data

Cache data that doesn't change frequently:

```JAVASCRIPT
// ✅ GOOD: Cache chat members at app initialization
let allMembers = [];

async function initApp() {
    allMembers = await pt.getChatMembers();
    await loadTasks();
}

function getMemberName(userId) {
    const member = allMembers.find(m => m.id === userId);
    return member ? member.name : 'Unknown';
}

// ❌ AVOID: Calling getChatMembers() repeatedly
async function displayTask(task) {
    const members = await pt.getChatMembers(); // Called for every task!
    const creator = members.find(m => m.id === task.creator_user_id);
    return creator.name;
}
```

### 5. Use Pagination

For large datasets, always implement pagination:

```JAVASCRIPT
// ✅ GOOD: Load data in pages
const result = await pt.list({
    entityNames: ['task'],
    filters: { status: 'active' },
    page: 1,
    pageSize: 20,
    returnMetadata: true
});

// ❌ AVOID: Loading thousands of records at once
const allTasks = await pt.list({
    entityNames: ['task'],
    limit: 10000
});
```

### 6. Batch Operations

Use `Promise.all()` for parallel operations:

```JAVASCRIPT
// ✅ GOOD: Parallel operations
async function batchUpdate(taskIds, updates) {
    const promises = taskIds.map(async id => {
        const task = await pt.get(id);
        return pt.edit(id, { ...task.data, ...updates });
    });

    await Promise.all(promises);
}

// ❌ AVOID: Sequential operations
async function slowBatchUpdate(taskIds, updates) {
    for (const id of taskIds) {
        const task = await pt.get(id);
        await pt.edit(id, { ...task.data, ...updates });
    }
}
```

### 7. Provide Immediate Feedback with Processing Status

For file uploads or long-running operations, create database rows immediately with a `PROCESSING` status to provide instant user feedback, then update them when processing completes.

#### Pattern: Add-Then-Update

```JAVASCRIPT
// ✅ GOOD: Create PROCESSING row immediately, update when done
async function uploadFileWithFeedback(file) {
    // 1. Create row immediately - user sees it right away
    const created = await pt.add('document', {
        filename: file.name,
        status: 'PROCESSING',
        topic: null,
        document_id: null
    });

    try {
        // 2. Upload and process
        const formData = new FormData();
        formData.append('files', file);

        const msg = `Process this file and use 'chatdb_edit' with entity_id: ${created.id} to update the row with results.`;
        await pt.uploadFiles(formData, msg);

        // User will see PROCESSING status immediately, then SUCCESS after AI updates
    } catch (error) {
        // 3. Update to ERROR if something fails
        await pt.edit(created.id, {
            filename: file.name,
            status: 'ERROR',
            error_message: error.message
        });
    }
}

// ❌ AVOID: User waits with no feedback
async function uploadFileNoFeedback(file) {
    const formData = new FormData();
    formData.append('files', file);

    // User sees nothing until AI finishes processing
    const msg = `Process this file and use 'chatdb_add' to create a row.`;
    await pt.uploadFiles(formData, msg);
}
```

#### When to Use Each Approach

Upload-Then-Add (chatdb_add):

* Goals and automation (chat, email)

* Background tasks

* No user waiting for feedback

* Simpler with fewer failure modes

Add-Then-Update (pt.add + chatdb_edit):

* Interactive Live Page uploads

* User is actively waiting

* Immediate feedback is critical

* Dashboard/real-time applications

Comparison:

| Aspect |Upload-Then-Add |Add-Then-Update |
--------------------------------------------
| User Feedback |Delayed |Immediate |
| Operations |1 (add only) |2 (add + edit) |
| Complexity |Simple |More complex |
| Orphaned Rows |None |Possible |
| Best For |Automation |Interactive UIs |

#### Best Practices for Add-Then-Update

1. Handle Cleanup on Failure:

```JAVASCRIPT
try {
    const created = await pt.add('document', { status: 'PROCESSING' });
    await processDocument(created.id);
} catch (error) {
    // Option 1: Update to ERROR status
    await pt.edit(created.id, { status: 'ERROR', error: error.message });

    // Option 2: Delete orphaned row
    // await pt.delete(created.id);
}
```

2. Use Clear Status Values:

```JAVASCRIPT
const STATUS = {
    PROCESSING: 'PROCESSING',  // Yellow badge, spinner
    SUCCESS: 'SUCCESS',        // Green badge, checkmark
    ERROR: 'ERROR'             // Red badge, x mark
};
```

3. Add Auto-Refresh:

```JAVASCRIPT
// Refresh table every 10 seconds to show updated statuses
setInterval(loadData, 10000);
```

4. Show Processing Indicators:

```JAVASCRIPT
function renderStatus(status) {
    if (status === 'PROCESSING') {
        return `<span class="text-yellow-600">⏳ Processing...</span>`;
    }
    if (status === 'SUCCESS') {
        return `<span class="text-green-600">✓ Complete</span>`;
    }
    return `<span class="text-red-600">✗ Error</span>`;
}
```

### 8. Implement Debouncing

Debounce search inputs to reduce API calls:

```JAVASCRIPT
let searchTimeout;

document.getElementById('searchInput').addEventListener('input', (e) => {
    clearTimeout(searchTimeout);
    const query = e.target.value.trim();

    if (query.length < 2) {
        clearResults();
        return;
    }

    searchTimeout = setTimeout(async () => {
        const results = await pt.list({
            entityNames: ['task'],
            filters: { text: { $contains: query } },
            limit: 20
        });
        displayResults(results);
    }, 300); // Wait 300ms after user stops typing
});
```

### 9. Cache Page Results

Implement caching for pagination:

```JAVASCRIPT
const pageCache = new Map();
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes

async function loadPageWithCache(page, filters) {
    const cacheKey = `${page}-${JSON.stringify(filters)}`;
    const cached = pageCache.get(cacheKey);

    if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
        return cached.data;
    }

    const result = await pt.list({
        entityNames: ['task'],
        filters: filters,
        page: page,
        pageSize: 20,
        returnMetadata: true
    });

    pageCache.set(cacheKey, {
        data: result,
        timestamp: Date.now()
    });

    return result;
}
```

## Error Handling

### 1. Always Handle Errors

Wrap data operations in try-catch blocks:

```JAVASCRIPT
async function robustOperation() {
    try {
        const entity = await pt.get(123);
        return entity;
    } catch (error) {
        console.error('Operation failed:', error);
        return null;
    }
}
```

### 2. Provide User Feedback

Show meaningful error messages to users:

```JAVASCRIPT
async function addTask() {
    const text = document.getElementById('taskInput').value.trim();

    if (!text) {
        showError('Please enter a task description');
        return;
    }

    try {
        await pt.add('task', {
            text: text,
            completed: false
        });

        showSuccess('Task added successfully');
        await loadTasks();
    } catch (error) {
        console.error('Error adding task:', error);
        showError('Failed to add task. Please try again.');
    }
}

function showError(message) {
    const alert = document.createElement('div');
    alert.className = 'bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4';
    alert.textContent = message;
    document.getElementById('alerts').appendChild(alert);

    setTimeout(() => alert.remove(), 5000);
}

function showSuccess(message) {
    const alert = document.createElement('div');
    alert.className = 'bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4';
    alert.textContent = message;
    document.getElementById('alerts').appendChild(alert);

    setTimeout(() => alert.remove(), 3000);
}
```

### 3. Implement Fallback Strategies

Provide fallbacks when operations fail:

```JAVASCRIPT
async function robustDataOperation() {
    try {
        const result = await pt.list({
            entityNames: ['task'],
            filters: { text: { $contains: 'important' } },
            limit: 50
        });

        return result;
    } catch (error) {
        console.error('Primary operation failed:', error);

        // Fallback to simpler query
        try {
            return await pt.list({
                entityNames: ['task'],
                limit: 20
            });
        } catch (fallbackError) {
            console.error('Fallback also failed:', fallbackError);
            return [];
        }
    }
}
```

## Code Organization

### 1. Separate Concerns

Organize code into logical functions:

```JAVASCRIPT
// ✅ GOOD: Separate concerns
async function loadTasks() {
    const entities = await fetchTasks();
    const tasks = filterTaskEntities(entities);
    displayTasks(tasks);
}

async function fetchTasks() {
    return await pt.list({
        entityNames: ['task'],
        filters: { completed: false }
    });
}

function filterTaskEntities(entities) {
    return entities.filter(e => e.entity_name === 'task');
}

function displayTasks(tasks) {
    document.getElementById('tasksList').innerHTML = tasks.map(renderTask).join('');
}

function renderTask(task) {
    return `
        <div class="task-card">
            <span>${task.data.text}</span>
            <button onclick="deleteTask(${task.id})">Delete</button>
        </div>
    `;
}

// ❌ AVOID: Everything in one function
async function doEverything() {
    const entities = await pt.list({ entityNames: ['task'] });
    const tasks = entities.filter(e => e.entity_name === 'task');
    document.getElementById('tasksList').innerHTML = tasks.map(t =>
        `<div><span>${t.data.text}</span><button onclick="deleteTask(${t.id})">Delete</button></div>`
    ).join('');
}
```

### 2. Use Meaningful Names

```JAVASCRIPT
// ✅ GOOD: Clear variable names
const chatMembers = await pt.getChatMembers();
const humanUsers = chatMembers.filter(m => m.type === 'user');
const aiAgents = chatMembers.filter(m => m.type === 'agent');
const chatOwner = chatMembers.find(m => m.is_owner);

// ❌ AVOID: Unclear names
const m = await pt.getChatMembers();
const u = m.filter(x => x.type === 'user');
```

### 3. Create Reusable Components

```JAVASCRIPT
// Reusable task card renderer
function createTaskCard(task) {
    const card = document.createElement('div');
    card.className = 'bg-white rounded-lg shadow p-4 mb-2';

    const isCompleted = task.data.completed === "true";

    card.innerHTML = `
        <div class="flex items-center justify-between">
            <div class="flex items-center gap-3">
                <input
                    type="checkbox"
                    ${isCompleted ? 'checked' : ''}
                    onchange="toggleTask(${task.id})"
                    class="h-4 w-4"
                >
                <span class="${isCompleted ? 'line-through text-gray-500' : ''}">
                    ${escapeHtml(task.data.text)}
                </span>
            </div>
            <button onclick="deleteTask(${task.id})" class="text-red-500">
                Delete
            </button>
        </div>
    `;

    return card;
}

// Usage
function displayTasks(tasks) {
    const container = document.getElementById('tasksList');
    container.innerHTML = '';
    tasks.forEach(task => {
        container.appendChild(createTaskCard(task));
    });
}
```

## Data Management Best Practices

### 1. Always Merge When Editing

```JAVASCRIPT
// ✅ GOOD: Preserve existing fields
const task = await pt.get(taskId);
await pt.edit(taskId, {
    ...task.data,
    completed: true
});

// ❌ BAD: Lose all other fields
await pt.edit(taskId, { completed: true });
```

### 2. Validate Before Saving

```JAVASCRIPT
async function addTask() {
    const text = document.getElementById('taskInput').value.trim();

    // Validate
    if (!text) {
        showError('Task description is required');
        return;
    }

    if (text.length > 500) {
        showError('Task description is too long (max 500 characters)');
        return;
    }

    // Save
    await pt.add('task', {
        text: text,
        completed: false
    });
}
```

### 3. Handle Missing Members Gracefully

```JAVASCRIPT
function getMemberName(userId) {
    const member = allMembers.find(m => m.id === userId);
    return member ? member.name : 'Unknown User';
}

function displayTaskCreator(task) {
    const creator = allMembers.find(m => m.id === task.creator_user_id);

    if (!creator) {
        return '<span class="text-gray-400">Unknown</span>';
    }

    const icon = creator.type === 'user' ? '👤' : '🤖';
    return `${icon} ${creator.name}`;
}
```

### 4. Use Appropriate Limits

```JAVASCRIPT
// ✅ GOOD: Reasonable limits
const recentTasks = await pt.list({
    entityNames: ['task'],
    limit: 50
});

// ❌ AVOID: Requesting too much data
const allTasks = await pt.list({
    entityNames: ['task'],
    limit: 10000
});
```

## Using Goals with Live Pages

### What are Goals?

Goals are automatic AI instructions that execute when specific conditions are met in a chat. When combined with Live Pages, Goals enable powerful automation workflows where data can be processed and stored in your database without manual intervention.

### How Goals Work with Live Pages

When you set up a Goal in your chat settings, the AI automatically:

1. Detects when the goal condition is triggered (e.g., file upload, specific keywords)

2. Executes the instructions you've defined in the Goal

3. Can use chatdb tools to create/update/query database entities

4. Stores results that your Live Page can display and interact with

This creates a seamless integration between:

* Direct chat interactions (uploading files, sending messages)

* Email forwarding (files sent to chat email address)

* API uploads (files uploaded programmatically via PrimeThink API)

* Chat mentions (files uploaded when the chat is mentioned in other conversations)

* AI processing (extraction, categorization, validation)

* Database storage (structured data in entities)

* Live Page display (visualization and interaction)

### Common Use Cases for Goals with Live Pages

1. Document Processing

```
Goal Trigger: User uploads a PDF file
Goal Action: Extract key information, categorize, store in database
Live Page: Display categorized documents with search/filter
```

2. Email Automation

```
Goal Trigger: Email with attachments forwarded to chat
Goal Action: Extract data, create database records
Live Page: Show processed emails in dashboard
```

3. Data Entry Shortcuts

```
Goal Trigger: User sends message with specific format
Goal Action: Parse message, validate, store as entity
Live Page: Display and manage all entries
```

4. File Analysis

```
Goal Trigger: Invoice/receipt uploaded
Goal Action: Extract line items, amounts, vendors
Live Page: Financial dashboard showing all invoices
```

5. Content Categorization

```
Goal Trigger: Document uploaded
Goal Action: Analyze content, assign categories/tags
Live Page: Browse and filter by categories
```

6. API Integration

```
Goal Trigger: File uploaded via API
Goal Action: Process file, extract metadata, store in database
Live Page: Monitor API uploads with status tracking
```

7. Chat Mention Processing

```
Goal Trigger: Chat mentioned in another conversation with file attachment
Goal Action: Process file in context of mention, create record
Live Page: Show all processed mentions and their results
```

8. Multi-Channel Document Inbox

```
Goal Trigger: File uploaded via any channel (chat, email, API, chat mentions)
Goal Action: Unified processing regardless of source
Live Page: Single dashboard showing all documents from all channels
```

### Best Practices for Writing Goal Prompts

1. Be Explicit About Tool Usage

Always specify which chatdb tool to use:

```
✅ GOOD: Use the tool 'chatdb_add' to create a new invoice record

❌ AVOID: Store the invoice information
```

2. Specify Entity Structure Clearly

Define exactly what fields to create:

```
Use the tool 'chatdb_add' with:
- entity_name: "invoice"
- data: {
    "invoice_number": "<extracted_number>",
    "vendor": "<extracted_vendor>",
    "amount": <extracted_amount>,
    "date": "<extracted_date>",
    "status": "pending"
  }
```

3. Handle Edge Cases

Account for scenarios where data might not be available:

```
If the document has extracted text:
  - Extract information and use 'chatdb_add' with status: "success"

If the document has no text or processing fails:
  - Use 'chatdb_add' with status: "pending" or "error"
```

4. Request Structured Responses

Ask for JSON responses for easier validation:

```
Respond with JUST JSON in this format:
[
  {
    "document_id": <id>,
    "extracted_field": "<value>",
    "status": "<success|error>"
  }
]
```

5. Create One Record Per Item

Be explicit about quantity:

```
✅ GOOD: You MUST call 'chatdb_add' exactly once per uploaded file.

❌ AVOID: Create records for each file.
```

6. Maintain Consistency

Use the same entity names and data structures across goals:

```
Always use:
- entity_name: "invoice" (not "invoices", "invoice_data", etc.)
- data.status: "pending" | "success" | "error" (consistent values)
- data.created_date: ISO format (consistent format)
```

### Goal Example for Live Pages

Here's a complete example of a Goal that works with a Live Page:

Goal Trigger: `If a user uploads a PDF or DOCX file`

Goal Instructions:

```
AI Processing Request

You are given a file uploaded as an attachment to THIS message. Follow EXACTLY:

1) If the document has extracted text:
   - Analyze the content and extract key information
   - Use the tool 'chatdb_add' to create a database record:
     - entity_name: "document"
     - data: {
         "filename": "<actual filename>",
         "category": "<derived category>",
         "summary": "<brief summary, max 200 chars>",
         "document_id": <ID from message attachments>,
         "processing_status": "success"
       }

2) If the document has no text or processing fails:
   - Use the tool 'chatdb_add' to create a database record:
     - entity_name: "document"
     - data: {
         "filename": "<actual filename>",
         "category": "Uncategorized",
         "summary": "Processing pending",
         "document_id": <ID from message attachments>,
         "processing_status": "pending"
       }

3) Respond with JSON:
   {
     "status": "<success|pending|error>",
     "filename": "<filename>",
     "category": "<category>"
   }
```

Corresponding Live Page:

* Displays all documents using `pt.list({ entityNames: ['document'] })`

* Shows category, summary, processing status

* Allows filtering by category or status

* Provides "View" button to see document text with `pt.getDocumentText()`

* Shows "Re-process" button for pending items using `pt.addMessage()`

### Benefits of Goals with Live Pages

Unified Experience:

* Users can upload files via Live Page, chat message, email, API, or chat mentions

* All uploads are processed consistently regardless of source

* All results appear in the same Live Page interface

* Single Goal handles all four upload channels

Automation:

* No manual data entry required

* AI handles extraction and categorization

* Reduces human error

* Zero-touch processing for API and automated uploads

Flexibility:

* Live Page: Visual interface for interactive uploads

* Chat: Conversational interface for manual uploads

* Email: Integration with existing email workflows

* API: Programmatic uploads for system integrations

* Chat Mentions: Context-aware processing when the chat is mentioned in other conversations

Scalability:

* Process single files or batch uploads

* Same Goal handles all sources (chat, email, API, chat mentions)

* Live Page adapts to any data volume

* Supports high-throughput API integrations

### Testing Goals with Live Pages

1. Test with Live Page Upload First:

* Use `pt.uploadFiles()` with instructions

* Verify AI creates correct database entities

* Check that Live Page displays data correctly

2. Test Chat Upload:

* Upload file directly in chat message

* Verify Goal triggers and runs

* Confirm same entity structure is created

3. Test Email Upload:

* Forward email with attachment to chat email address

* Verify Goal triggers automatically

* Confirm consistent entity structure

4. Test API Upload:

* Upload file programmatically via PrimeThink API

* Verify Goal triggers for API uploads

* Confirm API uploads create same entity structure

5. Test Chat Mention Upload:

* Mention the chat in another conversation with file attachment

* Verify Goal triggers when chat is mentioned

* Confirm entity structure matches other channels

6. Test Edge Cases:

* Upload file without text

* Upload unsupported format

* Upload very large file

* Upload multiple files at once

* Test each channel with edge cases

7. Verify Multi-Channel Consistency:

* Compare entities from all four channels (Live Page, chat, email, API, chat mentions)

* Ensure entity_name matches exactly across all sources

* Confirm data structure is identical regardless of upload method

* Verify all uploads appear correctly in Live Page

### Goal + Live Page Checklist

* [ ] Goal uses explicit chatdb tool names

* [ ] Entity structure is clearly defined

* [ ] Edge cases are handled (no text, errors)

* [ ] Response format is specified (JSON recommended)

* [ ] Entity names are consistent with Live Page queries

* [ ] Data field names match what Live Page expects

* [ ] Status values are well-defined and consistent

* [ ] Goal tested with Live Page uploads

* [ ] Goal tested with chat uploads

* [ ] Goal tested with email uploads

* [ ] Goal tested with API uploads

* [ ] Goal tested with chat mention uploads

* [ ] Live Page can display all possible status values

* [ ] Error cases have retry mechanisms

* [ ] All channels create identical entity structures

## Security Best Practices

### 1. Escape User Input

Always escape HTML when displaying user-generated content:

```JAVASCRIPT
function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// Usage
function renderTask(task) {
    return `
        <div class="task-card">
            <span>${escapeHtml(task.data.text)}</span>
        </div>
    `;
}
```

### 2. Validate Input Length

```JAVASCRIPT
function validateTaskInput(text) {
    if (!text || text.trim().length === 0) {
        return { valid: false, error: 'Task description is required' };
    }

    if (text.length > 500) {
        return { valid: false, error: 'Task description is too long (max 500 characters)' };
    }

    return { valid: true };
}

async function addTask() {
    const text = document.getElementById('taskInput').value;
    const validation = validateTaskInput(text);

    if (!validation.valid) {
        showError(validation.error);
        return;
    }

    await pt.add('task', {
        text: text.trim(),
        completed: false
    });
}
```

### 3. Confirm Destructive Actions

```JAVASCRIPT
async function deleteTask(taskId) {
    if (!confirm('Delete this task? This cannot be undone.')) {
        return;
    }

    try {
        await pt.delete(taskId);
        await loadTasks();
    } catch (error) {
        console.error('Error deleting task:', error);
        showError('Failed to delete task');
    }
}
```

## UI/UX Best Practices

### 1. Show Loading States

```JAVASCRIPT
async function loadTasks() {
    const loading = document.getElementById('loading');
    const tasksList = document.getElementById('tasksList');

    loading.style.display = 'block';
    tasksList.style.display = 'none';

    try {
        const entities = await pt.list({
            entityNames: ['task'],
            filters: { completed: false }
        });

        const tasks = entities.filter(e => e.entity_name === 'task');
        displayTasks(tasks);
    } finally {
        loading.style.display = 'none';
        tasksList.style.display = 'block';
    }
}
```

### 2. Provide Visual Feedback

```JAVASCRIPT
async function addTask() {
    const button = document.getElementById('addButton');
    const originalText = button.textContent;

    // Show loading state
    button.disabled = true;
    button.textContent = 'Adding...';

    try {
        const text = document.getElementById('taskInput').value.trim();
        await pt.add('task', { text: text, completed: false });

        // Show success
        button.textContent = '✓ Added';
        document.getElementById('taskInput').value = '';

        await loadTasks();

        // Reset button after delay
        setTimeout(() => {
            button.textContent = originalText;
            button.disabled = false;
        }, 1000);
    } catch (error) {
        button.textContent = 'Error';
        button.disabled = false;

        setTimeout(() => {
            button.textContent = originalText;
        }, 2000);
    }
}
```

### 3. Handle Empty States

```JAVASCRIPT
function displayTasks(tasks) {
    const container = document.getElementById('tasksList');

    if (tasks.length === 0) {
        container.innerHTML = `
            <div class="text-center py-12 text-gray-500">
                <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
                </svg>
                <p class="mt-2 text-sm">No tasks found</p>
                <button onclick="clearFilters()" class="mt-4 text-blue-500 text-sm">
                    Clear filters
                </button>
            </div>
        `;
        return;
    }

    container.innerHTML = tasks.map(renderTask).join('');
}
```

## Troubleshooting

### Common Issues

Issue: `pt.list()` returns empty array

Solutions:

* Check that entity name is correct

* Verify filters are valid

* Try without filters to see if entities exist

* Check browser console for errors

```JAVASCRIPT
// Debug filters
async function debugFilters() {
    // Start simple
    console.log('All tasks:', await pt.list({ entityNames: ['task'] }));

    // Add filters incrementally
    console.log('Active tasks:', await pt.list({
        entityNames: ['task'],
        filters: { status: 'active' }
    }));
}
```

Issue: Data not updating after edit

Solutions:

* Ensure you're merging with existing data

* Check that you're calling loadTasks() after edit

* Verify the edit was successful

```JAVASCRIPT
// ✅ GOOD: Proper edit
const task = await pt.get(taskId);
await pt.edit(taskId, {
    ...task.data,
    completed: true
});
await loadTasks(); // Refresh display
```

Issue: Performance is slow

Solutions:

* Implement pagination

* Use server-side filtering

* Cache static data

* Reduce data transfer with appropriate limits

```JAVASCRIPT
// ✅ GOOD: Optimized loading
const result = await pt.list({
    entityNames: ['task'],
    filters: { status: 'active' }, // Server-side filter
    page: 1,
    pageSize: 20, // Pagination
    returnMetadata: true
});
```

## Testing Tips

### 1. Test with Empty Data

```JAVASCRIPT
function displayTasks(tasks) {
    // Handle empty state
    if (!tasks || tasks.length === 0) {
        showEmptyState();
        return;
    }

    renderTasks(tasks);
}
```

### 2. Test with Large Datasets

```JAVASCRIPT
// Test pagination with many items
async function testWithLargeDataset() {
    const tasks = await pt.list({
        entityNames: ['task'],
        page: 1,
        pageSize: 20,
        returnMetadata: true
    });

    console.log('Has more pages:', tasks.pagination.has_more);
    console.log('Count:', tasks.count);
}
```

### 3. Test Error Scenarios

```JAVASCRIPT
async function testErrorHandling() {
    try {
        await pt.get(999999); // Non-existent ID
    } catch (error) {
        console.log('Error handled correctly:', error);
    }
}
```

## Performance Checklist

* [ ] Use `pt.get()` for single entity lookups

* [ ] Implement server-side filtering

* [ ] Use pagination for large datasets

* [ ] Cache chat members and other static data

* [ ] Implement debouncing for search inputs

* [ ] Use batch operations with `Promise.all()`

* [ ] Provide immediate feedback with PROCESSING status for uploads

* [ ] Choose appropriate filter operators

* [ ] Set reasonable limits on queries

* [ ] Implement caching where appropriate

* [ ] Show loading states

* [ ] Handle errors gracefully

* [ ] Validate user input

* [ ] Escape HTML content

* [ ] Test with empty and large datasets

* [ ] Use Goals for unified upload experience (Live Page + Chat + Email)

* [ ] Ensure Goal entity structure matches Live Page queries

* [ ] Make Goal prompts explicit about chatdb tool usage

* [ ] Choose appropriate pattern: Upload-Then-Add for automation, Add-Then-Update for interactive UIs

## Next Steps

* [Creating Live Pages](creating-live-pages.html) - Back to main guide

* [Data Management API Reference](data-management-api.html) - Learn about all pt API methods

* [Filtering and Querying](filtering-and-querying.html) - Advanced filtering techniques

* [Complete Examples](live-pages-examples.html) - See best practices in action



# Creating Live Apps

## Introduction

PrimeThink is a powerful platform for building dynamic applications that adapt to user journeys and preferences. These applications combine dynamic rendering with AI-powered natural language interactions, allowing both form-based and conversational inputs. The platform enables developers to create highly customizable user experiences that evolve based on user interactions and data.

## Core Concepts

### Live Apps Overview

A dynamic app in PrimeThink consists of multiple interconnected components that work together to create a responsive, user-centered experience. The application adapts its interface and functionality based on user interactions, stored data, and predefined rules.

### Task Types

The platform supports several specialized types of tasks:

1. Page Tasks
These tasks generate dynamic pages based on specific rules governing:

* Content rendering

* Update timing

* Data sources

* Display conditions

2. Chat Tasks

* Extraction Tasks: Designed to gather information from users through natural language conversations. These can follow flexible or strict guidelines depending on the data collection requirements.

* RAG Tasks (Retrieval Augmented Generation): Function as intelligent support systems or FAQs by leveraging provided documents and collections to answer user queries.

* Public Support Tasks: Shareable tasks that can be embedded in external websites for: * Lead generation * Guest user support * Anonymous session management with future authentication capabilities

### Navigation Structure

The application presents tasks through a sectioned navigation menu, where:

* Each section represents a distinct task or dynamic page

* Sections can be organized hierarchically

* Tasks are presented with clear goals and optional scheduling

* Initial prompts guide users when accessing each section

## Building Live Apps

### Orchestration Patterns

#### Level-Based Progression

1. Initial Onboarding (Level 0)

* User registration triggers the onboarding task

* Creates specific tasks based on initial user data

* Sets up the foundation for user progression

2. Level Progression

* Tasks monitor user achievements and progress

* Completion triggers level advancement

* New levels initialize with fresh onboarding tasks

* Creates new appropriate tasks and pages for the level

#### Independent Task Chains

Tasks can operate independently, managing their own progression:

* Tasks determine their follow-up actions

* Can create subsequent tasks upon completion

* Self-archive when finished

* Trigger new related tasks as needed

### Page Implementation

Pages can be created through two primary methods:

1. Descriptive Approach

* Define page requirements through natural language

* Can be generic or highly specific

* System generates appropriate rendering

2. Template-Based Approach

* Upload custom HTML/CSS templates

* Define data mapping rules

* System populates templates with dynamic data

### Data Sources

The platform can integrate data from multiple sources:

* External APIs

* User profile information

* User metadata

* Event data

* Tool-accessible knowledge bases

## Development Approach

### Planning Phase

1. Map out the application flow on paper:

* Define initial onboarding process

* Identify required tasks

* Plan data collection points

* Determine success criteria

* Design progression triggers

### Implementation Phase

1. Configure task orchestration

2. Set up data extraction patterns

3. Define evaluation criteria

4. Establish data storage rules

5. Create progression triggers

### Orchestration Strategy

Choose between:

* Centralized orchestration with a main task

* Distributed orchestration across multiple tasks

* Task-level self-orchestration

The choice depends on:

* Application complexity

* User journey requirements

* Data management needs

* Scalability requirements

## Best Practices

1. Clearly define user progression paths

2. Design flexible data extraction patterns

3. Plan for scalable orchestration

4. Implement appropriate page rendering strategies

5. Consider user experience in both form and conversation interactions

6. Design clear success criteria for task completion

7. Plan data storage and retrieval patterns

8. Create meaningful user feedback loops



# API: Auth

## Generating an API Key

In order to use the API, you will need to obtain an API key from the PrimeThink app. Go to `Settings > API Keys` and generate a new key.

## Using an API Key

To use the API, you will need to include the API key in the request header:

```
Authorization: Token YOUR_API_KEY
```

In some case, it may not be possible to use the `Authorization: Token` header. In this case, you can pass the API key as a query parameter:

```
?api_key=YOUR_API_KEY
```



# API: Use metadata in collections

When using collections via the api, it's possible to add metadata to the documents uploaded to the collection.

This will allow you to use the metadata to filter the documents within the collection when searching into the collection.

Example:

1. Upload a text document to the collection.

```CURL
curl -X "POST" "https://api.primethink.ai/api/v1/collections/<collection_id>/texts" \
     -H 'Authorization: <auth>' \
     -H 'Content-Type: application/json' \
     -d $'[
  {
    "name": "My report",
    "text": "report text",
    "metadata": "{\\"year\\":\\"\2025\"}"
  }
]'
```

Here we have added a metadata field called "year" with the value "2025".

Now let's say we want to search only the 2025 reports in the collection:

```CURL
curl -X "POST" "https://api.primethink.ai/api/v1/collections/<collection_id>/search?query=<my_query>" \
     -H 'Authorization: <auth>' \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "extra": {
    "year": "2025"
  }
}'
```



# Keyboard Shortcuts

I'll create a clear keyboard shortcuts guide in markdown format, organized by categories for better readability:

## Keyboard Shortcuts Guide

## Core Actions

| Action |Mac |Windows/Linux |
------------------------------
| New Chat |⌘ N |Ctrl + N |
| New Task |⌘ T |Ctrl + T |
| New Workspace |⌘ ⇧ N |Ctrl + Shift + N |

## Navigation

| Action |Mac |Windows/Linux |
------------------------------
| Switch to Group 1-9 |⌘ 1-9 |Ctrl + 1-9 |
| Next Tab |⌘ } |Ctrl + Tab |
| Previous Tab |⌘ { |Ctrl + Shift + Tab |
| Next Chat |⌘ ⌥ ↓ |Ctrl + Alt + ↓ |
| Previous Chat |⌘ ⌥ ↑ |Ctrl + Alt + ↑ |

## Admin & Settings

| Action |Mac |Windows/Linux |
------------------------------
| Memory Admin |⌘ ⇧ M |Ctrl + Shift + M |
| Collections Admin |⌘ ⇧ C |Ctrl + Shift + C |
| Settings |⌘ , |Ctrl + , |
| Notifications |⌘ ⇧ N |Ctrl + Shift + N |
| Scheduled Tasks |⌘ ⇧ S |Ctrl + Shift + S |
| Help |⌘ H |Ctrl + H |
| Feedback |⌘ ⇧ F |Ctrl + Shift + F |
| Support VA |⌘ / |Ctrl + / |

## Search & Interface

| Action |Mac |Windows/Linux |
------------------------------
| Global Search |Double ⇧ |Double Shift |
| Toggle Chat Bar |⌘ B |Ctrl + B |
| Toggle Sidebar |⌘ \ |Ctrl + \ |
| Filter Favorites |⌘ E |Ctrl + E |
| Filter Archived |⌘ A |Ctrl + A |

## Toggles

| Action |Mac |Windows/Linux |
------------------------------
| Location |⌘ L |Ctrl + L |
| Text-to-Speech |⌘ ⌥ T |Ctrl + Alt + T |

Legend:

* ⌘ (Command)

* ⇧ (Shift)

* ⌥ (Option/Alt)

* ↑ (Up Arrow)

* ↓ (Down Arrow)

Note: On Windows/Linux, the Command (⌘) key is replaced with Control (Ctrl)



# Feedback and Reporting problems

Start typing here...



# Use Cases

* Common scenarios

* Success stories

* Implementation examples

* Industry-specific applications



# Integration and APIs

Start typing here...



# Third-party Integrations

* Available integrations

* Integration setup guides

* Authentication and permissions

* Data sync options



# API Documentation

* API overview

* Authentication

* Endpoints

* Rate limits

* Example implementations



# Security and Privacy

## Data Protection

* Data encryption

* Privacy controls

* Information retention policies

* GDPR and compliance

## Security Settings

* Authentication methods

* Two-factor authentication

* Session management

* Security best practices



# Release notes

Here's a summary of recent updates and improvements to the PrimeThink platform, grouped by week and version.

## 

Week of April 21, 2025

This week, we've focused on enhancing collaboration within workspaces, streamlining task management, and refining the user interface for a smoother experience.

Workspaces & Collaboration

* Enhanced collaboration with new Workspace sharing options: 'View Only', 'Owner Only', and 'Shared'. You can now easily manage access levels directly from workspace settings, with clear visual indicators for shared workspaces. We've also added a confirmation step when enabling the fully 'Shared' mode.

* Ensured users can seamlessly send messages within 'Owner Only' shared workspaces.

* Improved the display of shared workspace information and icons for better clarity.

Tasks & Automation

* Provided more detailed control over documents attached to tasks, allowing you to manage their status and visibility more effectively.

* Improved the reliability of updating documents within tasks.

Chat Interface & Experience

* Links within Markdown messages are now fully clickable for easier navigation.

* Optimized the layout and spacing in the chat view for improved readability, particularly on mobile devices.

* Refined the document preview pop-up window, addressing scrollbar behavior and ensuring elements are positioned correctly.

* Streamlined how the application directs you when starting or returning to chats for a more intuitive flow.

Documents & Collections

* Improved the display and functionality within the document preview window.

User Accounts & Settings

* Improved login stability, preventing potential errors when entering incorrect passwords.

Platform Updates

* Resolved a redirection issue affecting users accessing the platform via the older app.ambrogio.ai address.

## 

Week of April 14, 2025

This week saw the launch of the new Task Evaluation feature, alongside improvements to notifications and agent management.

Tasks & Automation

* Introduced the Task Evaluation system. You can now set up automated tests for your tasks using predefined Excel plans to measure performance, verify effectiveness, and assist in task improvement.

* Added helpful guidance links ('help' and 'help_url') to task definitions.

Virtual Assistants (VAs) & AI

* Added helpful guidance links ('help' and 'help_url') to Virtual Assistant capability definitions.

* Ensured that private Virtual Assistants set as default assistants in chats or workspaces are correctly displayed to all members.

Chat Interface & Experience

* Improved email notifications for unread messages: Chat names now display correctly.

* Ensured you receive notifications properly, even when you @mention yourself in a chat.

* Corrected an issue where creating a new chat via the dedicated view didn't always place it in the selected workspace.

* Addressed inconsistencies in the sorting order of the chat list.

User Accounts & Settings

* Introduced an optional email domain whitelist feature for administrators to control outbound email notifications.

* Updated the default setting for the "New Chat" behavior in Group Settings to "Auto" for a more intuitive experience.

* Ensured temporary chats have a distinct visual appearance.

* Updated permissions so that only designated administrators can manage public tasks, which are now accessible only via their specific mention command.

Documents & Collections

* Improved the reliability of document indexing and saving processes.

Platform Updates

* Resolved an issue where users might encounter an error when assigning a chat to a workspace they didn't originally create.

* Corrected permissions related to modifying file properties (like search status) within a chat, ensuring only the uploader or authorized users can make changes.

* Addressed minor issues in the backend related to chat ordering and agent creation.

* Corrected missing fields in the forms for creating/editing agents and tasks.

## 

Week of April 7, 2025

This week brought major updates to user management, task/agent sharing, and the chat interface.

User Accounts & Settings

* You can now register for PrimeThink using invite codes.

* Users with novaware.io email addresses will now be automatically activated for easier internal testing.

* Added an "Invite User" button directly within the Members section for quicker team onboarding.

* Group Admins now have enhanced capabilities, including creating custom roles and managing 'Group Admin' assignments.

* User roles are now displayed more clearly in member lists and pending invites.

* Improved controls for role assignments during the invitation process.

* Streamlined the process for changing user roles within the platform.

* Users can no longer accidentally change their own role via the Members screen.

* Resolved an issue where a user's role might not update visually immediately after being changed.

* Addressed an issue that could prevent new group admins from creating private agents.

Tasks & Automation

* Easily import tasks created by others using a shared URL or ID via the new "Import Task" button.

* Quickly share your own tasks using the new "Copy Share ID" button next to each task.

* Removed outdated backend components related to the previous role system.

Virtual Assistants (VAs) & AI

* You can now import Virtual Assistants created by others using a shared URL or ID.

* Added a button to easily copy an Agent's share URL for sharing with colleagues.

* Improved the agent creation/edit interface: available capabilities are now dynamically shown based on the selected agent type.

Chat Interface & Experience

* Improved the content and formatting of email notifications for unread messages.

* You can now clear your AI memory without encountering errors.

* Addressed an issue that could cause the platform to crash during memory deletion.

* Refined the behavior of the "New Chat" screen when sending messages.

* Made it possible again to copy agent URLs.

* Ensured the correct screen state when navigating to the "New Chat" area.

* Implemented backend functionality for pinning chats (UI coming soon).

* The chat filtering icons now clearly indicate when a filter is active.

* Redesigned the "New Chat" screen: it now provides a more helpful starting point, showing recent tasks and chats.

* Enabled user mentions (@u_) and VA mentions (@va_) to trigger notifications or specific VAs.

* Notifications in the list now show a maximum of two lines for better readability.

* Clicking the chat icon next to a user in the Members screen now directly starts a one-on-one chat.

* The main "New Chat" button is now a split button: click directly for a standard chat, or use the arrow for options like "Temp Chat" or "Chat with options".

* Improved how pending invite details are displayed.

Documents & Collections

* Ensured real-time updates in the interface when a document is added to a chat.

* Streamlined the file upload process regarding how documents are initially added for search.

Platform Updates

* Introduced settings flags to control whether the app opens to your last chat or always starts a new one.

* Enhanced backend APIs for better group management and user timezone updates.

* Improved socket connections to properly support users belonging to multiple groups.

## 

Week of March 31, 2025

This week focused heavily on backend enhancements, new integrations, and preparing features for user interaction.

New Features

* Added support for setting a specific "Onboarding Task" for new users joining a group.

* Introduced backend capabilities for running commands within temporary chats or task contexts.

* Enabled cloning and importing of entire Collections using a unique ID.

* Integrated the Markitdown library for enhanced document processing, supporting ZIP files, EML emails, DOCX files with images, and advanced PDF analysis using Gemini or Mistral OCR.

Tasks & Automation

* Refined the backend processes for migrating virtual assistants and importing tasks/VAs, ensuring configurations like capabilities are correctly handled.

Virtual Assistants (VAs) & AI

* Enhanced backend systems for managing Virtual Assistants uniquely within each group.

* Improved permission handling related to Virtual Assistants.

Chat Interface & Experience

* Added backend support for deleting user accounts and groups.

User Accounts & Settings

* Implemented backend support for setting a default role for invited users within a group.

* Improved the backend handling of Virtual Assistant types, making the 'type' field mandatory.

Platform Updates

* Implemented backend API functionality for reordering groups in the group switcher.

* Addressed a critical issue preventing scheduled tasks from executing correctly.

* Improved how the platform selects PDF processing tools based on available API keys.

* Continued work on integrating social logins using Firebase.

## 

Week of March 24, 2025

This week included improvements to chat organization, document handling, and preparations for upcoming features.

Chat Interface & Experience

* Improved the display of user names in direct message chats.

* Added filtering options for direct message chats.

* Ensured drag-and-drop assignment of chats to workspaces updates smoothly without requiring a list refresh.

* Fixed an issue where editing a message could lose its attachments.

Tasks & Automation

* Ensured Virtual Assistant capabilities are correctly copied when importing tasks.

* Improved the handling of Virtual Assistants during task import processes.

* Updated the task creation/editing interface with new flags and document handling options.

Documents & Collections

* Improved pagination functionality in the Memory section.

Platform Updates

* Addressed an issue with MP4 file uploads originating from the mobile app.

* Implemented backend support for temporary chats, including automatic cleanup.

* Refined the process for checking and importing Tasks by their unique ID.

* Improved the display of group public names in the top navigation bar.

* Fixed an issue with changing the public name for a group in settings.

* Adjusted the display of group images in settings for consistency.

* Continued work on the new roles and permissions system (backend).

* Added backend support for direct message chats between users.

* Ensured the auto-archive feature correctly skips favorited chats.

* Continued preparations for the Android app release.

## 

Week of March 17, 2025

Focus this week was on backend infrastructure, Virtual Assistant management, and document processing enhancements.

Virtual Assistants (VAs) & AI

* Resolved an issue preventing the creation of new agents.

* Ensured Virtual Assistants are correctly managed uniquely for each group.

Documents & Collections

* Improved document processing for DOCX files containing images.

Platform Updates

* Addressed potential stability issues related to database connections under load (socket.io and k8s).

* Continued development work on the new roles and permissions system.

## 

Week of March 10, 2025

This week featured UI refinements for chat and collections, along with backend preparations for new features.

Chat Interface & Experience

* Improved the visual display of message titles within bubbles.

* Enhanced text input navigation, allowing the use of up/down arrows within multi-line messages.

* Improved the reliability of selecting and copying text from messages, preserving formatting.

* Added the ability to copy selected text using the right-click context menu in message bubbles.

Documents & Collections

* Streamlined actions within Collections to prevent unnecessary list refreshes.

Platform Updates

* Continued investigation into using Firebase for push notifications.

* Continued investigation into potentially migrating real-time updates from Pusher to Socket.io.

* Implemented a backend endpoint to allow refreshing user tokens before they expire.

* Improved the display of group images in the group switcher sidebar, showing initials if no image is set.

## 

Week of March 3, 2025

Updates this week focused on user management, task copying, and document status improvements.

Tasks & Automation

* When copying a task, it's now automatically prefixed with "Copy of" and set to private if the original was Global or Group-level.

Documents & Collections

* Renamed the document status 'disabled' to 'archived' for better clarity in Tasks and Chat attachments.

* Added status tracking ('search', 'context', 'archived') to documents associated with Tasks, similar to how they are handled in Chats.

* Added a 'hidden' flag for documents and collections, useful for items embedded from Tasks that shouldn't be directly visible.

* Improved the display of document names in memory search results.

User Accounts & Settings

* Implemented backend support for deleting invited users who haven't yet registered.

* Improved the process for reissuing invitation tokens when re-sending an invite.

* Added a flag to differentiate between settings managed automatically by the system and those set by users/admins.

* Ensured only authorized admins can view pending invites and manage invitations.

Platform Updates

* Continued work on setting up separate databases for different environments.

* Added backend support for listing users and VAs with descriptions in the context of multi-user chats.

* Removed legacy fields from user settings in the backend.

* Addressed potential backend processing bottlenecks.

## 

Week of February 25, 2025

This week brought improvements to chat management, task editing, group creation, and user feedback mechanisms.

Chat Interface & Experience

* Implemented manual reordering of chats in the sidebar.

* Added a context menu (three dots) to chat items in the sidebar, reintroducing the rename function.

* Improved the feedback tool based on user input regarding text visibility and input behavior.

Tasks & Automation

* Enhanced the Task editor with a larger, more user-friendly editor for the 'Goal' field.

* Added document status controls ('search', 'context', 'archived') to the Task editing interface.

User Accounts & Settings

* Enabled users to create new groups directly.

Platform Updates

* Improved the copying process for tasks to ensure all settings are carried over correctly.

* Addressed an issue with the feedback tool reported by users.

* Enhanced the backend logic for handling multi-user chats and Virtual Assistant interactions.

* Improved the document download functionality for pasted text documents.

## 

Week of February 19, 2025

This week focused on document management, backend stability, and preparing for multi-user features.

Documents & Collections

* Introduced status options ('search', 'context', 'disabled') for documents within chats, controllable via the Documents tab.

* Added status options ('active', 'disabled') for collections within chats, controllable via the Collections tab.

* Improved document processing for DOCX files to retain formatting.

* Added the ability to rename documents directly within the interface.

* Added a 'Name' field when pasting text to create a new document.

Chat Interface & Experience

* Introduced the 'Page' chat type, allowing for dynamic HTML content display within the main chat area.

* Added 'Page' as an option in the 'New Chat' and 'New Task' creation dialogs.

* Updated the top bar to show the group image and name instead of the default logo.

Platform Updates

* Addressed a potential backend error related to database connections.

* Improved handling of audio and recorded documents during processing.

* Enhanced backend support for multiple worker processes for improved scalability.

* Added support for uploading .eml and .py files.

* Addressed potential scalability issues identified during load testing.

* Fixed an issue where attachments could sometimes disappear from messages.

* Resolved backend warnings related to library deprecations.

* Ensured the document list correctly reflects the 'ready' status for processing.

## 

Week of February 12, 2025

Key updates this week included workspace improvements, user settings enhancements, and bug fixes.

Workspaces & Collaboration

* Added visual indicators (initials) for groups in the sidebar if no image is set.

* Fixed an issue preventing the 'Add Member' button from being visible in workspace settings.

User Accounts & Settings

* Added functionality to hide sensitive values (like API keys or passwords) in User and Group variable settings.

Chat Interface & Experience

* Fixed an issue where editing a message could remove existing attachments.

* Improved actions within Collections to prevent unnecessary list refreshes.

Platform Updates

* Addressed an issue where audio and recorded documents were not being processed correctly.

* Improved backend support for managing multiple background workers.

* Fixed an issue where downloading pasted text documents could fail.

## 

Week of February 5, 2025

This week focused on improving task ordering, search functionality, and memory management.

Tasks & Automation

* Corrected the sorting order for Tasks, ensuring frequently used tasks appear higher.

Chat Interface & Experience

* Fixed an issue where the search text in the chat list would disappear after selecting a chat.

Memory & AI

* Added an optional 'key' field to memories for better organization and retrieval.

* Ensured added collections are correctly included in the context for AI responses.

Platform Updates

* Addressed a potential conflict warning in backend data models.

* Continued work on improving document indexing status representation.

## 

Week of January 29, 2025

Focus this week was on document management, workspace features, and API improvements.

Documents & Collections

* Added an API endpoint to add existing documents to a collection by their ID.

* Added backend support for renaming documents specifically within a chat or collection context.

* Improved the document download functionality for pasted text documents.

Workspaces & Collaboration

* Added backend support for managing documents and collections directly within workspaces.

* Ensured the workspace system prompt is correctly used by the standard AI agent.

* Continued testing and refinement of workspace member management.

Platform Updates

* Added backend support for allowing programmatic API access using API keys associated with user roles and groups.

* Continued planning and discussion for the new roles and permissions system.

* Added backend support for processing actions involving specific document IDs.

* Removed potentially unused components related to older document processing libraries.

## 

Week of January 22, 2025

This week brought enhancements to user settings, notifications, document handling, and Virtual Assistant configuration.

User Accounts & Settings

* Added settings for default voice provider (OpenAI, Eleven Labs) and preferred voice, used when no specific VA is active.

* Added an API endpoint for users to update their timezone.

Notifications & Communication

* Implemented backend logic for sending email notifications after a delay for unread messages.

* Ensured public API endpoints for chat interactions maintain consistent data formats.

Virtual Assistants (VAs) & AI

* Added a 'Voice' selection field to the Virtual Assistant creation/editing form.

Documents & Collections

* Updated backend document models for better clarity ('extracted_text' field).

* Added API support for retrieving a single memory by its ID.

Chat Interface & Experience

* Implemented backend support for dynamic commands within HTML for 'Page' type chats.

Platform Updates

* Addressed an issue where removing capabilities when updating a Virtual Assistant wasn't working correctly.

* Ensured the standard AI agent only attempts to search within documents marked with 'search' status.

* Ensured the standard AI agent only searches within collections marked as 'active'.

* Improved backend task queue management.

* Improved document processing for DOCX files.

* Fixed an issue where saving documents or recordings could cause an error.

* Ensured the list of documents correctly reflects the 'ready' status after processing.

* Corrected backend data model naming conventions.

* Updated the API client to reflect recent backend changes (new chat fields, task flags, document/collection statuses).

* Fixed an error preventing tasks from loading correctly.

* Addressed a backend error related to concurrent operations.

* Fixed an issue where the 'Forgot Password' feature was case-sensitive.

## 

Week of January 15, 2025

This week focused on significant improvements to chat functionality, multi-user interactions, document management, and backend architecture.

Chat Interface & Experience

* Introduced the ability to rename documents specifically within a chat or collection context.

* Added a 'Name' field when pasting text to create a document.

* Updated the top bar to display the group image and name.

* Improved handling of group information retrieval.

* Fixed an issue with invalid links in invitation emails.

* Ensured the task list correctly filters private tasks belonging to other users.

* Resolved an error that could occur when deleting an empty chat.

* Fixed a bug preventing members from being added correctly when creating a new chat.

* Added a checkbox in the subchats tab to control context sharing ("Share context with subchats").

* Introduced the 'Page' chat type and updated creation dialogs accordingly.

* Added backend support for the 'Page' chat type.

* Implemented manual chat reordering in the sidebar.

* Improved the message editing process for multi-user chats.

* Enhanced the 'Paste Text' dialog in the Documents tab with a title field and clearer instructions.

* Added backend support for searching chats by title and summary.

* Updated the display of replied-to messages to show more context.

* Ensured Super Admins can edit globally available Virtual Assistants.

* Added Text-to-Speech (TTS) playback controls to user messages (previously only on VA messages).

* Added search functionality to the chat list filter.

* Added an option to edit user messages (with restrictions based on mentions).

Documents & Collections

* Ensured documents are correctly separated and managed per group.

* Added an API endpoint to rename documents specifically within a chat or collection.

Virtual Assistants (VAs) & AI

* Added support for multiple Text-to-Speech (TTS) providers (OpenAI, Eleven Labs) selectable per Virtual Assistant.

* Ensured Super Admins can edit globally available Virtual Assistants.

Workspaces & Collaboration

* Added functionality to delete chats directly from the workspace view.

* Fixed a bug preventing invited users from being added to new chats correctly.

* Ensured deleting a multi-user chat removes the current user but doesn't delete the chat if others remain.

Notifications & Communication

* Fixed an issue where clicking an invite link didn't correctly handle user registration/login flow.

* Ensured users invited to a chat receive a notification and their chat list refreshes automatically.

Platform Updates

* Completely separated document storage and access by group ID.

* Added backend support for multiple user groups.

* Addressed performance concerns regarding message posting times.

* Improved backend handling of chat message relationships (lazy loading).

* Added backend support for putting document names into the metadata for vector indexing.

* Added user timezone and default language settings.

* Implemented notification filtering per group.

* Added backend support for custom theme colors per group with user overrides.

## 

Week of January 8, 2025

This week focused on notifications, scheduled jobs, multi-user chat enhancements, and UI improvements across the platform.

Notifications & Communication

* Fixed an issue causing errors when viewing paginated notifications.

* Ensured push notifications include necessary IDs (group, chat, message) for correct navigation.

* Corrected notification counts for multi-user chats without a default VA.

* Ensured scheduled job notifications correctly link to the job message in the chat.

* Implemented backend support for sending push notifications specifically to authenticated users.

* Added the sub-chat ID to notification data for correct navigation.

* Added a visual indicator (badge count) to the group icon showing the total number of unread notifications for that group.

Tasks & Automation

* Ensured scheduled jobs correctly associate notifications with the job message ID.

* Added natural language processing for scheduling tasks (e.g., "every Monday at 9am").

* Added a visual indicator (loading spinner) and disabled state to the 'Save' button when adding/editing scheduled jobs.

Chat Interface & Experience

* Added visual avatar images to the members list.

* Fixed an issue preventing the selection of a default Virtual Assistant in user settings.

* Added the ability to use arrow keys and Enter to select mentions from the suggestion list.

* Improved the display of Virtual Assistant icons in the chat list for single vs. multi-user chats.

* Added a visual indicator (e.g., an icon) for temporary chats in the chat list.

* Added a copy button to the chat summary section in the sidebar.

* Added checkboxes for context sharing settings in the chat sidebar (for parent and sub-chats).

* Added a user setting for enabling auto-archiving of chats based on inactivity.

* Implemented UI for replying to and editing messages, including visual indicators for replies.

* Added the ability to add the content of any message to the current input prompt.

* Added an option to download messages as Markdown files.

* Restricted 'Save as Document' and 'Save as Memory' options based on chat capabilities.

* Redesigned the group switcher sidebar for improved usability and added quick access buttons (Invite, Location, TTS, Feedback, Help, Settings).

* Added filtering options for archived chats.

* Improved the display of dates in notifications.

* Added options to mark notifications as read/unread.

* Added support for rendering LaTeX mathematical notation within Markdown messages.

* Added a visual indicator (e.g., a filled icon) when chat filters (like Favorites or Archived) are active.

* Added a split 'New Chat' button offering quick creation of standard or temporary chats, or opening the advanced options dialog.

Virtual Assistants (VAs) & AI

* Ensured messages sent with VA mentions (@va_) do not include the mention text itself when processed by the AI.

* Added default avatar images for Virtual Assistants when no custom image is uploaded.

Documents & Collections

* Added preview thumbnails for image attachments within chat messages.

* Added file type icons for attachments.

* Improved the display of document processing status in Collections.

* Added a refresh button to the document list in the sidebar to manually check indexing progress.

* Added a resync button for documents in the chat sidebar in case of processing errors.

Mobile Experience

* Made dialog windows full-screen on mobile devices for better usability.

* Ensured the app navigates correctly on launch (to last chat or new chat screen).

* Addressed an issue preventing scrolling in mobile browsers.

Platform Updates

* Implemented an API endpoint to download message content as a Markdown file.

* Ensured the backend correctly handles different LLM providers based on user/group settings.

* Added backend support for camera capture and image selection for mobile attachments.

* Fixed an issue where scheduled jobs weren't generating responses correctly.

* Added backend support for retrieving single memory entries by ID.

* Investigated making core AI querying asynchronous for better performance.

* Fixed an API error related to streaming responses.

* Added a specific message type for scheduled job notifications.

* Ensured Virtual Assistant capabilities are correctly copied to subchats created via tools.

* Added backend support for assigning specific Virtual Assistant types to groups.

* Fixed an issue where AI requests could contain multiple system messages.

* Addressed potential issues with running multiple backend workers concurrently.

* Fixed an issue where the list of available members/VAs in the mention selector wasn't updating correctly.

* Improved the API for retrieving user details within a group to include avatar images.

* Added backend support for using cronjob expressions for scheduled tasks.

* Added backend support for creating subchats via tools.

* Added backend support for saving message content as a document.

* Added backend support for enabling/disabling location updates.

* Added backend support for chat notes and a tool to manage them.

* Implemented a system for tracking read/unread messages per user in a chat.

* Improved the message context menu positioning.

* Added backend support for selecting TTS voices per Virtual Assistant.

* Fixed an AI loop issue triggered by certain types of instructions.

* Resolved issues with TTS playback and component initialization on hot restarts.

* Improved memory re-indexing processes.

* Added backend support for clearing and re-indexing all memories.

* Implemented a notification system for tools to inform users about background processes.

* Improved error message display to users.

* Enhanced the web browsing tool with LLM-powered interaction and source quoting.

* Added support for OpenAI TTS.

* Added tools for summarizing documents and links.

* Updated core AI libraries (LangChain).

* Completed the database architecture for Collections and Documents.

* Implemented semantic search for memories in the Memory section.

* Prepared the platform for initial user onboarding.

* Added support for receiving text, URLs, documents, and images via the mobile share system.

* Added backend support for inviting external users and multiple VAs to a chat.

* Allowed switching the Virtual Assistant for existing chats.

* Added application icons and logos.

* Removed dependencies on older memory systems (Zep).

* Implemented a system for background tasks (summarization, entity extraction).

* Added support for push notifications on web platforms.

* Added backend support for extracting entities from conversations for memory.

* Ensured extra context is stored correctly in the vector database for memories.

* Implemented semantic indexing for memories to retrieve relevant context.

* Grouped chat messages visually by day.

* Refactored chat architecture for multi-user support.

* Added workspace selection during new chat creation.

* Implemented the core concept of unread messages.

* Added a long-press option on messages to save them as documents.

* Ensured all relevant context is added to the memory system.

* Removed dependencies on older chat UI components.

* Added backend support for Virtual Assistant tools stored in the database.

* Added filtering chats by the assigned Virtual Assistant.

* Included contextual information with messages and replies.

* Implemented various AI-powered tools.

* Added image analysis capabilities.

* Added visual indicators for when the AI is processing or encounters an error.

* Allowed memory types to be dynamically set per agent.

* Added a message reporting feature.

* Added necessary database indexes for performance.

* Implemented a 'Clear Chat' button functionality.

* Implemented proper URL-based routing for the web application.

* Fixed backend API errors.

* Added the concept of Workspaces for grouping chats.

* Added document capture via camera for mobile apps.

* Added backend support for setting chat goals and initial prompts.

* Implemented chat list sorting by last update time.

* Improved URL navigation for sub-chats.

* Added the ability to star/favorite chats.

* Completed core feature set for v0.5 release.

* Improved the saving mechanism for notes and todos created via tools.

* Implemented a data collection tool.

* Ensured memory clearing correctly removes associated vector data.

* Added secure storage for user login credentials.

* Added storage for user preferences regarding microphone usage.

* Ensured AI responses correctly reference the message they are replying to.

* Added structured data extraction capabilities to the memory system (e.g., extracting calories from meal descriptions).

* Added backend support for interactive, multi-step goals.

* Added backend support for automated, multi-step goals.

* Implemented document/URL vector indexing and storage.

* Implemented document/URL summarization and storage.

* Added support for cron-based reminders/scheduled tasks.

* Added the ability to select from multiple available assistants when creating chats.

* Added display of user and assistant avatars/images.

* Added collection and use of user location data (with permission).

This covers all the updates extracted from the provided tickets.

## 1.0.2

11-12-2024

### New features

* Notifications for all the chat users - In a multi user chat, it's now possible to write a message and notify all users by writing `@here` or `@all`.

* Duplicate task - A task can now easily duplicated with a new button near the task.

* Possibility to edit private VAs

### Improvements

* Summary VA - The Summary VA now works with more types of documents.

* Improved error handling

* Favourite chats

* Info chat reordered

## 1.0.1

09-12-2024

Small improvements.

## 1.0.0

08-12-2024

First release.



# Troubleshooting

## Common Issues

* Connection problems

* Agent response issues

* Permission errors

* Integration challenges



# Support

* FAQs

* Community forums

* Contact support

* Feature requests



# Future

## Collaborative AI Features

* Multi-agent conversations

* AI agent teamwork

* Human-AI collaboration best practices

* Agent handoffs and escalation

## Advanced AI Functions

* Custom agent training

* Workflow automation

* Data analysis and visualization

* Code generation and review

* Document processing



# API Reference

## Overview

The PrimeThink API provides programmatic access to PrimeThink's capabilities, allowing developers to integrate AI-powered features into their applications. This reference provides the resources you need to understand and work with our API.

## API Documentation Resources

* OpenAPI Specification: [https://api.primethink.ai/openapi.json](https://api.primethink.ai/openapi.json)

* Interactive API Documentation: [https://api.primethink.ai/docs](https://api.primethink.ai/docs)

## Authentication

API requests require authentication using an API key. You can obtain your API key from the PrimeThink dashboard.

Include your API key in the request header:

```
Authorization: Bearer YOUR_API_KEY
```

## Rate Limits

Please be aware of the following rate limits when using the API:

* Free tier: 100 requests per day

* Pro tier: 1,000 requests per hour

* Enterprise tier: Custom limits available

## Need Help?

If you encounter any issues or have questions about the API, please contact our support team at [support@primethink.ai](mailto:support@primethink.ai) or visit our [community forum](https://community.primethink.ai).



