Graine AI

Chat

The chat object on every platform — its transcript, connection state, typing indicator and upcoming calls; open, close, send, retry and clear; earlier messages, a new conversation and the window size; the chat views and the surface switch — with what each returns, threading, and how sending, reconnecting and offline messages behave.

What a customer sees and how the chat and the call take turns are on Chat. Each SDK's bar already draws the chat; this page is for reading it, driving it, or drawing your own.

The chat object

useGraineAgent(): {
  messages: Turn[]; send: (text: string) => void; connected: boolean; connecting: boolean;
  reconnecting: boolean; failed: boolean; retry: () => void; clearChat: () => void;
  typing: boolean; error: string | null; open: boolean; setOpen: (open: boolean) => void;
  chatSize: ChatSize; openChat: (options?: { size?: ChatSize }) => void; newConversation: () => Promise<void>;
  widgets: GraineWidget[]; client: GraineInAppClient; /* and the call's state: muted, agentSpeaking, caption… */
}
 
client.sendText(text: string): { id: string; queued: boolean }
client.queuedTexts: Array<{ id: string; text: string }>
client.openChat(options?: { size?: ChatSize }): void   // move to the chat and open it; ChatSize: "compact" | "large" | "full"
client.newConversation(): Promise<void>
client.conversation: "chat" | "call" | null            // the conversation this client holds now
client.chatFailed: boolean                             // the chat gave up; heard as "chat_failed"
client.retry(): Promise<void>                          // connect again now, the backoff starting over
client.clearChat(): void                               // empty the chat on screen; heard as "chat_cleared" (reason "app")
 
const { messages, send, typing, connected } = useGraineAgent();
send("Where is my order?");

Members

WhatReact NativeiOSAndroidFlutterKotlin Multiplatform
The transcript, oldest firstmessages (Turn[])turnsmessagesturns (messages too)turns
Connectedconnectedonline, statusconnected, onlineconnectedstatus
Openingconnectingstatus.isConnectingconnectingconnectingstatus
Reconnectingreconnectingstatus is reconnecting(attempt:)reconnecting (with connecting)reconnecting (with connecting)status
Gave upfailed, client.chatFailedstatus is offlinefailedfailed, errorstatus
The agent is typingtypingtypingtypingagentTypingtyping
Openopen, setOpenisOpen, open(size:), close()open(size), connect(), close(), shown(visible)isOpen, open(), close()open(size), close()
Open at a sizeclient.openChat({ size }); chatSize is the size in forceopen(size:); openedSizeopen(size); openedSizeGraineChat.open(context, size:)open(size)
Sendsend(text), client.sendText(text)send(_:)send(text)send(text)send(text)
Try again after it gave upretry(), client.retry(), or the next sendretry()connect(), or the next sendopen(), or the next sendretry()
Empty the transcriptclearChat(), client.clearChat()clear()clear()clear()clear()
Answer a componentuseGraineWidget().submit, client.submitWidget(widgetId, data, summary)the component view; answerComponent(widgetId:data:summary:) on the instancethe component view; answerWidget(widgetId, data, summary) on the instanceanswerWidget(id, data, summary)answerWidget(id, data, summary)
Upcoming callsuseGraineSchedulingupcomingCallsupcomingCallsupcomingCallsupcomingCalls, refreshUpcomingCalls(), cancelUpcomingCall(id)
Start a new conversationclient.newConversation()newConversation()newConversation(done)newConversation()newConversation()
Lay out the rows a window drawschatTurnRows(messages) (chatRows(items, read) for items of your own)GraineChatRows.build(turns)ChatRows.build(messages)ChatRows.build(turns, now:)GraineChatRows.build(turns)

The React Native client also answers, at any moment: isConnected, isMuted, isAgentSpeaking (booleans), connectionQuality (unknown outside a call) and networkState (online, offline or unknown) — for code outside React's render, where the hooks cannot reach.

Every member is read and called on the main thread on iOS and Android; changes arrive through @Published (iOS), change listeners (Android), notifyListeners (Flutter), state flows (Kotlin Multiplatform) and re-renders (React Native).

