@memberjunction/ng-conversations 5.41.0 → 5.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/__tests__/remote-browser-channel.test.js +163 -1
  2. package/dist/__tests__/remote-browser-channel.test.js.map +1 -1
  3. package/dist/lib/components/collection/collections-full-view.component.d.ts +105 -2
  4. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  5. package/dist/lib/components/collection/collections-full-view.component.js +1378 -489
  6. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  7. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +4 -0
  8. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  9. package/dist/lib/components/conversation/conversation-chat-area.component.js +13 -8
  10. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  11. package/dist/lib/components/conversation/conversation-list.component.d.ts +98 -5
  12. package/dist/lib/components/conversation/conversation-list.component.d.ts.map +1 -1
  13. package/dist/lib/components/conversation/conversation-list.component.js +1102 -401
  14. package/dist/lib/components/conversation/conversation-list.component.js.map +1 -1
  15. package/dist/lib/components/message/message-input.component.js +1 -1
  16. package/dist/lib/components/message/message-input.component.js.map +1 -1
  17. package/dist/lib/components/project/project-form-modal.component.d.ts +5 -1
  18. package/dist/lib/components/project/project-form-modal.component.d.ts.map +1 -1
  19. package/dist/lib/components/project/project-form-modal.component.js +104 -85
  20. package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
  21. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +22 -0
  22. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
  23. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +40 -0
  24. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
  25. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -1
  26. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
  27. package/dist/lib/components/realtime/realtime-agent-picker.component.js +97 -19
  28. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
  29. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +33 -0
  30. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  31. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +164 -7
  32. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  33. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +35 -2
  34. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -1
  35. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +67 -4
  36. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -1
  37. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +1 -0
  38. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
  39. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +13 -0
  40. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
  41. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +4 -0
  42. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  43. package/dist/lib/components/workspace/conversation-workspace.component.js +13 -8
  44. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  45. package/dist/lib/services/artifact-state.service.d.ts +1 -1
  46. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  47. package/dist/lib/services/artifact-state.service.js +7 -2
  48. package/dist/lib/services/artifact-state.service.js.map +1 -1
  49. package/dist/lib/services/realtime-pairing.d.ts +8 -7
  50. package/dist/lib/services/realtime-pairing.d.ts.map +1 -1
  51. package/dist/lib/services/realtime-pairing.js +19 -9
  52. package/dist/lib/services/realtime-pairing.js.map +1 -1
  53. package/package.json +26 -26
