# 🌀 SeaArt Python SDK

[![PyPI version](https://img.shields.io/pypi/v/seaart)](https://pypi.org/project/seaart/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](./LICENSE)

Unofficial typed Python client for the [SeaArt.ai](https://www.seaart.ai/) API with sync and async support.

## Installation

```bash
pip install seaart
```

## Quick Start

### Authentication

```python
from seaart import SyncClient

with SyncClient() as client:
    client.auth.login(email="user@example.com", password="password")
```

```python
from seaart import AsyncClient

async with AsyncClient() as client:
    await client.auth.login(email="user@example.com", password="password")
```

No account? Use tourist mode for quick access:

```python
client.auth.login_as_tourist()
```

### Character Chat

```python
from seaart import SyncClient
from seaart.helpers import extract_character_link
from seaart.types import AppId

link = extract_character_link("https://www.seaart.ai/character/chat/...")
with SyncClient(app_id=AppId.APP) as client:
    client.auth.login(email="user@example.com", password="password")

    chat = client.characters.chats.create(link.character_id)
    result = client.characters.chats.send("Hello!", chat.value.session_id)
    print(result.text)
```

```python
from seaart import AsyncClient
from seaart.helpers import extract_character_link
from seaart.types import AppId

link = extract_character_link("https://www.seaart.ai/character/chat/...")
async with AsyncClient(app_id=AppId.APP) as client:
    await client.auth.login(email="user@example.com", password="password")

    chat = await client.characters.chats.create(link.character_id)
    result = await client.characters.chats.send("Hello!", chat.value.session_id)
    print(result.text)
```

### Image Generation

```python
from seaart import SyncClient
from seaart.helpers import extract_model_link

model = extract_model_link("https://www.seaart.ai/create/image?id=...&model_ver_no=...")
with SyncClient() as client:
    client.auth.login(email="user@example.com", password="password")

    envelope = client.images.text_to_img(
        prompt="a serene mountain landscape at sunset, oil painting",
        model_no=model.model_no,
    )
    item = envelope.wait(timeout=120)
    print(item.image_url)
```

```python
from seaart import AsyncClient
from seaart.helpers import extract_model_link

model = extract_model_link("https://www.seaart.ai/create/image?id=...&model_ver_no=...")
async with AsyncClient() as client:
    await client.auth.login(email="user@example.com", password="password")

    envelope = await client.images.text_to_img(
        prompt="a serene mountain landscape at sunset, oil painting",
        model_no=model.model_no,
    )
    item = await envelope.wait(timeout=120)
    print(item.image_url)
```

## Features

- Sync and async clients with identical APIs
- Typed Pydantic v2 response models
- SSE streaming for character chat
- Task polling with `envelope.wait()`
- Built on curl-cffi (HTTP/2, HTTP/3, browser impersonation)
- Structured exception hierarchy

## Requirements

Python 3.11 or higher.

## Examples

| File | Description |
|------|-------------|
| [quickstart.py](examples/quickstart.py) | Minimal working examples for chat and image generation |
| [auth.py](examples/auth.py) | All authentication methods |
| [characters_chat.py](examples/characters_chat.py) | Chat, history, settings, voice audio |
| [characters_stream.py](examples/characters_stream.py) | SSE streaming for character responses |
| [image_generation.py](examples/image_generation.py) | Text-to-image, img-to-img, upscaling, background removal |
| [search.py](examples/search.py) | Search characters, models, works with pagination |
| [gen_agent.py](examples/gen_agent.py) | GenAgent streaming and image attachments |
| [async_usage.py](examples/async_usage.py) | Concurrent generation and streaming with AsyncClient |
| [custom_requests.py](examples/custom_requests.py) | Low-level `request()` and `request_stream()` for uncovered endpoints |
