# @yak-io/angular

> 📚 **Full documentation:** https://docs.yak.io/docs/sdks/angular
>
> 🤖 **For LLMs / AI agents:** https://docs.yak.io/llms.txt

Angular SDK for [Yak](https://docs.yak.io) — an embeddable AI assistant (text chat **and** push-to-talk voice) for web apps. `createYakProvider` returns a `YakApi`; bridge its state into Angular signals (or RxJS) with `subscribeToState`.

```bash
pnpm add @yak-io/angular
```

## Exports

| Export | Kind | Purpose |
| --- | --- | --- |
| `createYakProvider(options)` | fn | Create a widget instance. Returns a `YakApi` with `mount`/`destroy` + `subscribeToState`. |
| `createYakToolset` / `createYakServerAdapter` | fn | Compose GraphQL/REST/tRPC/custom tool adapters into one `onToolCall` (re-exported from `@yak-io/javascript`). |
| `enableYakLogging` / `disableYakLogging` / `isYakLoggingEnabled` | fn | Toggle verbose SDK logging. |
| Types | — | `YakProviderOptions`, `YakApi`, `YakState`, `ToolCallEventHandler`, plus core types from `@yak-io/javascript`. |

## Quickstart

### 1. Initialize in your root component

```ts
// app.component.ts
import { Component, OnInit, OnDestroy, inject } from "@angular/core";
import { Router } from "@angular/router";
import { createYakProvider, type YakApi } from "@yak-io/angular";
import { environment } from "../environments/environment";

@Component({
  selector: "app-root",
  standalone: true,
  template: "<router-outlet />",
})
export class AppComponent implements OnInit, OnDestroy {
  private router = inject(Router);
  private yak: YakApi = createYakProvider({
    appId: environment.yakAppId,
    mode: "both",   // "chat" | "voice" | "both" — default "chat"
    trigger: true,  // show the floating launcher pill
    theme: { position: "bottom-right", colorMode: "system" },
    getConfig: async () => {
      const res = await fetch("/api/yak");
      return res.json();
    },
    onToolCall: async (name, args) => {
      const res = await fetch("/api/yak", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, args }),
      });
      const data = await res.json();
      if (!data.ok) throw new Error(data.error);
      return data.result;
    },
    onRedirect: (path) => this.router.navigateByUrl(path),
  });

  ngOnInit() {
    this.yak.mount();
  }

  ngOnDestroy() {
    this.yak.destroy();
  }
}
```

### 2. Share via a service

```ts
// yak.service.ts
import { Injectable, signal } from "@angular/core";
import type { YakApi } from "@yak-io/angular";

@Injectable({ providedIn: "root" })
export class YakService {
  private yak: YakApi | null = null;
  readonly isOpen = signal(false);

  register(yak: YakApi) {
    this.yak = yak;
    yak.subscribeToState((state) => this.isOpen.set(state.isOpen));
  }

  open() { this.yak?.open(); }
  openWithPrompt(prompt: string) { this.yak?.openWithPrompt(prompt); }
  voiceToggle() { return this.yak?.voiceToggle(); }
}
```

Call `yakService.register(this.yak)` from your root component, then inject `YakService` anywhere:

```ts
@Component({
  template: `<button (click)="yak.open()">Open chat</button>
             <span *ngIf="yak.isOpen()">Chat is open</span>`,
})
export class HeaderComponent {
  yak = inject(YakService);
}
```

## Voice

Set `mode: "voice"` or `mode: "both"`, then drive the session. `voiceStart()` must run from a user gesture (browser mic requirement). Voice state arrives through `subscribeToState` as `state.voiceMachine`:

```ts
yak.subscribeToState((state) => {
  this.voiceState.set(state.voiceMachine.state); // "idle" | "listening" | "speaking" | ...
});
// button handler:
this.yak.voiceToggle();
```

## Tool events

```ts
yak.subscribeToToolEvents((event) => {
  // { name, args, ok, result?, error? }
  if (event.ok && event.name.startsWith("tasks.")) this.refreshTasks();
});
```

## API reference

### `createYakProvider(options)`

Returns a `YakApi`. Call `yak.mount()` in `ngOnInit()` and `yak.destroy()` in `ngOnDestroy()`.

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `appId` | `string` | — | Your Yak app ID (required). |
| `mode` | `"chat" \| "voice" \| "both"` | `"chat"` | Which surfaces the widget exposes. |
| `trigger` | `boolean \| TriggerButtonConfig` | `false` | Show the floating pill. **Set `true`** to display it; `TriggerButtonConfig` recolors it. |
| `getConfig` | `ChatConfigProvider` | — | Async provider of routes + tools. Called on open / voice start. |
| `onToolCall` | `ToolCallHandler` | — | Executes a tool the assistant calls. || `theme` | `Theme` | — | Position, color mode, and colors. |
| `onRedirect` | `(path: string) => void` | `window.location.assign` | Navigation handler. |
| `disableRestartButton` | `boolean` | `false` | Hide the restart-session button. |
| `user` | `{ id, hash }` | — | Signed end-user identity for conversation persistence + history. Call `setUser()` to change it after setup (login/logout). |

### `YakApi`

State (`isOpen`/`isReady`/`voiceMachine`) is read via the `subscribeToState` callback — Angular has no built-in reactive primitive the SDK can return, so wire it into signals or RxJS yourself.

```ts
type YakState = {
  isOpen: boolean;
  isReady: boolean;
  chatLoading: boolean;
  voiceMachine: VoiceMachine;
  voiceLoading: boolean;
};

type YakApi = {
  readonly isOpen: boolean;        // snapshot — use subscribeToState for reactivity
  readonly isReady: boolean;       // snapshot
  readonly chatLoading: boolean;   // snapshot — isOpen && !isReady
  readonly voiceMachine: VoiceMachine; // snapshot — { state, errorMessage? }
  readonly voiceLoading: boolean;  // snapshot — state === "connecting"
  open: () => void;
  close: () => void;
  openWithPrompt: (prompt: string) => void;
  voiceStart: () => Promise<void>;
  voiceStop: () => Promise<void>;
  voiceToggle: () => Promise<void>;
  setUser: (user?: UserIdentity) => void; // identity — call on login/logout
  subscribeToToolEvents: (handler: ToolCallEventHandler) => () => void;
  subscribeToState: (handler: (state: YakState) => void) => () => void;
  mount: () => void;    // call in ngOnInit()
  destroy: () => void;  // call in ngOnDestroy()
};
```

## Logging

```ts
import { enableYakLogging, disableYakLogging, isYakLoggingEnabled } from "@yak-io/angular";

enableYakLogging(); // verbose SDK logs
```

## Types

```ts
import type {
  YakProviderOptions,
  YakApi,
  YakState,
  ToolCallEventHandler,
  ChatConfigProvider,
  ToolCallHandler,
  ToolCallEvent,
  ToolAdapter,
  YakToolset,
  YakServerAdapterConfig,
  Theme,
  WidgetMode,
  WidgetPosition,
  VoiceState,
  VoiceMachine,
} from "@yak-io/angular";
```

## License

Proprietary — see [LICENSE](./LICENSE).