@@ -1 +1 @@
1
- {"version":3,"file":"base-realtime-channel-client.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/channels/base-realtime-channel-client.ts"],"names":[],"mappings":"AAmHA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,OAAgB,yBAAyB;IAC7C;;;OAGG;IACO,OAAO,GAAkC,IAAI,CAAC;IAwCxD;;;;;;;;;;;;;OAaG;IACI,mBAAmB;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACI,UAAU;QACf,OAAO,IAAI,CAAC,mBAAmB,EAAE,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;OAUG;IACI,oBAAoB;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAYD;;;;;OAKG;IACI,aAAa;QAClB,8BAA8B;IAChC,CAAC;IAED;;;;OAIG;IACI,UAAU,CAAC,GAA2B;QAC3C,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACnB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACO,YAAY;QACpB,iCAAiC;IACnC,CAAC;IAED;;;;OAIG;IACI,cAAc;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;OAUG;IACI,YAAY,CAAC,SAAiB;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACI,gBAAgB;QACrB,oDAAoD;IACtD,CAAC;IAED;;;;;OAKG;IACI,OAAO;QACZ,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;CACF","sourcesContent":["import type { Type } from '@angular/core';\nimport { JSONValue, RealtimeToolDefinition } from '@memberjunction/ai';\n\n/**\n * Host services handed to a {@link BaseRealtimeChannelClient} at {@link BaseRealtimeChannelClient.Initialize}.\n *\n * The context is the plugin's ONLY line back to the live session — channels never talk to\n * `RealtimeSessionService` (or any host component) directly, which is what keeps them drop-in\n * plugins. Every member is host-implemented:\n *\n * - the SESSION SERVICE supplies {@link SendContextNote} (perception feed into the live\n * model), {@link RequestSave} (debounced state-of-record persistence onto the session's\n * `MJ: AI Agent Session Channels` row) and {@link AgentName};\n * - the OVERLAY SHELL wires {@link SetFocusMode} (through the service's focus stream) so a\n * channel surface can request the focus layout — main call column collapsed, surface\n * panel filling the overlay, floating call pill riding on top.\n */\nexport interface RealtimeChannelContext {\n /** Display name of the agent the live session fronts (e.g. `\"Sage\"`), fixed at session start. */\n AgentName: string;\n\n /**\n * Feeds a background context note into the live realtime model (no spoken reply is\n * requested) — the PERCEPTION direction of the channel: serialized state deltas flow here\n * so the agent stays aware of what's on the surface. No-op when the session isn't live.\n */\n SendContextNote(text: string): void;\n\n /**\n * Asks the host to persist `stateJson` as this channel's state of record. The host\n * DEBOUNCES (a change burst becomes one save) and flushes any pending save at session\n * teardown — the plugin just calls this on every state mutation and never schedules\n * timers itself. Best-effort: persistence failures are logged host-side, never thrown.\n */\n RequestSave(stateJson: string): void;\n\n /**\n * Requests (or releases) the FOCUS layout for this channel's surface: the overlay\n * collapses the main call column so the surface owns the screen, with a compact floating\n * call pill keeping mute / thread / end reachable. Any channel may request it; the host\n * tracks which channel holds focus and routes the pill's \"exit\" back to it via\n * {@link BaseRealtimeChannelClient.RequestFocusExit}.\n */\n SetFocusMode(on: boolean): void;\n\n /**\n * Asks the live model to SPEAK a response to the supplied instructions RIGHT NOW —\n * the channel's \"react to this\" path, e.g. a widget submission the user expects an\n * audible reaction to ({@link SendContextNote} deliberately never triggers speech).\n * Rides the realtime client's spoken-update channel, so on some providers the spoken\n * reply is narration-kind (ephemeral, not persisted as a caption). OPTIONAL member:\n * older host contexts may not supply it — plugins must call it null-safely.\n */\n RequestSpokenResponse?(instructions: string): void;\n\n /**\n * Persists a snapshot of the channel's state as a first-class versioned artifact\n * (`MJ: Artifacts` + version, linked into conversation history when possible) — e.g. the\n * whiteboard's \"Save to artifacts\". Distinct from {@link RealtimeChannelContext.RequestSave},\n * which maintains the session's rolling state of record. Best-effort: resolves to the\n * created Artifact ID, or `null` on failure (logged host-side, never thrown). Works during\n * the call AND right after it ends (the host retains the session id for late saves).\n */\n SaveAsArtifact(name: string, contentJson: string): Promise<string | null>;\n\n /**\n * The live `MJ: AI Agent Sessions` id this channel belongs to, or `null` before the\n * session has minted / after it has torn down. A channel whose tools or surface drive a\n * SERVER-SIDE resource (e.g. the Remote Browser channel's server-hosted browser) passes\n * this as the `agentSessionID` argument to its own GraphQL resolvers via\n * {@link ExecuteServerAction}. Most channels (whiteboard, shared doc) keep all state\n * client-side and never read it.\n */\n AgentSessionID: string | null;\n\n /**\n * Executes a CHANNEL-SPECIFIC GraphQL operation against the session's MJ server — the\n * escape hatch for channels backed by a SERVER-SIDE resource that the generic\n * {@link RequestSave} / {@link SaveAsArtifact} contract doesn't cover (e.g. the Remote\n * Browser channel driving a server-hosted browser through its own\n * `ExecuteRemoteBrowserAction` mutation + `RemoteBrowserSnapshot` query).\n *\n * The host runs the operation through the SAME provider the live session uses, so the\n * request rides the authenticated session. Best-effort and tolerant: a transport or\n * server error resolves to `null` (logged host-side) rather than throwing, so a channel\n * can map the failure to a model-readable result string without `try/catch`.\n *\n * @typeParam TResult The expected shape of the GraphQL operation's data payload.\n * @param query The GraphQL query/mutation document.\n * @param variables The operation variables (all JSON-serializable).\n * @returns The operation's `data` payload, or `null` on any failure / when no session is live.\n */\n ExecuteServerAction<TResult>(query: string, variables: Record<string, JSONValue>): Promise<TResult | null>;\n}\n\n/**\n * The first-run INTRO content for an interactive channel — the concise \"what is this surface\n * and how do I use it\" copy the overlay shows the very first time a user opens this channel's\n * tab (persisted \"seen\" per user, so it's shown ONCE per channel per user).\n *\n * A channel opts in by overriding {@link BaseRealtimeChannelClient.GetOnboardingDetails}; the\n * default returns `null`, so the base Voice/text channel (which has no plugin at all) AND any\n * plugin that doesn't override it show nothing.\n */\nexport interface ChannelOnboardingDetails {\n /** Short title, usually the surface name (e.g. `\"Whiteboard\"`). */\n Heading: string;\n /** One or two sentences: what the surface is and what the user can expect to see on it. */\n Description: string;\n /** Optional quick-tip bullets (kept to 2-3 short, scannable lines). */\n Tips?: string[];\n /** Optional Font Awesome icon class for the intro panel (e.g. `'fa-solid fa-chalkboard'`). */\n IconClass?: string;\n}\n\n/**\n * Base class for CLIENT-SIDE interactive-channel plugins (per\n * `plans/ai-agent-sessions.md` → \"Interactive Channels\" / \"Pluggable Channel Interfaces\").\n *\n * An interactive channel is a bidirectional surface the session's single realtime agent\n * both PERCEIVES and ACTS UPON (whiteboard, shared doc, map, …). A concrete plugin\n * contributes everything the channel needs, so the session service / call overlay carry\n * ZERO channel-specific wiring:\n *\n * 1. a CLIENT-EXECUTED TOOL SET ({@link GetToolDefinitions}, declared to the realtime\n * model at session mint) plus the local executor ({@link ApplyAgentTool}) the host\n * routes `{@link ToolNamePrefix}*` calls to — the ACTION direction;\n * 2. a STATE→CONTEXT SERIALIZER policy — the plugin owns its state engine and pushes\n * coalesced deltas through {@link RealtimeChannelContext.SendContextNote} — the\n * PERCEPTION direction;\n * 3. an OPTIONAL ANGULAR SURFACE ({@link GetSurfaceComponent}) the overlay creates dynamically\n * in a channel tab, handed back through {@link BindSurface} so the plugin wires its own\n * inputs/outputs (the host never knows the component's API). A channel may be **server-only**\n * (no rendered surface) — e.g. a bridge-contributed meeting-controls or native-whiteboard\n * channel whose surface lives on the external platform, not in MJ. Such a channel returns\n * `null` from {@link GetSurfaceComponent} ({@link HasSurface} is `false`) and the overlay\n * simply skips its tab while still wiring its tools + perception;\n * 4. a STATE OF RECORD ({@link SerializeState}, persisted via\n * {@link RealtimeChannelContext.RequestSave} under {@link ChannelName}).\n *\n * ### Registration & resolution (mirrors the realtime model drivers)\n * Concrete plugins are `@RegisterClass(BaseRealtimeChannelClient, '<ClientPluginClass>')`\n * and are resolved at session start from the `MJ: AI Agent Channels` registry: each ACTIVE\n * row's `ClientPluginClass` is the ClassFactory key (exactly how `BaseRealtimeClient`\n * drivers resolve by provider key). Ship a `Load<YourChannel>()` no-op alongside the class\n * and call it from a static code path to defeat tree-shaking.\n *\n * ### Lifecycle — ONE INSTANCE PER SESSION (not a singleton)\n * `ClassFactory.CreateInstance` → {@link Initialize}(ctx) → zero or more\n * {@link BindSurface}/{@link UnbindSurface} cycles (the surface pane is created/destroyed\n * with the overlay's tab panel, e.g. collapse/expand) → {@link Dispose} at teardown.\n * {@link ApplyAgentTool} MUST work with NO surface bound (apply to the state engine\n * directly; skip the UI garnish) — tool calls can arrive while the panel is collapsed.\n *\n * @typeParam TSurface The plugin's Angular surface component type. The host only ever\n * sees the default (`object`) — the typed parameter exists so concrete plugins get a\n * fully typed {@link BindSurface} without casts.\n */\nexport abstract class BaseRealtimeChannelClient<TSurface extends object = object> {\n /**\n * The host context, available from {@link Initialize} until {@link Dispose}.\n * `null` outside that window — guard with `?.` in any code that can run early/late.\n */\n protected Context: RealtimeChannelContext | null = null;\n\n /**\n * The channel definition name — MUST match the `MJ: AI Agent Channels` row's `Name`\n * (e.g. `'Whiteboard'`). Used as the persistence key for {@link SerializeState} saves\n * and as the channel tab's stable key.\n */\n public abstract get ChannelName(): string;\n\n /**\n * The shared name prefix of every tool this channel exposes (e.g. `'Whiteboard_'`).\n * The host registers ONE local-execution route per plugin: tool calls whose name starts\n * with this prefix go to {@link ApplyAgentTool} instead of the server relay.\n */\n public abstract get ToolNamePrefix(): string;\n\n /** Label for the channel's tab on the overlay's surface panel (e.g. `'Whiteboard'`). */\n public abstract get TabTitle(): string;\n\n /** Font Awesome icon class for the channel's tab (e.g. `'fa-solid fa-chalkboard'`). */\n public abstract get TabIcon(): string;\n\n /**\n * The channel's CLIENT-EXECUTED tool declarations, aggregated by the session service\n * into the `clientTools` set declared to the realtime model at session mint. The server\n * only DECLARES these — execution stays in the browser via {@link ApplyAgentTool}.\n */\n public abstract GetToolDefinitions(): RealtimeToolDefinition[];\n\n /**\n * Executes ONE agent tool call locally (the ACTION direction) and returns the result\n * JSON string fed back to the model as the `tool_response`. Called for every tool whose\n * name starts with {@link ToolNamePrefix}. Must work both WITH a bound surface (apply +\n * UI garnish) and WITHOUT one (apply to the state engine directly — the tab pane may not\n * exist, e.g. the surface panel is collapsed). Should not throw: return a\n * `{ success: false, error }` payload so the model can narrate the failure (the host\n * additionally wraps anything thrown).\n */\n public abstract ApplyAgentTool(toolName: string, argsJson: string): string | Promise<string>;\n\n /**\n * The Angular component the overlay creates dynamically as this channel's tab pane, or `null`\n * for a **server-only** channel that renders no MJ surface (its surface, if any, lives on the\n * external platform — e.g. a bridge-contributed native whiteboard or meeting-controls channel).\n *\n * When this returns `null`, the overlay renders NO tab for the channel and never calls\n * {@link BindSurface}/{@link UnbindSurface} — but the channel's tools ({@link GetToolDefinitions} /\n * {@link ApplyAgentTool}) and perception ({@link RealtimeChannelContext.SendContextNote}) still run.\n * A created surface instance is handed straight back via {@link BindSurface}; the host treats it as\n * opaque.\n *\n * Default: `null` (server-only). A channel with a rendered surface overrides this to return its\n * component type.\n */\n public GetSurfaceComponent(): Type<TSurface> | null {\n return null;\n }\n\n /**\n * Whether this channel has a rendered MJ surface ({@link GetSurfaceComponent} returns non-null).\n * The overlay uses this to decide whether to register a surface tab; server-only channels are\n * `false`. Override only if surface availability must be decided WITHOUT constructing the type\n * (the default calls {@link GetSurfaceComponent} once).\n */\n public HasSurface(): boolean {\n return this.GetSurfaceComponent() != null;\n }\n\n /**\n * The channel's FIRST-RUN INTRO content, or `null` when the channel offers no onboarding.\n * The overlay shows this once per channel per user — the first time the user opens this\n * channel's surface tab — and remembers \"seen\" via the user's settings (NOT localStorage),\n * so it never re-appears on later sessions or other devices.\n *\n * Default: `null` (no intro). The base Voice/text channel has no plugin at all, so it never\n * shows an intro; an interactive channel with a surface worth explaining (whiteboard, remote\n * browser, …) overrides this to return its {@link ChannelOnboardingDetails}. A plugin that\n * doesn't override it simply shows nothing — onboarding is strictly opt-in.\n */\n public GetOnboardingDetails(): ChannelOnboardingDetails | null {\n return null;\n }\n\n /**\n * Called by the host right after it created the surface component (and BEFORE the\n * component's first change detection, so inputs set here are visible in its `ngOnInit`).\n * The plugin — which knows its own component type — sets inputs (state engine, agent\n * name, …) and subscribes outputs here, wiring perception/garnish flows back through\n * {@link Context}. May be called again with a NEW instance after an\n * {@link UnbindSurface} (the pane is destroyed/recreated with the tab panel).\n */\n public abstract BindSurface(instance: TSurface): void;\n\n /**\n * Called by the host when the surface component is being destroyed (tab panel\n * collapsed / overlay torn down). Drop the instance reference and unsubscribe any\n * output subscriptions — after this, {@link ApplyAgentTool} runs in its no-surface\n * mode. Default: no-op.\n */\n public UnbindSurface(): void {\n // default: nothing to release\n }\n\n /**\n * Binds the host context and invokes the {@link OnInitialize} hook. Called exactly once\n * per session, right after ClassFactory instantiation and before any tool call or\n * surface bind.\n */\n public Initialize(ctx: RealtimeChannelContext): void {\n this.Context = ctx;\n this.OnInitialize();\n }\n\n /**\n * Subclass hook invoked from {@link Initialize} once {@link Context} is bound — wire\n * state-engine subscriptions (e.g. state change → `Context.RequestSave(...)`) here.\n * Default: no-op.\n */\n protected OnInitialize(): void {\n // default: nothing to initialize\n }\n\n /**\n * Serializes the channel's current state of record (the payload persisted on the\n * session's channel row), or `null` when the channel keeps no persistent state.\n * Default: `null`.\n */\n public SerializeState(): string | null {\n return null;\n }\n\n /**\n * Restores a PRIOR session's saved channel state (the payload a previous session\n * persisted via {@link SerializeState} / {@link RealtimeChannelContext.RequestSave}).\n * Invoked by the session host AFTER {@link Initialize} and BEFORE any surface binding,\n * when a prior session's saved state exists for this channel.\n *\n * Returns `true` when the state was applied; `false` when the channel ignored it —\n * either because it keeps no persistent state (this default) or because the payload was\n * malformed/incompatible. Implementations MUST be tolerant: never throw on bad input,\n * just return `false` and start fresh.\n */\n public RestoreState(stateJson: string): boolean {\n return false;\n }\n\n /**\n * The focus pill's \"exit\" affordance, routed by the overlay to the channel that holds\n * focus. Implementations should leave focus mode through their OWN surface (so surface\n * toggles stay in sync), ultimately emitting `Context.SetFocusMode(false)`. The overlay\n * defensively clears its layout flag as well, so a no-op default is safe.\n */\n public RequestFocusExit(): void {\n // default: the overlay's defensive clear handles it\n }\n\n /**\n * Tears the plugin down at session end: release the surface binding, unsubscribe\n * state-engine subscriptions, then drop the context. Subclasses overriding this MUST\n * call `super.Dispose()`. Any final state save has already been flushed by the host\n * (the debounced {@link RealtimeChannelContext.RequestSave} pipeline) before disposal.\n */\n public Dispose(): void {\n this.UnbindSurface();\n this.Context = null;\n }\n}\n"]}
1
+ {"version":3,"file":"base-realtime-channel-client.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/channels/base-realtime-channel-client.ts"],"names":[],"mappings":"AAmHA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,OAAgB,yBAAyB;IAC7C;;;OAGG;IACO,OAAO,GAAkC,IAAI,CAAC;IAwCxD;;;;;;;;;;;;;OAaG;IACI,mBAAmB;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACI,UAAU;QACf,OAAO,IAAI,CAAC,mBAAmB,EAAE,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;OAUG;IACI,oBAAoB;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAYD;;;;;OAKG;IACI,aAAa;QAClB,8BAA8B;IAChC,CAAC;IAED;;;;OAIG;IACI,UAAU,CAAC,GAA2B;QAC3C,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACnB,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACO,YAAY;QACpB,iCAAiC;IACnC,CAAC;IAED;;;;OAIG;IACO,sBAAsB,GAAG,IAAI,CAAC;IAC9B,uBAAuB,GAAG,GAAG,CAAC;IAExC;;;;;;;;;;;;;OAaG;IACO,KAAK,CAAC,qBAAqB;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;QACvD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAC7D,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,sBAAsB,EAAE,MAAM,IAAI,UAAU,EAAE,CAAC;YAChF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YACtE,sEAAsE;YACtE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC;YACvC,IAAI,EAAE,EAAE,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,EAAE,cAAc,IAAI,IAAI,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACI,cAAc;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;OAUG;IACI,YAAY,CAAC,SAAiB;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACI,gBAAgB;QACrB,oDAAoD;IACtD,CAAC;IAED;;;;;OAKG;IACI,OAAO;QACZ,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;CACF","sourcesContent":["import type { Type } from '@angular/core';\nimport { JSONValue, RealtimeToolDefinition } from '@memberjunction/ai';\n\n/**\n * Host services handed to a {@link BaseRealtimeChannelClient} at {@link BaseRealtimeChannelClient.Initialize}.\n *\n * The context is the plugin's ONLY line back to the live session — channels never talk to\n * `RealtimeSessionService` (or any host component) directly, which is what keeps them drop-in\n * plugins. Every member is host-implemented:\n *\n * - the SESSION SERVICE supplies {@link SendContextNote} (perception feed into the live\n * model), {@link RequestSave} (debounced state-of-record persistence onto the session's\n * `MJ: AI Agent Session Channels` row) and {@link AgentName};\n * - the OVERLAY SHELL wires {@link SetFocusMode} (through the service's focus stream) so a\n * channel surface can request the focus layout — main call column collapsed, surface\n * panel filling the overlay, floating call pill riding on top.\n */\nexport interface RealtimeChannelContext {\n /** Display name of the agent the live session fronts (e.g. `\"Sage\"`), fixed at session start. */\n AgentName: string;\n\n /**\n * Feeds a background context note into the live realtime model (no spoken reply is\n * requested) — the PERCEPTION direction of the channel: serialized state deltas flow here\n * so the agent stays aware of what's on the surface. No-op when the session isn't live.\n */\n SendContextNote(text: string): void;\n\n /**\n * Asks the host to persist `stateJson` as this channel's state of record. The host\n * DEBOUNCES (a change burst becomes one save) and flushes any pending save at session\n * teardown — the plugin just calls this on every state mutation and never schedules\n * timers itself. Best-effort: persistence failures are logged host-side, never thrown.\n */\n RequestSave(stateJson: string): void;\n\n /**\n * Requests (or releases) the FOCUS layout for this channel's surface: the overlay\n * collapses the main call column so the surface owns the screen, with a compact floating\n * call pill keeping mute / thread / end reachable. Any channel may request it; the host\n * tracks which channel holds focus and routes the pill's \"exit\" back to it via\n * {@link BaseRealtimeChannelClient.RequestFocusExit}.\n */\n SetFocusMode(on: boolean): void;\n\n /**\n * Asks the live model to SPEAK a response to the supplied instructions RIGHT NOW —\n * the channel's \"react to this\" path, e.g. a widget submission the user expects an\n * audible reaction to ({@link SendContextNote} deliberately never triggers speech).\n * Rides the realtime client's spoken-update channel, so on some providers the spoken\n * reply is narration-kind (ephemeral, not persisted as a caption). OPTIONAL member:\n * older host contexts may not supply it — plugins must call it null-safely.\n */\n RequestSpokenResponse?(instructions: string): void;\n\n /**\n * Persists a snapshot of the channel's state as a first-class versioned artifact\n * (`MJ: Artifacts` + version, linked into conversation history when possible) — e.g. the\n * whiteboard's \"Save to artifacts\". Distinct from {@link RealtimeChannelContext.RequestSave},\n * which maintains the session's rolling state of record. Best-effort: resolves to the\n * created Artifact ID, or `null` on failure (logged host-side, never thrown). Works during\n * the call AND right after it ends (the host retains the session id for late saves).\n */\n SaveAsArtifact(name: string, contentJson: string): Promise<string | null>;\n\n /**\n * The live `MJ: AI Agent Sessions` id this channel belongs to, or `null` before the\n * session has minted / after it has torn down. A channel whose tools or surface drive a\n * SERVER-SIDE resource (e.g. the Remote Browser channel's server-hosted browser) passes\n * this as the `agentSessionID` argument to its own GraphQL resolvers via\n * {@link ExecuteServerAction}. Most channels (whiteboard, shared doc) keep all state\n * client-side and never read it.\n */\n AgentSessionID: string | null;\n\n /**\n * Executes a CHANNEL-SPECIFIC GraphQL operation against the session's MJ server — the\n * escape hatch for channels backed by a SERVER-SIDE resource that the generic\n * {@link RequestSave} / {@link SaveAsArtifact} contract doesn't cover (e.g. the Remote\n * Browser channel driving a server-hosted browser through its own\n * `ExecuteRemoteBrowserAction` mutation + `RemoteBrowserSnapshot` query).\n *\n * The host runs the operation through the SAME provider the live session uses, so the\n * request rides the authenticated session. Best-effort and tolerant: a transport or\n * server error resolves to `null` (logged host-side) rather than throwing, so a channel\n * can map the failure to a model-readable result string without `try/catch`.\n *\n * @typeParam TResult The expected shape of the GraphQL operation's data payload.\n * @param query The GraphQL query/mutation document.\n * @param variables The operation variables (all JSON-serializable).\n * @returns The operation's `data` payload, or `null` on any failure / when no session is live.\n */\n ExecuteServerAction<TResult>(query: string, variables: Record<string, JSONValue>): Promise<TResult | null>;\n}\n\n/**\n * The first-run INTRO content for an interactive channel — the concise \"what is this surface\n * and how do I use it\" copy the overlay shows the very first time a user opens this channel's\n * tab (persisted \"seen\" per user, so it's shown ONCE per channel per user).\n *\n * A channel opts in by overriding {@link BaseRealtimeChannelClient.GetOnboardingDetails}; the\n * default returns `null`, so the base Voice/text channel (which has no plugin at all) AND any\n * plugin that doesn't override it show nothing.\n */\nexport interface ChannelOnboardingDetails {\n /** Short title, usually the surface name (e.g. `\"Whiteboard\"`). */\n Heading: string;\n /** One or two sentences: what the surface is and what the user can expect to see on it. */\n Description: string;\n /** Optional quick-tip bullets (kept to 2-3 short, scannable lines). */\n Tips?: string[];\n /** Optional Font Awesome icon class for the intro panel (e.g. `'fa-solid fa-chalkboard'`). */\n IconClass?: string;\n}\n\n/**\n * Base class for CLIENT-SIDE interactive-channel plugins (per\n * `plans/ai-agent-sessions.md` → \"Interactive Channels\" / \"Pluggable Channel Interfaces\").\n *\n * An interactive channel is a bidirectional surface the session's single realtime agent\n * both PERCEIVES and ACTS UPON (whiteboard, shared doc, map, …). A concrete plugin\n * contributes everything the channel needs, so the session service / call overlay carry\n * ZERO channel-specific wiring:\n *\n * 1. a CLIENT-EXECUTED TOOL SET ({@link GetToolDefinitions}, declared to the realtime\n * model at session mint) plus the local executor ({@link ApplyAgentTool}) the host\n * routes `{@link ToolNamePrefix}*` calls to — the ACTION direction;\n * 2. a STATE→CONTEXT SERIALIZER policy — the plugin owns its state engine and pushes\n * coalesced deltas through {@link RealtimeChannelContext.SendContextNote} — the\n * PERCEPTION direction;\n * 3. an OPTIONAL ANGULAR SURFACE ({@link GetSurfaceComponent}) the overlay creates dynamically\n * in a channel tab, handed back through {@link BindSurface} so the plugin wires its own\n * inputs/outputs (the host never knows the component's API). A channel may be **server-only**\n * (no rendered surface) — e.g. a bridge-contributed meeting-controls or native-whiteboard\n * channel whose surface lives on the external platform, not in MJ. Such a channel returns\n * `null` from {@link GetSurfaceComponent} ({@link HasSurface} is `false`) and the overlay\n * simply skips its tab while still wiring its tools + perception;\n * 4. a STATE OF RECORD ({@link SerializeState}, persisted via\n * {@link RealtimeChannelContext.RequestSave} under {@link ChannelName}).\n *\n * ### Registration & resolution (mirrors the realtime model drivers)\n * Concrete plugins are `@RegisterClass(BaseRealtimeChannelClient, '<ClientPluginClass>')`\n * and are resolved at session start from the `MJ: AI Agent Channels` registry: each ACTIVE\n * row's `ClientPluginClass` is the ClassFactory key (exactly how `BaseRealtimeClient`\n * drivers resolve by provider key). Ship a `Load<YourChannel>()` no-op alongside the class\n * and call it from a static code path to defeat tree-shaking.\n *\n * ### Lifecycle — ONE INSTANCE PER SESSION (not a singleton)\n * `ClassFactory.CreateInstance` → {@link Initialize}(ctx) → zero or more\n * {@link BindSurface}/{@link UnbindSurface} cycles (the surface pane is created/destroyed\n * with the overlay's tab panel, e.g. collapse/expand) → {@link Dispose} at teardown.\n * {@link ApplyAgentTool} MUST work with NO surface bound (apply to the state engine\n * directly; skip the UI garnish) — tool calls can arrive while the panel is collapsed.\n *\n * @typeParam TSurface The plugin's Angular surface component type. The host only ever\n * sees the default (`object`) — the typed parameter exists so concrete plugins get a\n * fully typed {@link BindSurface} without casts.\n */\nexport abstract class BaseRealtimeChannelClient<TSurface extends object = object> {\n /**\n * The host context, available from {@link Initialize} until {@link Dispose}.\n * `null` outside that window — guard with `?.` in any code that can run early/late.\n */\n protected Context: RealtimeChannelContext | null = null;\n\n /**\n * The channel definition name — MUST match the `MJ: AI Agent Channels` row's `Name`\n * (e.g. `'Whiteboard'`). Used as the persistence key for {@link SerializeState} saves\n * and as the channel tab's stable key.\n */\n public abstract get ChannelName(): string;\n\n /**\n * The shared name prefix of every tool this channel exposes (e.g. `'Whiteboard_'`).\n * The host registers ONE local-execution route per plugin: tool calls whose name starts\n * with this prefix go to {@link ApplyAgentTool} instead of the server relay.\n */\n public abstract get ToolNamePrefix(): string;\n\n /** Label for the channel's tab on the overlay's surface panel (e.g. `'Whiteboard'`). */\n public abstract get TabTitle(): string;\n\n /** Font Awesome icon class for the channel's tab (e.g. `'fa-solid fa-chalkboard'`). */\n public abstract get TabIcon(): string;\n\n /**\n * The channel's CLIENT-EXECUTED tool declarations, aggregated by the session service\n * into the `clientTools` set declared to the realtime model at session mint. The server\n * only DECLARES these — execution stays in the browser via {@link ApplyAgentTool}.\n */\n public abstract GetToolDefinitions(): RealtimeToolDefinition[];\n\n /**\n * Executes ONE agent tool call locally (the ACTION direction) and returns the result\n * JSON string fed back to the model as the `tool_response`. Called for every tool whose\n * name starts with {@link ToolNamePrefix}. Must work both WITH a bound surface (apply +\n * UI garnish) and WITHOUT one (apply to the state engine directly — the tab pane may not\n * exist, e.g. the surface panel is collapsed). Should not throw: return a\n * `{ success: false, error }` payload so the model can narrate the failure (the host\n * additionally wraps anything thrown).\n */\n public abstract ApplyAgentTool(toolName: string, argsJson: string): string | Promise<string>;\n\n /**\n * The Angular component the overlay creates dynamically as this channel's tab pane, or `null`\n * for a **server-only** channel that renders no MJ surface (its surface, if any, lives on the\n * external platform — e.g. a bridge-contributed native whiteboard or meeting-controls channel).\n *\n * When this returns `null`, the overlay renders NO tab for the channel and never calls\n * {@link BindSurface}/{@link UnbindSurface} — but the channel's tools ({@link GetToolDefinitions} /\n * {@link ApplyAgentTool}) and perception ({@link RealtimeChannelContext.SendContextNote}) still run.\n * A created surface instance is handed straight back via {@link BindSurface}; the host treats it as\n * opaque.\n *\n * Default: `null` (server-only). A channel with a rendered surface overrides this to return its\n * component type.\n */\n public GetSurfaceComponent(): Type<TSurface> | null {\n return null;\n }\n\n /**\n * Whether this channel has a rendered MJ surface ({@link GetSurfaceComponent} returns non-null).\n * The overlay uses this to decide whether to register a surface tab; server-only channels are\n * `false`. Override only if surface availability must be decided WITHOUT constructing the type\n * (the default calls {@link GetSurfaceComponent} once).\n */\n public HasSurface(): boolean {\n return this.GetSurfaceComponent() != null;\n }\n\n /**\n * The channel's FIRST-RUN INTRO content, or `null` when the channel offers no onboarding.\n * The overlay shows this once per channel per user — the first time the user opens this\n * channel's surface tab — and remembers \"seen\" via the user's settings (NOT localStorage),\n * so it never re-appears on later sessions or other devices.\n *\n * Default: `null` (no intro). The base Voice/text channel has no plugin at all, so it never\n * shows an intro; an interactive channel with a surface worth explaining (whiteboard, remote\n * browser, …) overrides this to return its {@link ChannelOnboardingDetails}. A plugin that\n * doesn't override it simply shows nothing — onboarding is strictly opt-in.\n */\n public GetOnboardingDetails(): ChannelOnboardingDetails | null {\n return null;\n }\n\n /**\n * Called by the host right after it created the surface component (and BEFORE the\n * component's first change detection, so inputs set here are visible in its `ngOnInit`).\n * The plugin — which knows its own component type — sets inputs (state engine, agent\n * name, …) and subscribes outputs here, wiring perception/garnish flows back through\n * {@link Context}. May be called again with a NEW instance after an\n * {@link UnbindSurface} (the pane is destroyed/recreated with the tab panel).\n */\n public abstract BindSurface(instance: TSurface): void;\n\n /**\n * Called by the host when the surface component is being destroyed (tab panel\n * collapsed / overlay torn down). Drop the instance reference and unsubscribe any\n * output subscriptions — after this, {@link ApplyAgentTool} runs in its no-surface\n * mode. Default: no-op.\n */\n public UnbindSurface(): void {\n // default: nothing to release\n }\n\n /**\n * Binds the host context and invokes the {@link OnInitialize} hook. Called exactly once\n * per session, right after ClassFactory instantiation and before any tool call or\n * surface bind.\n */\n public Initialize(ctx: RealtimeChannelContext): void {\n this.Context = ctx;\n this.OnInitialize();\n }\n\n /**\n * Subclass hook invoked from {@link Initialize} once {@link Context} is bound — wire\n * state-engine subscriptions (e.g. state change → `Context.RequestSave(...)`) here.\n * Default: no-op.\n */\n protected OnInitialize(): void {\n // default: nothing to initialize\n }\n\n /**\n * Max time {@link ResolveAgentSessionId} waits for the session id to bind before giving up, and the\n * poll interval it re-checks on. Protected so tests can shrink the wait; production keeps the\n * defaults (the real mint race is sub-second, 8s is generous headroom).\n */\n protected SessionIdWaitTimeoutMs = 8000;\n protected SessionIdWaitIntervalMs = 200;\n\n /**\n * Resolves the live {@link RealtimeChannelContext.AgentSessionID}, briefly WAITING for it when it\n * isn't bound yet rather than giving up instantly. `AgentSessionID` is a live getter over the\n * session service's current id: it reads `null` in the window BEFORE the session mints (the\n * realtime model can fire a tool call the very first beat it connects, before `mintSession`\n * resolves) and again AFTER teardown. Server-backed tool paths (e.g. the Remote Browser channel's\n * `browser_*` tools) call this instead of reading `Context?.AgentSessionID` synchronously, so a tool\n * invoked a beat early WAITS for the session to come live — defense-in-depth against the\n * \"session id missing\" race — instead of returning a hard failure to the model.\n *\n * Returns the id as soon as it's non-null (the common path resolves immediately, no delay), or\n * `null` if it's still unbound after {@link SessionIdWaitTimeoutMs} — or the channel was\n * {@link Dispose}d in the meantime (`Context` goes null, so we stop waiting on a torn-down session).\n */\n protected async ResolveAgentSessionId(): Promise<string | null> {\n const immediate = this.Context?.AgentSessionID ?? null;\n if (immediate) {\n return immediate;\n }\n const intervalMs = Math.max(1, this.SessionIdWaitIntervalMs);\n for (let waited = 0; waited < this.SessionIdWaitTimeoutMs; waited += intervalMs) {\n await new Promise<void>((resolve) => setTimeout(resolve, intervalMs));\n // Context goes null on Dispose() — the session is gone, stop waiting.\n if (!this.Context) {\n return null;\n }\n const id = this.Context.AgentSessionID;\n if (id) {\n return id;\n }\n }\n return this.Context?.AgentSessionID ?? null;\n }\n\n /**\n * Serializes the channel's current state of record (the payload persisted on the\n * session's channel row), or `null` when the channel keeps no persistent state.\n * Default: `null`.\n */\n public SerializeState(): string | null {\n return null;\n }\n\n /**\n * Restores a PRIOR session's saved channel state (the payload a previous session\n * persisted via {@link SerializeState} / {@link RealtimeChannelContext.RequestSave}).\n * Invoked by the session host AFTER {@link Initialize} and BEFORE any surface binding,\n * when a prior session's saved state exists for this channel.\n *\n * Returns `true` when the state was applied; `false` when the channel ignored it —\n * either because it keeps no persistent state (this default) or because the payload was\n * malformed/incompatible. Implementations MUST be tolerant: never throw on bad input,\n * just return `false` and start fresh.\n */\n public RestoreState(stateJson: string): boolean {\n return false;\n }\n\n /**\n * The focus pill's \"exit\" affordance, routed by the overlay to the channel that holds\n * focus. Implementations should leave focus mode through their OWN surface (so surface\n * toggles stay in sync), ultimately emitting `Context.SetFocusMode(false)`. The overlay\n * defensively clears its layout flag as well, so a no-op default is safe.\n */\n public RequestFocusExit(): void {\n // default: the overlay's defensive clear handles it\n }\n\n /**\n * Tears the plugin down at session end: release the surface binding, unsubscribe\n * state-engine subscriptions, then drop the context. Subclasses overriding this MUST\n * call `super.Dispose()`. Any final state save has already been flushed by the host\n * (the debounced {@link RealtimeChannelContext.RequestSave} pipeline) before disposal.\n */\n public Dispose(): void {\n this.UnbindSurface();\n this.Context = null;\n }\n}\n"]}
@@ -3,6 +3,7 @@ import type { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
3
3
  import { UUIDsEqual } from '@memberjunction/global';
4
4
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
5
5
  import { RealtimeModelOption, RealtimePairedAgentRow } from '../../services/realtime-pairing';
6
+ import { RealtimeModelVoices, RealtimeVoiceOption } from '@memberjunction/graphql-dataprovider';
6
7
  import * as i0 from "@angular/core";
7
8
  /**
8
9
  * The user's confirmed choice from the realtime picker: the agent to call, an optional
@@ -16,6 +17,8 @@ export interface RealtimeAgentPick {
16
17
  Agent: MJAIAgentEntityExtended;
17
18
  /** Explicit realtime model id, or `null` for the server's automatic selection. */
18
19
  PreferredModelId: string | null;
20
+ /** Explicit provider-native voice id (e.g. `echo`), or `null` for the configured/default voice. */
21
+ PreferredVoice: string | null;
19
22
  /** Explicit co-agent id (`MJ: AI Agents.ID`, Realtime type), or `null` for the server's resolution chain. */
20
23
  CoAgentId: string | null;
21
24
  }
