# iOS SDK

A Swift package with no dependencies and no UI. It keeps the conversation —
messages, typing, connection — as observable state, and you draw it however
your app looks. iOS 15 or later.

## Setup

Create a publishable token that allows your app. Its origin is `ios-app://`
plus its bundle id:

```bash
vatio tokens create --env live --label ios --origin ios-app://com.acme.app
```

To add it to an existing token instead, use `vatio tokens origins PREFIX URL...`
or the tokens section of **Channels → Web widget** in the inbox.

Add the package `https://github.com/vatio-ai/ios` in Xcode (File → Add
Package Dependencies…), then create a client with the `vatpub_` token. It is
meant to ship inside the app; a `vat_` token must never.

```swift
import Vatio

let vatio = Vatio(workspace: "acme", token: "vatpub_REPLACE_ME")
let chat = VatioChat(vatio)
```

## Using it

```swift
chat.messages        // [VatioMessage], oldest first: id, role, content, createdAt, isPending
chat.isTyping        // the agent is writing
chat.status          // .idle, .connecting, .connected, .reconnecting, .closed

try await chat.send("What does Acme sell?")
```

`VatioChat` is an `ObservableObject`, so a SwiftUI view that observes it
redraws as the reply arrives. Creating one opens nothing: the conversation
starts on the first `send`, and the visitor's message is in `messages` at once,
with `isPending` set until the server confirms it. Call `await chat.resume()`
when the view appears to reopen the visitor's conversation, if they have one.

A common layout is a text bar at the bottom of a screen that, on submit,
switches to a conversation tab and sends:

```swift
AskBar { question in
    tab = .conversation
    Task { try? await chat.send(question) }
}
```

The [package README](https://github.com/vatio-ai/ios#readme) has the
complete SwiftUI example.

| Method | Purpose |
|---|---|
| `vatio.config()` | Agent name, avatar, accent color, greeting and suggestions |
| `vatio.conversations(visitorToken:)` | This visitor's conversations on this device, newest first |
| `chat.send(_:attachments:)` | Send a message, optionally with files; starts the conversation on first call |
| `chat.resume()` | Reopen the stored conversation; never starts a new one |
| `chat.open(_:)` | Switch to a conversation from `conversations()` |
| `chat.startOver()` | Forget the stored conversation; server history is kept |
| `chat.rate(_:comment:)` | Answer the feedback moment: `.good`, `.neutral`, `.bad` |
| `chat.dismissFeedback()` | Decline the feedback moment |
| `chat.close()` | Disconnect |

`send` throws a `VatioError` (`code`, `message`, `status`) and removes the
pending message, so put the text back in your field. Failures outside a
`send` land in `chat.lastError`.

## Files and voice notes

Attach up to four `VatioUpload`s — images, PDFs, text files or audio, up to
8 MB each, as on the [web](https://vatio.ai/docs/sdk/reference#files-and-voice-notes). With files,
the text may be empty:

```swift
try await chat.send(attachments: [VatioUpload(data: jpeg, filename: "photo.jpg", contentType: "image/jpeg")])
try await chat.send(attachments: [try VatioUpload(fileURL: recordingURL)])  // an AVAudioRecorder .m4a
```

Once the server answers, the pending message is replaced by the stored one,
and each `VatioAttachment` has a `url`. A voice note is transcribed in the
background: the message reads "Audio" at first, and its `content` becomes the
transcript a few seconds later, in place; the agent answers after that. When `isMediaLabel` is true, `content` is only a
stand-in ("Image") — draw the attachment instead. The example app has a
composer with a photo picker and a voice recorder.

## Visitor feedback

When a request looks resolved, Vatio offers a feedback moment, at most once
per conversation (see [the widget guide](https://vatio.ai/docs/channels/widget#visitor-feedback)).
`chat.feedback` holds it; show your own prompt while `isOpen`:

```swift
if let feedback = chat.feedback, feedback.isOpen {
    RatingCard(question: feedback.question ?? "How did \(feedback.agentName) do?") { rating, comment in
        Task { try await chat.rate(rating, comment: comment) }
    }
}
```

`question` is how the agent asks, written for that conversation, or `nil`
when there is none. It goes back to `nil` when the visitor writes again
without answering.

## Signed-in users

Pass a visitor token your backend signed to say who the user is, as described
in [Sessions and channels](https://vatio.ai/docs/authentication/sessions):

```swift
let chat = VatioChat(vatio, visitorToken: signedToken)
```

A different token subject is a different person with their own conversation,
so signing out of your app signs out of the chat. An anonymous conversation is
kept when the user signs in.

## Delivery and storage

Replies arrive over a WebSocket. When it drops, the SDK reconnects, polls in
the meantime, and fetches what it missed; messages are deduplicated by id.
`replyStyle` (`.stream` by default, `.paced`, `.instant`) works as on the
[web](https://vatio.ai/docs/sdk/limits#reply-style).

The conversation and the visitor id are stored in `UserDefaults`, per
workspace and signed-in user. Chat credentials last 12 hours. Rate limits
and message length are the [same as on the web](https://vatio.ai/docs/sdk/limits).