send

ParameterType
textStringThe customer's message. Empty text is ignored

Returns nothing — except React Native's client.sendText, which returns { id, queued }: queued is true when the chat was not connected.

What happens: the customer's turn appears at once. Sent while connected, it goes straight to the agent (delivery: sent); typed while the chat is down, it waits — Waiting for connection…, delivery: sending — and goes in order as soon as the chat reconnects (React Native keeps up to 20 waiting). If the chat cannot reconnect, the turn is marked failed and retry() tries again; in React Native the turn keeps waiting (pending) and retry() connects the chat again. The typing indicator shows from the send until the agent's first words, a component, the chat closing, or 12 seconds.

{ "id": "t8", "text": "Where is my order?", "queued": true }

Reconnecting

After an unexpected drop, while the customer has the chat open, the SDK reconnects by itself. iOS, Android, Flutter and Kotlin Multiplatform try six times, 1, 2, 4, 8, 15 and 15 seconds apart, then stop at offline until the customer sends a message or your app retries. React Native waits 1, 2, 4, 8, 15, then 30 seconds between tries for as long as the customer is in the chat (reconnectDelaysMs on the client changes the waits). It does not reconnect after the chat was closed on purpose, after Graine refused it, or on a voice call. A reconnect starts a new conversation with the agent; the transcript on screen stays — and for a customer whose conversation continues, the reconnect brings the earlier messages again, which replace those on screen without repeating any.

Android and Flutter say so with reconnecting (iOS reconnecting(attempt:), Kotlin Multiplatform status), true from the drop — or a first try that failed — until the chat is back, gives up or is closed. A chat that gave up says that too: offline (iOS, Kotlin Multiplatform), failed (Android, Flutter) and, in React Native, failed / client.chatFailed — after its first connect failed, or Graine ended or refused the conversation (React Native never gives up on a network drop). retry() — or the next message — connects again.

Earlier messages

For an identified customer with memory on, Graine keeps the last 50 messages with each agent, for 7 days after the latest, and a new chat with that agent starts from them — the next day, after a relaunch, on another of their devices (Memory → Conversations continue).

  • The transcript begins with them, oldest first, followed by anything of the customer's still sending or not sent. They arrive before the agent's first reply, with the time each was said, delivered, never live, and with restored set — restored: true in React Native. Each carries channel: chat, or voice for something said on a call (iOS GraineChatChannel, Android GraineChatTurn.Channel, Flutter GraineChatChannel, Kotlin Multiplatform GraineChatChannel, React Native channel?: "voice").
  • The chat views draw a chat message as a bubble, a run of messages from a voice call as one row — Voice call · 6:40 pm — and a date line (Today, Yesterday, or the date) wherever the day changes. The window opens on the conversation, not on the starters.
  • No second welcome. The agent continues the conversation; the customer types next.
  • Emptied when forgetMemory() succeeds — a failed one leaves them on screen — on newConversation(), and, as before, by identify(null) or another customer signing in.

Anonymous customers, customers who turned memory off, and agents without memory start every chat empty. An SDK before 0.7.0 does not show the earlier messages; the agent still continues from them.

newConversation

Starts the chat over with this agent: a fresh thread.

client.newConversation(): Promise<void>

Returns nothing: React Native and the website resolve, iOS and Kotlin Multiplatform return, Flutter completes, and Android calls done(Unit, null) on the main thread.

What happens: the agent's chat connection ends; Graine drops the conversation it kept for this agent and the identified customer; the transcript clears, with anything still waiting to go; and, when the customer was in the chat, a fresh chat opens — the welcome message and the starters return. Memory stays: the agent's memories of the customer are untouched (forgetMemory() forgets them), and so are other agents' conversations. For an anonymous customer nothing was kept: it only clears the transcript, and cannot fail.