@@ -80,6 +83,12 @@ export declare class RealtimeAgentPickerComponent extends BaseAngularComponent i
80
83
  Models: RealtimeModelOption[];
81
84
  /** The explicitly chosen voice model id, or `null` for "Auto (recommended)" (the default). */
82
85
  SelectedModelId: string | null;
86
+ /** Active Realtime models + their voices (for the voice selector); loaded only for authorized users. */
87
+ VoiceModels: RealtimeModelVoices[];
88
+ /** The explicitly chosen voice id, or `null` for the configured/default voice. */
89
+ SelectedVoiceId: string | null;
90
+ /** Voices for the currently-selected model (empty when no model picked or it declares none). */
91
+ get SelectedModelVoices(): RealtimeVoiceOption[];
83
92
  /** The explicitly chosen co-agent id, or `null` for "Auto (recommended)" (the server's chain). */
84
93
  SelectedCoAgentId: string | null;
85
94
  /**
@@ -108,8 +117,10 @@ export declare class RealtimeAgentPickerComponent extends BaseAngularComponent i
108
117
  * the selector hidden (the server's automatic selection still applies).
109
118
  */
110
119
  private loadVoiceModels;
111
- /** Records the voice-model choice (`''` = Auto → `null`). */
120
+ /** Records the voice-model choice (`''` = Auto → `null`); clears the voice so it can't outlive a switch. */
112
121
  OnModelChange(value: string): void;
