Skip to content

Ejemplos

Dos aplicaciones completas en el monorepo (apps/examples/), cada una dependiendo únicamente de @nykk/ur-agent-client (nada de @nykk/ur-admin-ui ni de ningún otro paquete interno de ur). Ambas usan createChatStore — la diferencia entre ellas es deliberadamente solo de estilo, para mostrar que el SDK no impone nada en ese eje.

Antes de arrancar cualquiera de los dos

Necesitas el identifier (o id) de un agente real con el canal js_sdk habilitado — lo está por defecto al crear el agente (ver el quickstart si no tienes uno a mano). Pega esos dos valores en la config del proyecto que vayas a probar:

json
{
  "agent": "<identifier-o-id-del-agente>",
  "apiBase": "<origen-de-tu-backend-ur, o undefined para 'mismo origen'>"
}
  • Angular → src/app/agent-chat.config.ts
  • React → src/app/agent-chat-config.ts

Angular

apps/examples/angular-chat — Tailwind + DaisyUI (mismo look que el panel de ur, con las clases chat/chat-bubble que DaisyUI ya trae pensadas para esto).

bash
pnpm start:example-angular-chat   # http://localhost:4201

Todo el "pegamento" con el SDK vive en agent-chat.service.ts: crea el UrAgentClient con la config de arriba, lo envuelve con createChatStore, y refleja store.getState() en un signal dentro de store.subscribe(...) — el resto de la app solo lee ese signal.

ts
@Injectable({ providedIn: 'root' })
export class AgentChatService implements OnDestroy {
  private readonly client = new UrAgentClient({
    agent: AGENT_CHAT_CONFIG.agent,
    apiBase: AGENT_CHAT_CONFIG.apiBase,
  });
  private readonly store = createChatStore(this.client);
  private readonly unsubscribe: () => void;

  readonly state = signal<UrAgentChatState>(this.store.getState());

  constructor() {
    this.unsubscribe = this.store.subscribe(() => this.state.set(this.store.getState()));
    // Un subject nuevo no tiene hilo previo — nada que restaurar.
    if (!this.client.isNewSubject) {
      void this.store.loadHistory();
    }
  }

  send(text: string): void {
    void this.store.send(text);
  }

  reset(): void {
    this.store.reset();
  }

  ngOnDestroy(): void {
    this.unsubscribe();
    this.client.abort();
  }
}

app.ts solo inyecta este servicio, lee chat.state() en el template y llama a chat.send(...) / chat.reset() — no conoce el streaming ni el protocolo por debajo.

React

apps/examples/react-chat — CSS plano a mano, sin ningún framework de estilos, a propósito, como contraste con el ejemplo Angular.

bash
pnpm start:example-react-chat   # http://localhost:4202

Mismo patrón que el servicio Angular, encapsulado en el hook useUrAgentChat, pero el store se conecta a React vía useSyncExternalStore(store.subscribe, store.getState) — sin adaptador intermedio, es exactamente la forma que ese hook ya espera:

ts
export function useUrAgentChat() {
  // Lazy initializer: se ejecuta una sola vez por instancia montada del hook.
  const [{ client, store }] = useState(() => {
    const client = new UrAgentClient({
      agent: AGENT_CHAT_CONFIG.agent,
      apiBase: AGENT_CHAT_CONFIG.apiBase,
    });
    return { client, store: createChatStore(client) };
  });

  const state = useSyncExternalStore(store.subscribe, store.getState);

  useEffect(() => {
    if (!client.isNewSubject) {
      void store.loadHistory();
    }
    return () => client.abort();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const send = useCallback((text: string) => void store.send(text), [store]);
  const reset = useCallback(() => store.reset(), [store]);

  return { state, send, reset } as const;
}

app.tsx + chat.css son el render: leen state y llaman a send/reset, igual que en Angular — la única diferencia real entre los dos ejemplos es esta capa de adaptación al framework.

Otros frameworks

El mismo patrón para Vue, Svelte y vanilla está en Integrar con un agente de IA.