# Action Table - Image

## 1. Introduction

This tutorial will guide you through using the JamAI Base TypeScript SDK to create a simple receipt information extraction system.

### What We'll Build

Snap a photo of your receipt and let AI do the expense report for you!

In this tutorial, we'll create a receipt processing system that:

* Takes a receipt image as input
* Uploads it to JamAI Base action table
* Extracts key information like:
  * Shop name
  * Total amount

### Prerequisites

Before starting, you'll need:

* Node.js 16.x or higher installed
* Project ID and Personal Access Token (PAT)

## 2. Installation and Setup

### Installing the SDK

```bash
npm install jamaibase
```

### Basic Configuration

Get your Personal Access Token (PAT) here:

Get your Project ID here:

```typescript
import JamAI from "jamaibase";

const PROJECT_ID = "your_project_id";
const PAT = "your_PAT";

const client = new JamAI({
  projectId: PROJECT_ID,
  token: PAT,
});
```

## 3. Creating Your Action Table

{% stepper %}
{% step %}

### Create action table

* Navigate to your JamAI Base action table tab.
* Create a new action table named "receipt".
  {% endstep %}

{% step %}

### Configure columns

Configure the following columns:

```
Input Column:
- Name: "Image"
- Type: FILE
```

{% endstep %}

{% step %}

### Configure output columns

```
Output Columns:
- Name: "Shop Name"
  Type: str
  ☑️ Output checkbox enabled

- Name: "Total"
  Type: str
  ☑️ Output checkbox enabled
```

{% endstep %}
{% endstepper %}

## 4. Basic Implementation

### 4.1 Simple Receipt Processor

```typescript
async function processSingleReceipt(imagePath: string) {
  // Upload image file
  const fileResponse = await client.file.uploadFile({
    file_path: imagePath,
  });

  // Process in action table
  const response = await client.table.addRow({
    table_type: "action",
    table_id: "receipt",
    data: [{ Image: fileResponse.uri }],
    concurrent: false,
  });

  // Extract results
  return {
    shopName:
      response.rows[0]?.columns["Shop Name"]?.choices[0]?.message?.content,
    total: response.rows[0]?.columns["Total"]?.choices[0]?.message?.content,
  };
}
```

### 4.2 Complete Implementation with Error Handling

```typescript
import JamAI from "jamaibase";
import * as fs from "fs";
import * as path from "path";

class ReceiptProcessor {
  private client: JamAI;

  constructor(projectId: string, pat: string) {
    this.client = new JamAI({
      projectId: projectId,
      token: pat,
    });
  }

  validateImage(imagePath: string): boolean {
    // Validate if file exists and has correct extension
    if (!fs.existsSync(imagePath)) {
      throw new Error(`Image not found: ${imagePath}`);
    }

    const validExtensions = [".jpg", ".jpeg", ".png", ".webp", ".gif"];
    const fileExt = path.extname(imagePath).toLowerCase();
    if (!validExtensions.includes(fileExt)) {
      throw new Error(
        `Unsupported file format. Use: ${validExtensions.join(", ")}`
      );
    }

    return true;
  }

  async processReceipt(
    imagePath: string
  ): Promise<{ shopName: string; total: string } | null> {
    try {
      // Validate image
      this.validateImage(imagePath);

      // Upload file
      console.log("Uploading image...");
      const fileResponse = await this.client.file.uploadFile({
        file_path: imagePath,
      });
      console.log(`Upload successful: ${fileResponse.uri}`);

      // Process in action table
      console.log("Processing receipt...");
      const response = await this.client.table.addRow({
        table_type: "action",
        table_id: "receipt",
        data: [{ Image: fileResponse.uri }],
        concurrent: false,
      });

      // Extract and return results
      const results = {
        shopName: String(
          response.rows[0]?.columns["Shop Name"]?.choices[0]?.message
            ?.content ?? ""
        ),
        total: String(
          response.rows[0]?.columns["Total"]?.choices[0]?.message?.content ?? ""
        ),
      };
      console.log("Processing complete!");
      return results;
    } catch (error) {
      console.error(`Error processing receipt: ${error}`);
      return null;
    }
  }
}
```

## 5. Usage Examples

### 5.1 Basic Usage

```typescript
// Initialize processor
const processor = new ReceiptProcessor(PROJECT_ID, PAT);

// Process single receipt
const result = await processor.processReceipt("path/to/receipt.jpg");

if (result) {
  console.log(`Shop Name: ${result.shopName}`);
  console.log(`Total: ${result.total}`);
}
```

### 5.2 Batch Processing

```typescript
async function processReceiptBatch(receiptFolder: string) {
  const processor = new ReceiptProcessor(PROJECT_ID, PAT);
  const results = [];

  const files = fs.readdirSync(receiptFolder);

  for (const filename of files) {
    if (filename.toLowerCase().match(/\.(jpg|jpeg|png|webp|gif)$/)) {
      const imagePath = path.join(receiptFolder, filename);
      const result = await processor.processReceipt(imagePath);
      if (result) {
        results.push({
          filename: filename,
          ...result,
        });
      }
    }
  }

  return results;
}

// Usage
const results = await processReceiptBatch("path/to/receipt/folder");
for (const result of results) {
  console.log(`File: ${result.filename}`);
  console.log(`Shop: ${result.shopName}`);
  console.log(`Total: ${result.total}`);
  console.log("---");
}
```