122
+ /** Records the voice choice (`''` = default → `null`). */
123
+ OnVoiceChange(value: string): void;
113
124
  /** Records the co-agent choice (`''` = Auto → `null`) and refreshes the pairing constraint. */
114
125
  OnCoAgentChange(value: string): void;
115
126
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-agent-picker.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-agent-picker.component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime-agent-picker.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-agent-picker.component.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { AIEngineBase } from '@memberjunction/ai-engine-base';
5
5
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
6
6
  import { REALTIME_ADVANCED_SESSION_CONTROLS, UserHoldsAuthorization } from '../../services/user-authorization';
7
7
  import { BuildRealtimeModelOptions, ConstrainTargetsToPairings, DefaultPairedTargetId, LoadCoAgentPairings, } from '../../services/realtime-pairing';
8
+ import { GraphQLLiveKitClient } from '@memberjunction/graphql-dataprovider';
8
9
  import * as i0 from "@angular/core";
9
10
  const _c0 = ["searchInput"];
10
11
  const _forTrack0 = ($index, $item) => $item.ID;
@@ -20,7 +21,7 @@ function RealtimeAgentPickerComponent_Conditional_5_For_10_Template(rf, ctx) { i
20
21
  } }
21
22
  function RealtimeAgentPickerComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
22
23
  const _r2 = i0.ɵɵgetCurrentView();
23
- i0.ɵɵelementStart(0, "div", 6)(1, "label", 16);
24
+ i0.ɵɵelementStart(0, "div", 7)(1, "label", 16);
24
25
  i0.ɵɵelement(2, "i", 17);
25
26
  i0.ɵɵelementStart(3, "span");
26
27
  i0.ɵɵtext(4, "Co-agent");
@@ -40,7 +41,7 @@ function RealtimeAgentPickerComponent_Conditional_5_Template(rf, ctx) { if (rf &
40
41
  i0.ɵɵrepeater(ctx_r3.CoAgents);
41
42
  } }
42
43
  function RealtimeAgentPickerComponent_Conditional_11_Template(rf, ctx) { if (rf & 1) {
43
- i0.ɵɵelementStart(0, "div", 11);
44
+ i0.ɵɵelementStart(0, "div", 12);
44
45
  i0.ɵɵtext(1);
45
46
  i0.ɵɵelementEnd();
46
47
  } if (rf & 2) {
@@ -91,26 +92,60 @@ function RealtimeAgentPickerComponent_Conditional_13_For_10_Template(rf, ctx) {
91
92
  i0.ɵɵadvance();
92
93
  i0.ɵɵtextInterpolate(model_r11.Name);
93
94
  } }
95
+ function RealtimeAgentPickerComponent_Conditional_13_Conditional_11_For_10_Template(rf, ctx) { if (rf & 1) {
96
+ i0.ɵɵelementStart(0, "option", 20);
97
+ i0.ɵɵtext(1);
98
+ i0.ɵɵelementEnd();
99
+ } if (rf & 2) {
100
+ const v_r14 = ctx.$implicit;
101
+ i0.ɵɵproperty("value", v_r14.ID);
102
+ i0.ɵɵadvance();
103
+ i0.ɵɵtextInterpolate(v_r14.Name);
104
+ } }
105
+ function RealtimeAgentPickerComponent_Conditional_13_Conditional_11_Template(rf, ctx) { if (rf & 1) {
106
+ const _r12 = i0.ɵɵgetCurrentView();
107
+ i0.ɵɵelementStart(0, "div", 25)(1, "label", 29);
108
+ i0.ɵɵelement(2, "i", 30);
109
+ i0.ɵɵelementStart(3, "span");
110
+ i0.ɵɵtext(4, "Voice");
111
+ i0.ɵɵelementEnd()();
112
+ i0.ɵɵelementStart(5, "select", 31, 3);
113
+ i0.ɵɵlistener("change", function RealtimeAgentPickerComponent_Conditional_13_Conditional_11_Template_select_change_5_listener() { i0.ɵɵrestoreView(_r12); const voiceSelect_r13 = i0.ɵɵreference(6); const ctx_r3 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r3.OnVoiceChange(voiceSelect_r13.value)); });
114
+ i0.ɵɵelementStart(7, "option", 19);
115
+ i0.ɵɵtext(8, "Default");
116
+ i0.ɵɵelementEnd();
117
+ i0.ɵɵrepeaterCreate(9, RealtimeAgentPickerComponent_Conditional_13_Conditional_11_For_10_Template, 2, 2, "option", 20, _forTrack0);
118
+ i0.ɵɵelementEnd()();
119
+ } if (rf & 2) {
120
+ const ctx_r3 = i0.ɵɵnextContext(2);
121
+ i0.ɵɵadvance(5);
122
+ i0.ɵɵproperty("value", ctx_r3.SelectedVoiceId ?? "");
123
+ i0.ɵɵadvance(4);
124
+ i0.ɵɵrepeater(ctx_r3.SelectedModelVoices);
125
+ } }
94
126
  function RealtimeAgentPickerComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
95
127
  const _r9 = i0.ɵɵgetCurrentView();
96
- i0.ɵɵelementStart(0, "div", 12)(1, "label", 25);
97
- i0.ɵɵelement(2, "i", 26);
128
+ i0.ɵɵelementStart(0, "div", 25)(1, "label", 26);
129
+ i0.ɵɵelement(2, "i", 27);
98
130
  i0.ɵɵelementStart(3, "span");
99
131
  i0.ɵɵtext(4, "Voice model");
100
132
  i0.ɵɵelementEnd()();
101
- i0.ɵɵelementStart(5, "select", 27, 2);
133
+ i0.ɵɵelementStart(5, "select", 28, 2);
102
134
  i0.ɵɵlistener("change", function RealtimeAgentPickerComponent_Conditional_13_Template_select_change_5_listener() { i0.ɵɵrestoreView(_r9); const modelSelect_r10 = i0.ɵɵreference(6); const ctx_r3 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r3.OnModelChange(modelSelect_r10.value)); });
103
135
  i0.ɵɵelementStart(7, "option", 19);
104
136
  i0.ɵɵtext(8, "Auto (recommended)");
105
137
  i0.ɵɵelementEnd();
106
138
  i0.ɵɵrepeaterCreate(9, RealtimeAgentPickerComponent_Conditional_13_For_10_Template, 2, 2, "option", 20, _forTrack0);
107
139
  i0.ɵɵelementEnd()();
140
+ i0.ɵɵconditionalCreate(11, RealtimeAgentPickerComponent_Conditional_13_Conditional_11_Template, 11, 1, "div", 25);
108
141
  } if (rf & 2) {
109
142
  const ctx_r3 = i0.ɵɵnextContext();
110
143
  i0.ɵɵadvance(5);
111
144
  i0.ɵɵproperty("value", ctx_r3.SelectedModelId ?? "");
112
145
  i0.ɵɵadvance(4);
113
146
  i0.ɵɵrepeater(ctx_r3.Models);
147
+ i0.ɵɵadvance(2);
148
+ i0.ɵɵconditional(ctx_r3.SelectedModelVoices.length ? 11 : -1);
114
149
  } }
115
150
  /**
116
151
  * Compact anchored popover that lets the user choose WHICH agent a realtime
@@ -173,6 +208,14 @@ export class RealtimeAgentPickerComponent extends BaseAngularComponent {
173
208
  Models = [];
174
209
  /** The explicitly chosen voice model id, or `null` for "Auto (recommended)" (the default). */
175
210
  SelectedModelId = null;
211
+ /** Active Realtime models + their voices (for the voice selector); loaded only for authorized users. */
212
+ VoiceModels = [];
213
+ /** The explicitly chosen voice id, or `null` for the configured/default voice. */
214
+ SelectedVoiceId = null;
215
+ /** Voices for the currently-selected model (empty when no model picked or it declares none). */
216
+ get SelectedModelVoices() {
217
+ return this.VoiceModels.find((m) => UUIDsEqual(m.ModelID, this.SelectedModelId))?.Voices ?? [];
218
+ }
176
219
  /** The explicitly chosen co-agent id, or `null` for "Auto (recommended)" (the server's chain). */
177
220
  SelectedCoAgentId = null;
178
221
  /**
@@ -229,11 +272,26 @@ export class RealtimeAgentPickerComponent extends BaseAngularComponent {
229
272
  console.error('[RealtimeAgentPicker] Failed to load realtime models:', error);
230
273
  this.Models = [];
231
274
  }
275
+ // Voices come from the driver via a server query (the cached models carry no voice list).
276
+ try {
277
+ const client = new GraphQLLiveKitClient(this.ProviderToUse);
278
+ this.VoiceModels = await client.GetRealtimeModelVoices();
279
+ }
280
+ catch (error) {
281
+ console.error('[RealtimeAgentPicker] Failed to load realtime voices:', error);
282
+ this.VoiceModels = [];
283
+ }
232
284
  this.cdr.markForCheck();
233
285
  }
234
- /** Records the voice-model choice (`''` = Auto → `null`). */
286
+ /** Records the voice-model choice (`''` = Auto → `null`); clears the voice so it can't outlive a switch. */
235
287
  OnModelChange(value) {
236
288
  this.SelectedModelId = value && value.length > 0 ? value : null;
289
+ this.SelectedVoiceId = null;
290
+ this.cdr.markForCheck();
291
+ }
292
+ /** Records the voice choice (`''` = default → `null`). */
293
+ OnVoiceChange(value) {
294
+ this.SelectedVoiceId = value && value.length > 0 ? value : null;
237
295
  this.cdr.markForCheck();
238
296
  }
239
297
  /** Records the co-agent choice (`''` = Auto → `null`) and refreshes the pairing constraint. */
@@ -385,6 +443,7 @@ export class RealtimeAgentPickerComponent extends BaseAngularComponent {
385
443
  return {
386
444
  Agent: agent,
387
445
  PreferredModelId: this.CanOverrideSessionConfig ? this.SelectedModelId : null,
446
+ PreferredVoice: this.CanOverrideSessionConfig ? this.SelectedVoiceId : null,
388
447
  CoAgentId: this.SelectedCoAgentId
389
448
  };
390
449
  }