Errors the same as forgetMemory(): device_limit, unavailable, network, rate_limited, invalid_key, origin_refused — iOS throws GraineServiceError, Flutter and Kotlin Multiplatform GraineException, React Native and the website reject, and Android's done hears the GraineError. Nothing changes then: the chat is as it was, connected again if it was open.

The chat window does the same from its header: a New conversation control, a compose icon beside close, shown only while the window holds earlier messages (restored turns). It does not ask first — Call History keeps the conversation.

clear

Empties the chat on screen — React Native clearChat() (also client.clearChat(), heard as chat_cleared with reason app), iOS, Android, Flutter and Kotlin Multiplatform clear().

Only the screen. The conversation goes on — connected stays connected — a message still waiting for the connection is still sent, and nothing Graine keeps is touched: the conversation that continues comes back with the next history, and the agent's memories stay. newConversation() starts over; forgetMemory() forgets.

Views

GraineVoiceBar draws the chat window when the agent offers the chat, and the switch when it offers both — at the window's size (safeArea gives it the phone's insets for a sheet or a full screen). GraineAgentBar and GraineFab draw it too, with components.

GraineChatScreen(props: { onClose?: () => void; safeArea?: { top?: number; bottom?: number } })

The chat as a screen of your own app. While it is on screen the customer is in the chat and the bar steps aside; leaving puts back what was open. Without onClose, no chevron is drawn.

Multi-agent API: with several agents, each view takes the agent it draws — iOS GraineChatView(graine: GraineInApp.named("support")), Android chatView.source = GraineInApp.get("support").chat, Flutter GraineChat(app: GraineInApp.named('support')), Kotlin Multiplatform GraineChat(agent = "support"); in React Native, the nearest provider's. Without one, a view draws the default agent.

Window size

How big the chat opens: the design's Chat window → Window size in the Garage (chat.size in the design: compact, large or full; anything else is compact). Your app can choose otherwise, and one open's size wins over the app's, which wins over the design's:

PlatformFor every chatFor one open
React NativechatSize on GraineProvider (or the client's options)client.openChat({ size })
iOSGraineConfig(chatSize: .large)GraineInApp.shared.chat.open(size: .full)
AndroidGraineConfig(chatSize = GraineChatSize.LARGE)GraineInApp.get().chat.open(GraineChatSize.FULL)
FlutterGraineConfig(chatSize: GraineChatSize.large)GraineChat.open(context, size: GraineChatSize.full)
Kotlin MultiplatformGraineConfig(chatSize = GraineChatSize.LARGE)Graine.chat.open(GraineChatSize.FULL)
WebsiteGraineAgent.load({ chatSize })GraineAgent.open({ size })

A size passed to an open lasts until the chat closes, and moving to the call and back keeps it. With the bar on screen, the open moves it to the chat at that size. What each size looks like on a phone, a tablet and a computer: Chat → Window size. The type on each platform: Types → GraineChatSize.

Which surface is open

React Native: useGraineSurface(): { surface, surfaces, canSwitch, setSurface }, with surface voice or chat, and the provider's surface and onSurfaceChange props. iOS: GraineSurface on GraineAgentBar. The dashboard's What visitors get decides what can be chosen; an app can choose among those, never add one.

The transcript

[
  { "id": "t1", "role": "agent", "text": "Hi Rohan — how can I help?", "at": "2026-10-13T16:40:02Z", "proactive": false, "live": false },
  { "id": "t2", "role": "customer", "text": "Where is my order?", "at": "2026-10-13T16:40:11Z", "delivery": "sent" },
  { "id": "t3", "role": "agent", "text": "It left the warehouse this morning and arrives tomorrow.", "at": "2026-10-13T16:40:13Z", "proactive": false, "live": false }
]

An earlier message, put back when the conversation continued:

{ "id": "a1b2c3d4e5f6", "role": "customer", "text": "Is my order shipped?", "at": "2026-10-13T09:30:00Z", "delivery": "sent", "restored": true, "channel": "chat" }

Fields: GraineChatTurn. The customer's role is customer (user in Flutter and Kotlin Multiplatform).

On this page