## 6. Best Practices

* Error Handling
  * Always validate input files
  * Handle network errors gracefully
* Performance
  * Reuse the client instance
  * Consider batch processing for multiple files
  * Implement rate limiting for large batches
* Security
  * Use environment variables for credentials

## Complete Standalone Example

Save this as `receipt_processor.ts`:

```typescript
import JamAI from "jamaibase";
import * as fs from "fs";
import * as path from "path";

class ReceiptProcessor {
  private client: JamAI;

  constructor(projectId: string, pat: string) {
    this.client = new JamAI({
      projectId: projectId,
      token: pat,
    });
  }

  validateImage(imagePath: string): boolean {
    if (!fs.existsSync(imagePath)) {
      throw new Error(`Image not found: ${imagePath}`);
    }

    const validExtensions = [".jpg", ".jpeg", ".png"];
    const fileExt = path.extname(imagePath).toLowerCase();
    if (!validExtensions.includes(fileExt)) {
      throw new Error(
        `Unsupported file format. Use: ${validExtensions.join(", ")}`
      );
    }

    return true;
  }

  async processReceipt(
    imagePath: string
  ): Promise<{ shopName: string; total: string } | null> {
    try {
      this.validateImage(imagePath);

      console.log(`Processing receipt: ${imagePath}`);
      console.log("Uploading image...");
      const fileResponse = await this.client.file.uploadFile({
        file_path: imagePath,
      });
      console.log("Upload successful!");

      console.log("Extracting information...");
      const response = await this.client.table.addRow({
        table_type: "action",
        table_id: "receipt",
        data: [{ Image: fileResponse.uri }],
        concurrent: false,
      });

      const results = {
        shopName: String(
          response.rows[0]?.columns["Shop Name"]?.choices[0]?.message
            ?.content ?? ""
        ),
        total: String(
          response.rows[0]?.columns["Total"]?.choices[0]?.message?.content ?? ""
        ),
      };
      return results;
    } catch (error) {
      console.error(`Error: ${error}`);
      return null;
    }
  }
}

async function processFolder(
  folderPath: string,
  processor: ReceiptProcessor
): Promise<void> {
  // Process all receipts in a folder
  if (!fs.existsSync(folderPath)) {
    console.log(`Folder not found: ${folderPath}`);
    return;
  }

  const results = [];
  const files = fs.readdirSync(folderPath);

  for (const filename of files) {
    if (filename.toLowerCase().match(/\.(jpg|jpeg|png)$/)) {
      const imagePath = path.join(folderPath, filename);
      const result = await processor.processReceipt(imagePath);
      if (result) {
        results.push({
          filename: filename,
          ...result,
        });
      }
    }
  }

  console.log("\n=== Processing Results ===");
  for (const result of results) {
    console.log(`\nFile: ${result.filename}`);
    console.log(`Shop Name: ${result.shopName}`);
    console.log(`Total: ${result.total}`);
  }
}

// Main execution
async function main() {
  // Get credentials from environment variables
  const PROJECT_ID = process.env.JAMAI_PROJECT_ID || "your_project_id";
  const PAT = process.env.JAMAI_API_KEY || "your_PAT";

  const processor = new ReceiptProcessor(PROJECT_ID, PAT);

  // Process a single receipt
  const singleResult = await processor.processReceipt("path/to/receipt.jpg");
  if (singleResult) {
    console.log("\nSingle Receipt Result:");
    console.log(`Shop Name: ${singleResult.shopName}`);
    console.log(`Total: ${singleResult.total}`);
  }

  // Or process a folder of receipts
  // await processFolder("path/to/receipt/folder", processor);
}

main().catch(console.error);
```

## Running the Example

{% stepper %}
{% step %}
Save the code as `receipt_processor.ts`
{% endstep %}

{% step %}
Create a `.env` file:

```
JAMAI_PROJECT_ID=your_project_id
JAMAI_API_KEY=your_PAT
```

{% endstep %}

{% step %}
Install dependencies:

```bash
npm install jamaibase dotenv
npm install --save-dev @types/node typescript ts-node
```

{% endstep %}

{% step %}
Run with:

```bash
ts-node receipt_processor.ts
```

Or compile and run:

```bash
tsc receipt_processor.ts
node receipt_processor.js
```

{% endstep %}
{% endstepper %}

## Troubleshooting

<details>

<summary>File not found error</summary>

* Check that the file path is correct
* Use absolute paths or ensure relative paths are correct

</details>

<details>

<summary>Unsupported file format</summary>

* Ensure image is in JPG, PNG, WEBP, or GIF format
* Check file extension matches actual file type

</details>

<details>

<summary>Upload timeout</summary>

* Check internet connection
* Verify file size is reasonable (< 10MB recommended)

</details>

<details>

<summary>API errors</summary>

* Verify your PAT and Project ID are correct
* Check that the action table "receipt" exists in your project

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.jamaibase.com/developer-reference/typescript-sdk-documentation/quick-start-action-table/action-table-image.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