@@ -402,28 +461,28 @@ export class RealtimeAgentPickerComponent extends BaseAngularComponent {
402
461
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.searchInput = _t.first);
403
462
  } }, hostBindings: function RealtimeAgentPickerComponent_HostBindings(rf, ctx) { if (rf & 1) {
404
463
  i0.ɵɵlistener("click", function RealtimeAgentPickerComponent_click_HostBindingHandler($event) { return ctx.OnDocumentClick($event); }, i0.ɵɵresolveDocument)("keydown.escape", function RealtimeAgentPickerComponent_keydown_escape_HostBindingHandler() { return ctx.OnEscape(); }, i0.ɵɵresolveDocument);
405
- } }, inputs: { Agents: "Agents", DefaultAgentId: "DefaultAgentId", CoAgents: "CoAgents", DefaultCoAgentId: "DefaultCoAgentId" }, outputs: { AgentPicked: "AgentPicked", Cancelled: "Cancelled" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 20, vars: 5, consts: [["searchInput", ""], ["coAgentSelect", ""], ["modelSelect", ""], [1, "mj-voice-picker", 3, "click"], [1, "mj-voice-picker__header"], [1, "fa-solid", "fa-phone"], [1, "mj-voice-picker__select-row"], [1, "mj-voice-picker__search"], [1, "fa-solid", "fa-magnifying-glass"], ["type", "text", "placeholder", "Search agents", 3, "input", "keydown.enter", "keydown.arrowdown", "keydown.arrowup", "value"], [1, "mj-voice-picker__list"], [1, "mj-voice-picker__empty"], [1, "mj-voice-picker__select-row", "mj-voice-picker__select-row--model"], [1, "mj-voice-picker__footer"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], ["mjButton", "", "variant", "outline", "size", "sm", 3, "click"], ["for", "mjVoiceCoAgentSelect", 1, "mj-voice-picker__select-label"], [1, "fa-solid", "fa-headset"], ["id", "mjVoiceCoAgentSelect", 1, "mj-voice-picker__select", 3, "change", "value"], ["value", ""], [3, "value"], ["type", "button", 1, "mj-voice-picker__item", 3, "mj-voice-picker__item--selected", "title"], ["type", "button", 1, "mj-voice-picker__item", 3, "click", "dblclick", "title"], [1, "mj-voice-picker__item-name"], [1, "mj-voice-picker__default-badge"], ["for", "mjVoiceModelSelect", 1, "mj-voice-picker__select-label"], [1, "fa-solid", "fa-microchip"], ["id", "mjVoiceModelSelect", 1, "mj-voice-picker__select", 3, "change", "value"]], template: function RealtimeAgentPickerComponent_Template(rf, ctx) { if (rf & 1) {
464
+ } }, inputs: { Agents: "Agents", DefaultAgentId: "DefaultAgentId", CoAgents: "CoAgents", DefaultCoAgentId: "DefaultCoAgentId" }, outputs: { AgentPicked: "AgentPicked", Cancelled: "Cancelled" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 20, vars: 5, consts: [["searchInput", ""], ["coAgentSelect", ""], ["modelSelect", ""], ["voiceSelect", ""], [1, "mj-voice-picker", 3, "click"], [1, "mj-voice-picker__header"], [1, "fa-solid", "fa-phone"], [1, "mj-voice-picker__select-row"], [1, "mj-voice-picker__search"], [1, "fa-solid", "fa-magnifying-glass"], ["type", "text", "placeholder", "Search agents", 3, "input", "keydown.enter", "keydown.arrowdown", "keydown.arrowup", "value"], [1, "mj-voice-picker__list"], [1, "mj-voice-picker__empty"], [1, "mj-voice-picker__footer"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], ["mjButton", "", "variant", "outline", "size", "sm", 3, "click"], ["for", "mjVoiceCoAgentSelect", 1, "mj-voice-picker__select-label"], [1, "fa-solid", "fa-headset"], ["id", "mjVoiceCoAgentSelect", 1, "mj-voice-picker__select", 3, "change", "value"], ["value", ""], [3, "value"], ["type", "button", 1, "mj-voice-picker__item", 3, "mj-voice-picker__item--selected", "title"], ["type", "button", 1, "mj-voice-picker__item", 3, "click", "dblclick", "title"], [1, "mj-voice-picker__item-name"], [1, "mj-voice-picker__default-badge"], [1, "mj-voice-picker__select-row", "mj-voice-picker__select-row--model"], ["for", "mjVoiceModelSelect", 1, "mj-voice-picker__select-label"], [1, "fa-solid", "fa-microchip"], ["id", "mjVoiceModelSelect", 1, "mj-voice-picker__select", 3, "change", "value"], ["for", "mjVoiceVoiceSelect", 1, "mj-voice-picker__select-label"], [1, "fa-solid", "fa-waveform-lines"], ["id", "mjVoiceVoiceSelect", 1, "mj-voice-picker__select", 3, "change", "value"]], template: function RealtimeAgentPickerComponent_Template(rf, ctx) { if (rf & 1) {
406
465
  const _r1 = i0.ɵɵgetCurrentView();
407
- i0.ɵɵelementStart(0, "div", 3);
466
+ i0.ɵɵelementStart(0, "div", 4);
408
467
  i0.ɵɵlistener("click", function RealtimeAgentPickerComponent_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); });
409
- i0.ɵɵelementStart(1, "div", 4);
410
- i0.ɵɵelement(2, "i", 5);
468
+ i0.ɵɵelementStart(1, "div", 5);
469
+ i0.ɵɵelement(2, "i", 6);
411
470
  i0.ɵɵelementStart(3, "span");
412
471
  i0.ɵɵtext(4, "Start a voice call with\u2026");
413
472
  i0.ɵɵelementEnd()();
414
- i0.ɵɵconditionalCreate(5, RealtimeAgentPickerComponent_Conditional_5_Template, 11, 1, "div", 6);
415
- i0.ɵɵelementStart(6, "div", 7);
416
- i0.ɵɵelement(7, "i", 8);
417
- i0.ɵɵelementStart(8, "input", 9, 0);
473
+ i0.ɵɵconditionalCreate(5, RealtimeAgentPickerComponent_Conditional_5_Template, 11, 1, "div", 7);
474
+ i0.ɵɵelementStart(6, "div", 8);
475
+ i0.ɵɵelement(7, "i", 9);
476
+ i0.ɵɵelementStart(8, "input", 10, 0);
418
477
  i0.ɵɵlistener("input", function RealtimeAgentPickerComponent_Template_input_input_8_listener() { i0.ɵɵrestoreView(_r1); const searchInput_r6 = i0.ɵɵreference(9); return i0.ɵɵresetView(ctx.OnSearchInput(searchInput_r6.value)); })("keydown.enter", function RealtimeAgentPickerComponent_Template_input_keydown_enter_8_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.StartCall()); })("keydown.arrowdown", function RealtimeAgentPickerComponent_Template_input_keydown_arrowdown_8_listener($event) { i0.ɵɵrestoreView(_r1); ctx.MoveSelection(1); return i0.ɵɵresetView($event.preventDefault()); })("keydown.arrowup", function RealtimeAgentPickerComponent_Template_input_keydown_arrowup_8_listener($event) { i0.ɵɵrestoreView(_r1); ctx.MoveSelection(-1); return i0.ɵɵresetView($event.preventDefault()); });
419
478
  i0.ɵɵelementEnd()();
420
- i0.ɵɵelementStart(10, "div", 10);
421
- i0.ɵɵconditionalCreate(11, RealtimeAgentPickerComponent_Conditional_11_Template, 2, 1, "div", 11)(12, RealtimeAgentPickerComponent_Conditional_12_Template, 2, 0);
479
+ i0.ɵɵelementStart(10, "div", 11);
480
+ i0.ɵɵconditionalCreate(11, RealtimeAgentPickerComponent_Conditional_11_Template, 2, 1, "div", 12)(12, RealtimeAgentPickerComponent_Conditional_12_Template, 2, 0);
422
481
  i0.ɵɵelementEnd();
423
- i0.ɵɵconditionalCreate(13, RealtimeAgentPickerComponent_Conditional_13_Template, 11, 1, "div", 12);
482
+ i0.ɵɵconditionalCreate(13, RealtimeAgentPickerComponent_Conditional_13_Template, 12, 2);
424
483
  i0.ɵɵelementStart(14, "div", 13)(15, "button", 14);
425
484
  i0.ɵɵlistener("click", function RealtimeAgentPickerComponent_Template_button_click_15_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.StartCall()); });
426
- i0.ɵɵelement(16, "i", 5);
485
+ i0.ɵɵelement(16, "i", 6);
427
486
  i0.ɵɵtext(17, " Start ");
428
487
  i0.ɵɵelementEnd();
429
488
  i0.ɵɵelementStart(18, "button", 15);
@@ -521,6 +580,25 @@ export class RealtimeAgentPickerComponent extends BaseAngularComponent {
521
580
  }
522
581
  </select>
523
582
  </div>
583
+ @if (SelectedModelVoices.length) {
584
+ <div class="mj-voice-picker__select-row mj-voice-picker__select-row--model">
585
+ <label class="mj-voice-picker__select-label" for="mjVoiceVoiceSelect">
586
+ <i class="fa-solid fa-waveform-lines"></i>
587
+ <span>Voice</span>
588
+ </label>
589
+ <select
590
+ #voiceSelect
591
+ id="mjVoiceVoiceSelect"
592
+ class="mj-voice-picker__select"
593
+ [value]="SelectedVoiceId ?? ''"
594
+ (change)="OnVoiceChange(voiceSelect.value)">
595
+ <option value="">Default</option>
596
+ @for (v of SelectedModelVoices; track v.ID) {
597
+ <option [value]="v.ID">{{ v.Name }}</option>
598
+ }
599
+ </select>
600
+ </div>
601
+ }
524
602
  }
525
603
  <div class="mj-voice-picker__footer">
526
604
  <button mjButton variant="primary" size="sm" [disabled]="!SelectedAgent" (click)="StartCall()">
@@ -552,5 +630,5 @@ export class RealtimeAgentPickerComponent extends BaseAngularComponent {
552
630
  type: HostListener,
553
631
  args: ['document:keydown.escape']
554
632
  }] }); })();
555
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeAgentPickerComponent, { className: "RealtimeAgentPickerComponent", filePath: "src/lib/components/realtime/realtime-agent-picker.component.ts", lineNumber: 272 }); })();
633
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeAgentPickerComponent, { className: "RealtimeAgentPickerComponent", filePath: "src/lib/components/realtime/realtime-agent-picker.component.ts", lineNumber: 294 }); })();
556
634
  //# sourceMappingURL=realtime-agent-picker.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-agent-picker.component.js","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-agent-picker.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n OnInit,\n Output,\n ViewChild,\n AfterViewInit,\n inject,\n} from '@angular/core';\nimport type { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { REALTIME_ADVANCED_SESSION_CONTROLS, UserHoldsAuthorization } from '../../services/user-authorization';\nimport {\n BuildRealtimeModelOptions,\n ConstrainTargetsToPairings,\n DefaultPairedTargetId,\n LoadCoAgentPairings,\n RealtimeModelOption,\n RealtimePairedAgentRow,\n} from '../../services/realtime-pairing';\n\n/**\n * The user's confirmed choice from the realtime picker: the agent to call, an optional\n * EXPLICIT realtime model (`null` = \"Auto (recommended)\", i.e. let the server pick —\n * always `null` for users without the `Realtime: Advanced Session Controls`\n * authorization, who never see the model selector), and an optional EXPLICIT co-agent\n * (`null` = the server's co-agent resolution chain).\n */\nexport interface RealtimeAgentPick {\n /** The agent the voice call should front. */\n Agent: MJAIAgentEntityExtended;\n /** Explicit realtime model id, or `null` for the server's automatic selection. */\n PreferredModelId: string | null;\n /** Explicit co-agent id (`MJ: AI Agents.ID`, Realtime type), or `null` for the server's resolution chain. */\n CoAgentId: string | null;\n}\n\n/**\n * Compact anchored popover that lets the user choose WHICH agent a realtime\n * voice call should front — shown by {@link MessageInputComponent} when the\n * phone button is clicked on a conversation with **no prior agent\n * participation** (new / empty conversation), and on demand via the caret\n * button next to the phone (any conversation), where it doubles as the way\n * to pick a specific **co-agent** (the Realtime-type agent that voices the\n * call) and — for authorized users — a specific **voice model**. The plain\n * phone click on an existing conversation keeps its friction-free \"call the\n * resolved agent immediately\" behavior.\n *\n * Mostly dumb: the host supplies the agent list (the same cached set the\n * @mention autocomplete / routing logic uses), the Realtime-type co-agent\n * candidates (filtered from that same set), and the defaults to preselect;\n * the picker emits the user's choice. The data it loads itself:\n * - the compact \"Voice model\" option list (active Realtime models via a\n * narrow RunView) — loaded and rendered ONLY when the current user holds\n * the `Realtime: Advanced Session Controls` authorization (pure UX\n * disclosure; the server enforces the authorization on the mint), and\n * - the chosen co-agent's pairing rows (`MJ: AI Agent Co Agents`, Active 'CoAgent'-type rows): a\n * co-agent with pairing rows may only front its paired targets, so the\n * agent list constrains to those rows (Sequence order) with the IsDefault\n * row preselected; a co-agent with zero rows is universal and the list is\n * untouched.\n *\n * It does NOT persist anything (the host persists the co-agent preference\n * via `UserInfoEngine`; `mj-conversation-agent-picker` pins\n * `MJConversationEntity.DefaultAgentID` — different jobs, which is why this\n * is a separate component).\n *\n * Anchors itself bottom-right above the composer — the host's\n * `.message-input-wrapper` is `position: relative`.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-agent-picker',\n imports: [MJButtonDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"mj-voice-picker\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-voice-picker__header\">\n <i class=\"fa-solid fa-phone\"></i>\n <span>Start a voice call with…</span>\n </div>\n @if (ShowCoAgentSelect) {\n <div class=\"mj-voice-picker__select-row\">\n <label class=\"mj-voice-picker__select-label\" for=\"mjVoiceCoAgentSelect\">\n <i class=\"fa-solid fa-headset\"></i>\n <span>Co-agent</span>\n </label>\n <select\n #coAgentSelect\n id=\"mjVoiceCoAgentSelect\"\n class=\"mj-voice-picker__select\"\n [value]=\"SelectedCoAgentId ?? ''\"\n (change)=\"OnCoAgentChange(coAgentSelect.value)\">\n <option value=\"\">Auto (recommended)</option>\n @for (coAgent of CoAgents; track coAgent.ID) {\n <option [value]=\"coAgent.ID\">{{ coAgent.Name }}</option>\n }\n </select>\n </div>\n }\n <div class=\"mj-voice-picker__search\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <input\n #searchInput\n type=\"text\"\n placeholder=\"Search agents\"\n [value]=\"SearchText\"\n (input)=\"OnSearchInput(searchInput.value)\"\n (keydown.enter)=\"StartCall()\"\n (keydown.arrowdown)=\"MoveSelection(1); $event.preventDefault()\"\n (keydown.arrowup)=\"MoveSelection(-1); $event.preventDefault()\" />\n </div>\n <div class=\"mj-voice-picker__list\">\n @if (FilteredAgents.length === 0) {\n <div class=\"mj-voice-picker__empty\">{{ IsConstrainedByPairings ? 'No paired agents available' : 'No matching agents' }}</div>\n } @else {\n @for (agent of FilteredAgents; track agent.ID) {\n <button\n type=\"button\"\n class=\"mj-voice-picker__item\"\n [class.mj-voice-picker__item--selected]=\"IsSelected(agent)\"\n [title]=\"agent.Description || agent.Name\"\n (click)=\"Select(agent)\"\n (dblclick)=\"Pick(agent)\">\n <i [class]=\"IconClassFor(agent)\"></i>\n <span class=\"mj-voice-picker__item-name\">{{ agent.Name }}</span>\n @if (IsDefaultChoice(agent)) {\n <span class=\"mj-voice-picker__default-badge\">default</span>\n }\n </button>\n }\n }\n </div>\n @if (CanOverrideSessionConfig && Models.length > 0) {\n <div class=\"mj-voice-picker__select-row mj-voice-picker__select-row--model\">\n <label class=\"mj-voice-picker__select-label\" for=\"mjVoiceModelSelect\">\n <i class=\"fa-solid fa-microchip\"></i>\n <span>Voice model</span>\n </label>\n <select\n #modelSelect\n id=\"mjVoiceModelSelect\"\n class=\"mj-voice-picker__select\"\n [value]=\"SelectedModelId ?? ''\"\n (change)=\"OnModelChange(modelSelect.value)\">\n <option value=\"\">Auto (recommended)</option>\n @for (model of Models; track model.ID) {\n <option [value]=\"model.ID\">{{ model.Name }}</option>\n }\n </select>\n </div>\n }\n <div class=\"mj-voice-picker__footer\">\n <button mjButton variant=\"primary\" size=\"sm\" [disabled]=\"!SelectedAgent\" (click)=\"StartCall()\">\n <i class=\"fa-solid fa-phone\"></i> Start\n </button>\n <button mjButton variant=\"outline\" size=\"sm\" (click)=\"Cancel()\">Cancel</button>\n </div>\n </div>\n `,\n styles: [`\n :host {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 8px;\n z-index: 60;\n }\n .mj-voice-picker {\n width: 300px;\n display: flex; flex-direction: column;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n overflow: hidden;\n }\n .mj-voice-picker__header {\n display: flex; align-items: center; gap: 8px;\n padding: 10px 12px 8px;\n font-size: 13px; font-weight: 600;\n color: var(--mj-text-primary);\n }\n .mj-voice-picker__header i { color: var(--mj-brand-primary); }\n .mj-voice-picker__search {\n display: flex; align-items: center; gap: 8px;\n margin: 0 12px 8px;\n padding: 0 10px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 6px;\n }\n .mj-voice-picker__search i { font-size: 12px; color: var(--mj-text-muted); }\n .mj-voice-picker__search input {\n flex: 1; min-width: 0;\n padding: 7px 0;\n background: transparent; border: none; outline: none;\n font-size: 13px; color: var(--mj-text-primary);\n }\n .mj-voice-picker__search input::placeholder { color: var(--mj-text-disabled); }\n .mj-voice-picker__search:focus-within { border-color: var(--mj-border-focus); }\n .mj-voice-picker__list {\n max-height: 220px; overflow-y: auto;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n padding: 4px 0;\n }\n .mj-voice-picker__item {\n display: flex; align-items: center; gap: 8px; width: 100%;\n padding: 7px 12px;\n background: transparent; border: none; cursor: pointer;\n color: var(--mj-text-primary); font-size: 13px; text-align: left;\n }\n .mj-voice-picker__item i { width: 16px; text-align: center; color: var(--mj-text-secondary); }\n .mj-voice-picker__item:hover { background: var(--mj-bg-surface-hover); }\n .mj-voice-picker__item--selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n }\n .mj-voice-picker__item--selected i { color: var(--mj-brand-primary); }\n .mj-voice-picker__item-name {\n flex: 1; min-width: 0;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .mj-voice-picker__default-badge {\n flex-shrink: 0;\n padding: 1px 6px;\n border-radius: 999px;\n font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n }\n .mj-voice-picker__empty {\n padding: 12px; font-size: 12px; color: var(--mj-text-muted); text-align: center;\n }\n .mj-voice-picker__select-row {\n display: flex; align-items: center; gap: 8px;\n padding: 0 12px 8px;\n }\n .mj-voice-picker__select-row--model {\n padding: 8px 12px 0;\n }\n .mj-voice-picker__select-label {\n display: flex; align-items: center; gap: 6px;\n flex-shrink: 0;\n font-size: 12px; color: var(--mj-text-secondary);\n }\n .mj-voice-picker__select-label i { font-size: 11px; color: var(--mj-text-muted); }\n .mj-voice-picker__select {\n flex: 1; min-width: 0;\n padding: 5px 8px;\n font-size: 12px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 6px;\n outline: none;\n }\n .mj-voice-picker__select:focus { border-color: var(--mj-border-focus); }\n .mj-voice-picker__footer {\n display: flex; align-items: center; gap: 8px;\n padding: 10px 12px;\n }\n `]\n})\nexport class RealtimeAgentPickerComponent extends BaseAngularComponent implements OnInit, AfterViewInit {\n /** Agents the user can call — same cached set the @mention / routing logic uses. */\n @Input() Agents: MJAIAgentEntityExtended[] = [];\n\n /** The agent the default resolution would pick — preselected and listed first. */\n @Input() DefaultAgentId: string | null = null;\n\n /**\n * The ACTIVE Realtime-type co-agent candidates the user can run (host-filtered from\n * the same cached, run-permission-filtered agent set as {@link Agents}). The co-agent\n * selector renders only when MORE THAN ONE candidate exists — with zero or one\n * there's nothing meaningful to choose and the server's resolution chain applies.\n */\n @Input() CoAgents: MJAIAgentEntityExtended[] = [];\n\n /**\n * The user's persisted co-agent preference (host-loaded via `UserInfoEngine`) —\n * preselected in the co-agent selector when it's still a valid candidate.\n */\n @Input() DefaultCoAgentId: string | null = null;\n\n /** Emitted with the chosen agent + optional explicit voice model / co-agent when the user confirms. */\n @Output() AgentPicked = new EventEmitter<RealtimeAgentPick>();\n\n /** Emitted when the user dismisses without starting a call. */\n @Output() Cancelled = new EventEmitter<void>();\n\n @ViewChild('searchInput') private searchInput?: ElementRef<HTMLInputElement>;\n\n public SearchText = '';\n public SelectedAgentId: string | null = null;\n\n /** Active Realtime models the model selector offers (loaded only for authorized users). */\n public Models: RealtimeModelOption[] = [];\n\n /** The explicitly chosen voice model id, or `null` for \"Auto (recommended)\" (the default). */\n public SelectedModelId: string | null = null;\n\n /** The explicitly chosen co-agent id, or `null` for \"Auto (recommended)\" (the server's chain). */\n public SelectedCoAgentId: string | null = null;\n\n /**\n * Whether the current user holds the `Realtime: Advanced Session Controls`\n * authorization. Gates the voice-model selector (and any future config-override\n * controls) — pure disclosure; the server enforces the authorization on the mint.\n */\n public CanOverrideSessionConfig = false;\n\n /** The selected co-agent's pairing rows; empty = universal co-agent (no constraint). */\n public Pairings: RealtimePairedAgentRow[] = [];\n\n // Template helper — expose the shared UUID comparator to the view.\n public readonly UUIDsEqual = UUIDsEqual;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n /** Monotonic guard so a stale pairing load can't clobber a newer co-agent choice. */\n private pairingLoadToken = 0;\n\n /** Co-agent selector visibility: only when there is a real choice to make. */\n public get ShowCoAgentSelect(): boolean {\n return this.CoAgents.length > 1;\n }\n\n /** True while the agent list is constrained to the selected co-agent's paired targets. */\n public get IsConstrainedByPairings(): boolean {\n return this.Pairings.length > 0;\n }\n\n public ngOnInit(): void {\n const initial = this.DefaultAgentId && this.findAgent(this.DefaultAgentId)\n ? this.DefaultAgentId\n : this.FilteredAgents[0]?.ID ?? null;\n this.SelectedAgentId = initial;\n this.CanOverrideSessionConfig = UserHoldsAuthorization(\n this.ProviderToUse?.CurrentUser,\n REALTIME_ADVANCED_SESSION_CONTROLS,\n this.ProviderToUse\n );\n if (this.CanOverrideSessionConfig) {\n void this.loadVoiceModels();\n }\n // Preselect the persisted co-agent preference when it's still a valid candidate.\n if (this.DefaultCoAgentId && this.CoAgents.some(a => UUIDsEqual(a.ID, this.DefaultCoAgentId))) {\n this.SelectedCoAgentId = this.DefaultCoAgentId;\n void this.reloadPairings();\n }\n }\n\n /**\n * Loads the \"Voice model\" options: active models whose type is `Realtime`, filtered from\n * {@link AIEngineBase}'s already-cached `Models` (provider-scoped engine instance, lazy\n * `Config` — no RunView round-trip). Called ONLY for users who hold the\n * advanced-session-controls authorization. Tolerant — a failure simply leaves\n * the selector hidden (the server's automatic selection still applies).\n */\n private async loadVoiceModels(): Promise<void> {\n try {\n const engine = AIEngineBase.GetProviderInstance<AIEngineBase>(this.ProviderToUse, AIEngineBase) as AIEngineBase;\n await engine.Config(false, undefined, this.ProviderToUse);\n this.Models = BuildRealtimeModelOptions(engine.Models ?? []);\n } catch (error) {\n console.error('[RealtimeAgentPicker] Failed to load realtime models:', error);\n this.Models = [];\n }\n this.cdr.markForCheck();\n }\n\n /** Records the voice-model choice (`''` = Auto → `null`). */\n public OnModelChange(value: string): void {\n this.SelectedModelId = value && value.length > 0 ? value : null;\n this.cdr.markForCheck();\n }\n\n /** Records the co-agent choice (`''` = Auto → `null`) and refreshes the pairing constraint. */\n public OnCoAgentChange(value: string): void {\n this.SelectedCoAgentId = value && value.length > 0 ? value : null;\n void this.reloadPairings();\n }\n\n /**\n * Loads the selected co-agent's pairing rows and re-applies the target constraint:\n * with rows present, the agent list narrows to the paired targets (Sequence order)\n * and the IsDefault row's target is preselected; with zero rows (or Auto co-agent)\n * the list returns to the unconstrained default. Guarded against stale loads when\n * the user changes co-agents quickly.\n */\n private async reloadPairings(): Promise<void> {\n const token = ++this.pairingLoadToken;\n const coAgentId = this.SelectedCoAgentId;\n const pairings = coAgentId ? await LoadCoAgentPairings(this.ProviderToUse, coAgentId) : [];\n if (token !== this.pairingLoadToken) {\n return; // a newer co-agent choice superseded this load\n }\n this.Pairings = pairings;\n this.ensureSelectionVisible();\n this.cdr.markForCheck();\n }\n\n /**\n * Keeps the target selection coherent after the visible list changes: prefers the\n * pairing default (when constrained), then the host default, then keeps the current\n * selection when still visible, then the first visible row.\n */\n private ensureSelectionVisible(): void {\n const visible = this.FilteredAgents;\n if (this.IsConstrainedByPairings) {\n const pairedDefault = DefaultPairedTargetId(this.Pairings);\n if (pairedDefault && visible.some(a => UUIDsEqual(a.ID, pairedDefault))) {\n this.SelectedAgentId = pairedDefault;\n return;\n }\n }\n if (this.SelectedAgentId && visible.some(a => UUIDsEqual(a.ID, this.SelectedAgentId))) {\n return; // current selection still valid\n }\n if (this.DefaultAgentId && visible.some(a => UUIDsEqual(a.ID, this.DefaultAgentId))) {\n this.SelectedAgentId = this.DefaultAgentId;\n return;\n }\n this.SelectedAgentId = visible[0]?.ID ?? null;\n }\n\n public ngAfterViewInit(): void {\n // Microtask so focus lands after the popover renders without\n // triggering ExpressionChangedAfterItHasBeenChecked.\n Promise.resolve().then(() => this.searchInput?.nativeElement.focus());\n }\n\n /**\n * The visible target list: pairing-constrained first (a co-agent WITH pairing rows may\n * only front its paired targets, kept in pairing Sequence order), then search-filtered.\n * Unconstrained lists keep the default-first + alphabetical ordering.\n */\n public get FilteredAgents(): MJAIAgentEntityExtended[] {\n const base = ConstrainTargetsToPairings(this.Agents, this.Pairings);\n const needle = this.SearchText.trim().toLowerCase();\n const filtered = base.filter(a => !needle || (a.Name ?? '').toLowerCase().includes(needle));\n if (this.IsConstrainedByPairings) {\n return filtered; // pairing Sequence order is the canonical order\n }\n return filtered.sort((a, b) => {\n const aDefault = this.isDefault(a) ? 0 : 1;\n const bDefault = this.isDefault(b) ? 0 : 1;\n if (aDefault !== bDefault) return aDefault - bDefault;\n return (a.Name ?? '').localeCompare(b.Name ?? '');\n });\n }\n\n public get SelectedAgent(): MJAIAgentEntityExtended | null {\n return this.SelectedAgentId ? this.findAgent(this.SelectedAgentId) : null;\n }\n\n public IsSelected(agent: MJAIAgentEntityExtended): boolean {\n return !!this.SelectedAgentId && UUIDsEqual(agent.ID, this.SelectedAgentId);\n }\n\n /**\n * Whether `agent` carries the \"default\" badge: under a pairing constraint that's the\n * co-agent's IsDefault target; otherwise the host-resolved default agent.\n */\n public IsDefaultChoice(agent: MJAIAgentEntityExtended): boolean {\n if (this.IsConstrainedByPairings) {\n const pairedDefault = DefaultPairedTargetId(this.Pairings);\n return !!pairedDefault && UUIDsEqual(agent.ID, pairedDefault);\n }\n return this.isDefault(agent);\n }\n\n public IconClassFor(agent: MJAIAgentEntityExtended): string {\n return agent.IconClass?.trim() || 'fa-solid fa-robot';\n }\n\n public OnSearchInput(value: string): void {\n this.SearchText = value;\n // Keep the selection on a visible row.\n const visible = this.FilteredAgents;\n if (!visible.some(a => this.IsSelected(a))) {\n this.SelectedAgentId = visible[0]?.ID ?? null;\n }\n this.cdr.markForCheck();\n }\n\n /** Arrow-key navigation through the filtered list. */\n public MoveSelection(delta: number): void {\n const visible = this.FilteredAgents;\n if (visible.length === 0) return;\n const currentIndex = visible.findIndex(a => this.IsSelected(a));\n const nextIndex = currentIndex < 0\n ? 0\n : Math.min(visible.length - 1, Math.max(0, currentIndex + delta));\n this.SelectedAgentId = visible[nextIndex].ID;\n this.cdr.markForCheck();\n }\n\n public Select(agent: MJAIAgentEntityExtended): void {\n this.SelectedAgentId = agent.ID;\n this.cdr.markForCheck();\n }\n\n /** Confirm a specific agent immediately (double-click shortcut). */\n public Pick(agent: MJAIAgentEntityExtended): void {\n this.AgentPicked.emit(this.buildPick(agent));\n }\n\n public StartCall(): void {\n const agent = this.SelectedAgent;\n if (agent) {\n this.AgentPicked.emit(this.buildPick(agent));\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n /** Dismiss when the user clicks anywhere outside the popover. (The phone\n * button that opens it stops propagation, so the opening click never\n * reaches this handler.) */\n @HostListener('document:click', ['$event'])\n public OnDocumentClick(event: MouseEvent): void {\n if (!this.elementRef.nativeElement.contains(event.target as Node)) {\n this.Cancelled.emit();\n }\n }\n\n @HostListener('document:keydown.escape')\n public OnEscape(): void {\n this.Cancelled.emit();\n }\n\n /**\n * Assembles the emitted pick. The model preference is hard-gated on the\n * authorization here (defense in depth — unauthorized users never see the selector,\n * so `SelectedModelId` should already be null).\n */\n private buildPick(agent: MJAIAgentEntityExtended): RealtimeAgentPick {\n return {\n Agent: agent,\n PreferredModelId: this.CanOverrideSessionConfig ? this.SelectedModelId : null,\n CoAgentId: this.SelectedCoAgentId\n };\n }\n\n private isDefault(agent: MJAIAgentEntityExtended): boolean {\n return !!this.DefaultAgentId && UUIDsEqual(agent.ID, this.DefaultAgentId);\n }\n\n private findAgent(id: string): MJAIAgentEntityExtended | null {\n return this.Agents.find(a => UUIDsEqual(a.ID, id)) ?? null;\n }\n}\n"]}
1
+ {"version":3,"file":"realtime-agent-picker.component.js","sourceRoot":"","sources":["../../../../src/lib/components/realtime/realtime-agent-picker.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n OnInit,\n Output,\n ViewChild,\n AfterViewInit,\n inject,\n} from '@angular/core';\nimport type { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { REALTIME_ADVANCED_SESSION_CONTROLS, UserHoldsAuthorization } from '../../services/user-authorization';\nimport {\n BuildRealtimeModelOptions,\n ConstrainTargetsToPairings,\n DefaultPairedTargetId,\n LoadCoAgentPairings,\n RealtimeModelOption,\n RealtimePairedAgentRow,\n} from '../../services/realtime-pairing';\nimport { GraphQLLiveKitClient, GraphQLDataProvider, RealtimeModelVoices, RealtimeVoiceOption } from '@memberjunction/graphql-dataprovider';\n\n/**\n * The user's confirmed choice from the realtime picker: the agent to call, an optional\n * EXPLICIT realtime model (`null` = \"Auto (recommended)\", i.e. let the server pick —\n * always `null` for users without the `Realtime: Advanced Session Controls`\n * authorization, who never see the model selector), and an optional EXPLICIT co-agent\n * (`null` = the server's co-agent resolution chain).\n */\nexport interface RealtimeAgentPick {\n /** The agent the voice call should front. */\n Agent: MJAIAgentEntityExtended;\n /** Explicit realtime model id, or `null` for the server's automatic selection. */\n PreferredModelId: string | null;\n /** Explicit provider-native voice id (e.g. `echo`), or `null` for the configured/default voice. */\n PreferredVoice: string | null;\n /** Explicit co-agent id (`MJ: AI Agents.ID`, Realtime type), or `null` for the server's resolution chain. */\n CoAgentId: string | null;\n}\n\n/**\n * Compact anchored popover that lets the user choose WHICH agent a realtime\n * voice call should front — shown by {@link MessageInputComponent} when the\n * phone button is clicked on a conversation with **no prior agent\n * participation** (new / empty conversation), and on demand via the caret\n * button next to the phone (any conversation), where it doubles as the way\n * to pick a specific **co-agent** (the Realtime-type agent that voices the\n * call) and — for authorized users — a specific **voice model**. The plain\n * phone click on an existing conversation keeps its friction-free \"call the\n * resolved agent immediately\" behavior.\n *\n * Mostly dumb: the host supplies the agent list (the same cached set the\n * @mention autocomplete / routing logic uses), the Realtime-type co-agent\n * candidates (filtered from that same set), and the defaults to preselect;\n * the picker emits the user's choice. The data it loads itself:\n * - the compact \"Voice model\" option list (active Realtime models via a\n * narrow RunView) — loaded and rendered ONLY when the current user holds\n * the `Realtime: Advanced Session Controls` authorization (pure UX\n * disclosure; the server enforces the authorization on the mint), and\n * - the chosen co-agent's pairing rows (`MJ: AI Agent Co Agents`, Active 'CoAgent'-type rows): a\n * co-agent with pairing rows may only front its paired targets, so the\n * agent list constrains to those rows (Sequence order) with the IsDefault\n * row preselected; a co-agent with zero rows is universal and the list is\n * untouched.\n *\n * It does NOT persist anything (the host persists the co-agent preference\n * via `UserInfoEngine`; `mj-conversation-agent-picker` pins\n * `MJConversationEntity.DefaultAgentID` — different jobs, which is why this\n * is a separate component).\n *\n * Anchors itself bottom-right above the composer — the host's\n * `.message-input-wrapper` is `position: relative`.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-agent-picker',\n imports: [MJButtonDirective],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"mj-voice-picker\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-voice-picker__header\">\n <i class=\"fa-solid fa-phone\"></i>\n <span>Start a voice call with…</span>\n </div>\n @if (ShowCoAgentSelect) {\n <div class=\"mj-voice-picker__select-row\">\n <label class=\"mj-voice-picker__select-label\" for=\"mjVoiceCoAgentSelect\">\n <i class=\"fa-solid fa-headset\"></i>\n <span>Co-agent</span>\n </label>\n <select\n #coAgentSelect\n id=\"mjVoiceCoAgentSelect\"\n class=\"mj-voice-picker__select\"\n [value]=\"SelectedCoAgentId ?? ''\"\n (change)=\"OnCoAgentChange(coAgentSelect.value)\">\n <option value=\"\">Auto (recommended)</option>\n @for (coAgent of CoAgents; track coAgent.ID) {\n <option [value]=\"coAgent.ID\">{{ coAgent.Name }}</option>\n }\n </select>\n </div>\n }\n <div class=\"mj-voice-picker__search\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <input\n #searchInput\n type=\"text\"\n placeholder=\"Search agents\"\n [value]=\"SearchText\"\n (input)=\"OnSearchInput(searchInput.value)\"\n (keydown.enter)=\"StartCall()\"\n (keydown.arrowdown)=\"MoveSelection(1); $event.preventDefault()\"\n (keydown.arrowup)=\"MoveSelection(-1); $event.preventDefault()\" />\n </div>\n <div class=\"mj-voice-picker__list\">\n @if (FilteredAgents.length === 0) {\n <div class=\"mj-voice-picker__empty\">{{ IsConstrainedByPairings ? 'No paired agents available' : 'No matching agents' }}</div>\n } @else {\n @for (agent of FilteredAgents; track agent.ID) {\n <button\n type=\"button\"\n class=\"mj-voice-picker__item\"\n [class.mj-voice-picker__item--selected]=\"IsSelected(agent)\"\n [title]=\"agent.Description || agent.Name\"\n (click)=\"Select(agent)\"\n (dblclick)=\"Pick(agent)\">\n <i [class]=\"IconClassFor(agent)\"></i>\n <span class=\"mj-voice-picker__item-name\">{{ agent.Name }}</span>\n @if (IsDefaultChoice(agent)) {\n <span class=\"mj-voice-picker__default-badge\">default</span>\n }\n </button>\n }\n }\n </div>\n @if (CanOverrideSessionConfig && Models.length > 0) {\n <div class=\"mj-voice-picker__select-row mj-voice-picker__select-row--model\">\n <label class=\"mj-voice-picker__select-label\" for=\"mjVoiceModelSelect\">\n <i class=\"fa-solid fa-microchip\"></i>\n <span>Voice model</span>\n </label>\n <select\n #modelSelect\n id=\"mjVoiceModelSelect\"\n class=\"mj-voice-picker__select\"\n [value]=\"SelectedModelId ?? ''\"\n (change)=\"OnModelChange(modelSelect.value)\">\n <option value=\"\">Auto (recommended)</option>\n @for (model of Models; track model.ID) {\n <option [value]=\"model.ID\">{{ model.Name }}</option>\n }\n </select>\n </div>\n @if (SelectedModelVoices.length) {\n <div class=\"mj-voice-picker__select-row mj-voice-picker__select-row--model\">\n <label class=\"mj-voice-picker__select-label\" for=\"mjVoiceVoiceSelect\">\n <i class=\"fa-solid fa-waveform-lines\"></i>\n <span>Voice</span>\n </label>\n <select\n #voiceSelect\n id=\"mjVoiceVoiceSelect\"\n class=\"mj-voice-picker__select\"\n [value]=\"SelectedVoiceId ?? ''\"\n (change)=\"OnVoiceChange(voiceSelect.value)\">\n <option value=\"\">Default</option>\n @for (v of SelectedModelVoices; track v.ID) {\n <option [value]=\"v.ID\">{{ v.Name }}</option>\n }\n </select>\n </div>\n }\n }\n <div class=\"mj-voice-picker__footer\">\n <button mjButton variant=\"primary\" size=\"sm\" [disabled]=\"!SelectedAgent\" (click)=\"StartCall()\">\n <i class=\"fa-solid fa-phone\"></i> Start\n </button>\n <button mjButton variant=\"outline\" size=\"sm\" (click)=\"Cancel()\">Cancel</button>\n </div>\n </div>\n `,\n styles: [`\n :host {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 8px;\n z-index: 60;\n }\n .mj-voice-picker {\n width: 300px;\n display: flex; flex-direction: column;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);\n overflow: hidden;\n }\n .mj-voice-picker__header {\n display: flex; align-items: center; gap: 8px;\n padding: 10px 12px 8px;\n font-size: 13px; font-weight: 600;\n color: var(--mj-text-primary);\n }\n .mj-voice-picker__header i { color: var(--mj-brand-primary); }\n .mj-voice-picker__search {\n display: flex; align-items: center; gap: 8px;\n margin: 0 12px 8px;\n padding: 0 10px;\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 6px;\n }\n .mj-voice-picker__search i { font-size: 12px; color: var(--mj-text-muted); }\n .mj-voice-picker__search input {\n flex: 1; min-width: 0;\n padding: 7px 0;\n background: transparent; border: none; outline: none;\n font-size: 13px; color: var(--mj-text-primary);\n }\n .mj-voice-picker__search input::placeholder { color: var(--mj-text-disabled); }\n .mj-voice-picker__search:focus-within { border-color: var(--mj-border-focus); }\n .mj-voice-picker__list {\n max-height: 220px; overflow-y: auto;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n padding: 4px 0;\n }\n .mj-voice-picker__item {\n display: flex; align-items: center; gap: 8px; width: 100%;\n padding: 7px 12px;\n background: transparent; border: none; cursor: pointer;\n color: var(--mj-text-primary); font-size: 13px; text-align: left;\n }\n .mj-voice-picker__item i { width: 16px; text-align: center; color: var(--mj-text-secondary); }\n .mj-voice-picker__item:hover { background: var(--mj-bg-surface-hover); }\n .mj-voice-picker__item--selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n }\n .mj-voice-picker__item--selected i { color: var(--mj-brand-primary); }\n .mj-voice-picker__item-name {\n flex: 1; min-width: 0;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .mj-voice-picker__default-badge {\n flex-shrink: 0;\n padding: 1px 6px;\n border-radius: 999px;\n font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em;\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n }\n .mj-voice-picker__empty {\n padding: 12px; font-size: 12px; color: var(--mj-text-muted); text-align: center;\n }\n .mj-voice-picker__select-row {\n display: flex; align-items: center; gap: 8px;\n padding: 0 12px 8px;\n }\n .mj-voice-picker__select-row--model {\n padding: 8px 12px 0;\n }\n .mj-voice-picker__select-label {\n display: flex; align-items: center; gap: 6px;\n flex-shrink: 0;\n font-size: 12px; color: var(--mj-text-secondary);\n }\n .mj-voice-picker__select-label i { font-size: 11px; color: var(--mj-text-muted); }\n .mj-voice-picker__select {\n flex: 1; min-width: 0;\n padding: 5px 8px;\n font-size: 12px;\n color: var(--mj-text-primary);\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 6px;\n outline: none;\n }\n .mj-voice-picker__select:focus { border-color: var(--mj-border-focus); }\n .mj-voice-picker__footer {\n display: flex; align-items: center; gap: 8px;\n padding: 10px 12px;\n }\n `]\n})\nexport class RealtimeAgentPickerComponent extends BaseAngularComponent implements OnInit, AfterViewInit {\n /** Agents the user can call — same cached set the @mention / routing logic uses. */\n @Input() Agents: MJAIAgentEntityExtended[] = [];\n\n /** The agent the default resolution would pick — preselected and listed first. */\n @Input() DefaultAgentId: string | null = null;\n\n /**\n * The ACTIVE Realtime-type co-agent candidates the user can run (host-filtered from\n * the same cached, run-permission-filtered agent set as {@link Agents}). The co-agent\n * selector renders only when MORE THAN ONE candidate exists — with zero or one\n * there's nothing meaningful to choose and the server's resolution chain applies.\n */\n @Input() CoAgents: MJAIAgentEntityExtended[] = [];\n\n /**\n * The user's persisted co-agent preference (host-loaded via `UserInfoEngine`) —\n * preselected in the co-agent selector when it's still a valid candidate.\n */\n @Input() DefaultCoAgentId: string | null = null;\n\n /** Emitted with the chosen agent + optional explicit voice model / co-agent when the user confirms. */\n @Output() AgentPicked = new EventEmitter<RealtimeAgentPick>();\n\n /** Emitted when the user dismisses without starting a call. */\n @Output() Cancelled = new EventEmitter<void>();\n\n @ViewChild('searchInput') private searchInput?: ElementRef<HTMLInputElement>;\n\n public SearchText = '';\n public SelectedAgentId: string | null = null;\n\n /** Active Realtime models the model selector offers (loaded only for authorized users). */\n public Models: RealtimeModelOption[] = [];\n\n /** The explicitly chosen voice model id, or `null` for \"Auto (recommended)\" (the default). */\n public SelectedModelId: string | null = null;\n\n /** Active Realtime models + their voices (for the voice selector); loaded only for authorized users. */\n public VoiceModels: RealtimeModelVoices[] = [];\n\n /** The explicitly chosen voice id, or `null` for the configured/default voice. */\n public SelectedVoiceId: string | null = null;\n\n /** Voices for the currently-selected model (empty when no model picked or it declares none). */\n public get SelectedModelVoices(): RealtimeVoiceOption[] {\n return this.VoiceModels.find((m) => UUIDsEqual(m.ModelID, this.SelectedModelId))?.Voices ?? [];\n }\n\n /** The explicitly chosen co-agent id, or `null` for \"Auto (recommended)\" (the server's chain). */\n public SelectedCoAgentId: string | null = null;\n\n /**\n * Whether the current user holds the `Realtime: Advanced Session Controls`\n * authorization. Gates the voice-model selector (and any future config-override\n * controls) — pure disclosure; the server enforces the authorization on the mint.\n */\n public CanOverrideSessionConfig = false;\n\n /** The selected co-agent's pairing rows; empty = universal co-agent (no constraint). */\n public Pairings: RealtimePairedAgentRow[] = [];\n\n // Template helper — expose the shared UUID comparator to the view.\n public readonly UUIDsEqual = UUIDsEqual;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);\n /** Monotonic guard so a stale pairing load can't clobber a newer co-agent choice. */\n private pairingLoadToken = 0;\n\n /** Co-agent selector visibility: only when there is a real choice to make. */\n public get ShowCoAgentSelect(): boolean {\n return this.CoAgents.length > 1;\n }\n\n /** True while the agent list is constrained to the selected co-agent's paired targets. */\n public get IsConstrainedByPairings(): boolean {\n return this.Pairings.length > 0;\n }\n\n public ngOnInit(): void {\n const initial = this.DefaultAgentId && this.findAgent(this.DefaultAgentId)\n ? this.DefaultAgentId\n : this.FilteredAgents[0]?.ID ?? null;\n this.SelectedAgentId = initial;\n this.CanOverrideSessionConfig = UserHoldsAuthorization(\n this.ProviderToUse?.CurrentUser,\n REALTIME_ADVANCED_SESSION_CONTROLS,\n this.ProviderToUse\n );\n if (this.CanOverrideSessionConfig) {\n void this.loadVoiceModels();\n }\n // Preselect the persisted co-agent preference when it's still a valid candidate.\n if (this.DefaultCoAgentId && this.CoAgents.some(a => UUIDsEqual(a.ID, this.DefaultCoAgentId))) {\n this.SelectedCoAgentId = this.DefaultCoAgentId;\n void this.reloadPairings();\n }\n }\n\n /**\n * Loads the \"Voice model\" options: active models whose type is `Realtime`, filtered from\n * {@link AIEngineBase}'s already-cached `Models` (provider-scoped engine instance, lazy\n * `Config` — no RunView round-trip). Called ONLY for users who hold the\n * advanced-session-controls authorization. Tolerant — a failure simply leaves\n * the selector hidden (the server's automatic selection still applies).\n */\n private async loadVoiceModels(): Promise<void> {\n try {\n const engine = AIEngineBase.GetProviderInstance<AIEngineBase>(this.ProviderToUse, AIEngineBase) as AIEngineBase;\n await engine.Config(false, undefined, this.ProviderToUse);\n this.Models = BuildRealtimeModelOptions(engine.Models ?? []);\n } catch (error) {\n console.error('[RealtimeAgentPicker] Failed to load realtime models:', error);\n this.Models = [];\n }\n // Voices come from the driver via a server query (the cached models carry no voice list).\n try {\n const client = new GraphQLLiveKitClient(this.ProviderToUse as unknown as GraphQLDataProvider);\n this.VoiceModels = await client.GetRealtimeModelVoices();\n } catch (error) {\n console.error('[RealtimeAgentPicker] Failed to load realtime voices:', error);\n this.VoiceModels = [];\n }\n this.cdr.markForCheck();\n }\n\n /** Records the voice-model choice (`''` = Auto → `null`); clears the voice so it can't outlive a switch. */\n public OnModelChange(value: string): void {\n this.SelectedModelId = value && value.length > 0 ? value : null;\n this.SelectedVoiceId = null;\n this.cdr.markForCheck();\n }\n\n /** Records the voice choice (`''` = default → `null`). */\n public OnVoiceChange(value: string): void {\n this.SelectedVoiceId = value && value.length > 0 ? value : null;\n this.cdr.markForCheck();\n }\n\n /** Records the co-agent choice (`''` = Auto → `null`) and refreshes the pairing constraint. */\n public OnCoAgentChange(value: string): void {\n this.SelectedCoAgentId = value && value.length > 0 ? value : null;\n void this.reloadPairings();\n }\n\n /**\n * Loads the selected co-agent's pairing rows and re-applies the target constraint:\n * with rows present, the agent list narrows to the paired targets (Sequence order)\n * and the IsDefault row's target is preselected; with zero rows (or Auto co-agent)\n * the list returns to the unconstrained default. Guarded against stale loads when\n * the user changes co-agents quickly.\n */\n private async reloadPairings(): Promise<void> {\n const token = ++this.pairingLoadToken;\n const coAgentId = this.SelectedCoAgentId;\n const pairings = coAgentId ? await LoadCoAgentPairings(this.ProviderToUse, coAgentId) : [];\n if (token !== this.pairingLoadToken) {\n return; // a newer co-agent choice superseded this load\n }\n this.Pairings = pairings;\n this.ensureSelectionVisible();\n this.cdr.markForCheck();\n }\n\n /**\n * Keeps the target selection coherent after the visible list changes: prefers the\n * pairing default (when constrained), then the host default, then keeps the current\n * selection when still visible, then the first visible row.\n */\n private ensureSelectionVisible(): void {\n const visible = this.FilteredAgents;\n if (this.IsConstrainedByPairings) {\n const pairedDefault = DefaultPairedTargetId(this.Pairings);\n if (pairedDefault && visible.some(a => UUIDsEqual(a.ID, pairedDefault))) {\n this.SelectedAgentId = pairedDefault;\n return;\n }\n }\n if (this.SelectedAgentId && visible.some(a => UUIDsEqual(a.ID, this.SelectedAgentId))) {\n return; // current selection still valid\n }\n if (this.DefaultAgentId && visible.some(a => UUIDsEqual(a.ID, this.DefaultAgentId))) {\n this.SelectedAgentId = this.DefaultAgentId;\n return;\n }\n this.SelectedAgentId = visible[0]?.ID ?? null;\n }\n\n public ngAfterViewInit(): void {\n // Microtask so focus lands after the popover renders without\n // triggering ExpressionChangedAfterItHasBeenChecked.\n Promise.resolve().then(() => this.searchInput?.nativeElement.focus());\n }\n\n /**\n * The visible target list: pairing-constrained first (a co-agent WITH pairing rows may\n * only front its paired targets, kept in pairing Sequence order), then search-filtered.\n * Unconstrained lists keep the default-first + alphabetical ordering.\n */\n public get FilteredAgents(): MJAIAgentEntityExtended[] {\n const base = ConstrainTargetsToPairings(this.Agents, this.Pairings);\n const needle = this.SearchText.trim().toLowerCase();\n const filtered = base.filter(a => !needle || (a.Name ?? '').toLowerCase().includes(needle));\n if (this.IsConstrainedByPairings) {\n return filtered; // pairing Sequence order is the canonical order\n }\n return filtered.sort((a, b) => {\n const aDefault = this.isDefault(a) ? 0 : 1;\n const bDefault = this.isDefault(b) ? 0 : 1;\n if (aDefault !== bDefault) return aDefault - bDefault;\n return (a.Name ?? '').localeCompare(b.Name ?? '');\n });\n }\n\n public get SelectedAgent(): MJAIAgentEntityExtended | null {\n return this.SelectedAgentId ? this.findAgent(this.SelectedAgentId) : null;\n }\n\n public IsSelected(agent: MJAIAgentEntityExtended): boolean {\n return !!this.SelectedAgentId && UUIDsEqual(agent.ID, this.SelectedAgentId);\n }\n\n /**\n * Whether `agent` carries the \"default\" badge: under a pairing constraint that's the\n * co-agent's IsDefault target; otherwise the host-resolved default agent.\n */\n public IsDefaultChoice(agent: MJAIAgentEntityExtended): boolean {\n if (this.IsConstrainedByPairings) {\n const pairedDefault = DefaultPairedTargetId(this.Pairings);\n return !!pairedDefault && UUIDsEqual(agent.ID, pairedDefault);\n }\n return this.isDefault(agent);\n }\n\n public IconClassFor(agent: MJAIAgentEntityExtended): string {\n return agent.IconClass?.trim() || 'fa-solid fa-robot';\n }\n\n public OnSearchInput(value: string): void {\n this.SearchText = value;\n // Keep the selection on a visible row.\n const visible = this.FilteredAgents;\n if (!visible.some(a => this.IsSelected(a))) {\n this.SelectedAgentId = visible[0]?.ID ?? null;\n }\n this.cdr.markForCheck();\n }\n\n /** Arrow-key navigation through the filtered list. */\n public MoveSelection(delta: number): void {\n const visible = this.FilteredAgents;\n if (visible.length === 0) return;\n const currentIndex = visible.findIndex(a => this.IsSelected(a));\n const nextIndex = currentIndex < 0\n ? 0\n : Math.min(visible.length - 1, Math.max(0, currentIndex + delta));\n this.SelectedAgentId = visible[nextIndex].ID;\n this.cdr.markForCheck();\n }\n\n public Select(agent: MJAIAgentEntityExtended): void {\n this.SelectedAgentId = agent.ID;\n this.cdr.markForCheck();\n }\n\n /** Confirm a specific agent immediately (double-click shortcut). */\n public Pick(agent: MJAIAgentEntityExtended): void {\n this.AgentPicked.emit(this.buildPick(agent));\n }\n\n public StartCall(): void {\n const agent = this.SelectedAgent;\n if (agent) {\n this.AgentPicked.emit(this.buildPick(agent));\n }\n }\n\n public Cancel(): void {\n this.Cancelled.emit();\n }\n\n /** Dismiss when the user clicks anywhere outside the popover. (The phone\n * button that opens it stops propagation, so the opening click never\n * reaches this handler.) */\n @HostListener('document:click', ['$event'])\n public OnDocumentClick(event: MouseEvent): void {\n if (!this.elementRef.nativeElement.contains(event.target as Node)) {\n this.Cancelled.emit();\n }\n }\n\n @HostListener('document:keydown.escape')\n public OnEscape(): void {\n this.Cancelled.emit();\n }\n\n /**\n * Assembles the emitted pick. The model preference is hard-gated on the\n * authorization here (defense in depth — unauthorized users never see the selector,\n * so `SelectedModelId` should already be null).\n */\n private buildPick(agent: MJAIAgentEntityExtended): RealtimeAgentPick {\n return {\n Agent: agent,\n PreferredModelId: this.CanOverrideSessionConfig ? this.SelectedModelId : null,\n PreferredVoice: this.CanOverrideSessionConfig ? this.SelectedVoiceId : null,\n CoAgentId: this.SelectedCoAgentId\n };\n }\n\n private isDefault(agent: MJAIAgentEntityExtended): boolean {\n return !!this.DefaultAgentId && UUIDsEqual(agent.ID, this.DefaultAgentId);\n }\n\n private findAgent(id: string): MJAIAgentEntityExtended | null {\n return this.Agents.find(a => UUIDsEqual(a.ID, id)) ?? null;\n }\n}\n"]}