@vivix-ai/ivi-frontend-sdk 0.3.6 → 0.3.7

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.
package/dist/index.d.cts CHANGED
@@ -628,6 +628,12 @@ interface IVIVolumeControlProps {
628
628
 
629
629
  type IviSubtitleRole = IviRuntimeConversationItem["role"];
630
630
  type IviSubtitleSource = "conversation" | "response_audio_transcript";
631
+ type IviSubtitleCompletedReason = "conversation_done" | "response_done";
632
+ type IviCompletedSubtitleDecision = "keep" | "remove" | {
633
+ removeAfterMs: number;
634
+ };
635
+ type IviCompletedSubtitleDecisionResult = IviCompletedSubtitleDecision | void | Promise<IviCompletedSubtitleDecision | void>;
636
+ declare const DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = 3000;
631
637
  interface IviSubtitleItem {
632
638
  id: string;
633
639
  role: IviSubtitleRole;
@@ -649,6 +655,15 @@ interface IviSubtitleItem {
649
655
  /** 最近一次字幕内容或状态更新的时间戳。 */
650
656
  updatedAt: number;
651
657
  }
658
+ interface IviSubtitleCompletedContext {
659
+ /** 当前字幕队列快照。 */
660
+ entries: readonly IviSubtitleItem[];
661
+ /** 本次完成态来自 conversation 完成还是 response 完成。 */
662
+ reason: IviSubtitleCompletedReason;
663
+ /** 当字幕被移除、会话结束、配置变化或组件卸载时触发。 */
664
+ signal: AbortSignal;
665
+ }
666
+ type IviSubtitleCompletedHandler = (item: IviSubtitleItem, context: IviSubtitleCompletedContext) => IviCompletedSubtitleDecisionResult;
652
667
  interface IviUseSubtitlesOptions {
653
668
  /** 要收集的发言人角色,默认 ["user"]。传单个字符串或数组均可。 */
654
669
  roles?: IviSubtitleRole | IviSubtitleRole[];
@@ -660,6 +675,16 @@ interface IviUseSubtitlesOptions {
660
675
  * 只有 response.done 才会把这一轮 model 字幕标记为 done。
661
676
  */
662
677
  useModelStreamingTranscript?: boolean;
678
+ /**
679
+ * 字幕进入完成态后多久自动移除,默认 3000ms;传 false 可恢复为一直保留直到被 maxItems 裁剪。
680
+ * 自定义 onSubtitleCompleted 时,此配置仅作为未提供自定义策略时的默认策略。
681
+ */
682
+ hideCompletedAfterMs?: number | false;
683
+ /**
684
+ * 字幕进入完成态时调用一次。返回 "remove" 立即删除,返回 { removeAfterMs } 延迟删除,
685
+ * 返回 "keep" 或 undefined 则保留。异步策略可通过 context.signal 响应取消。
686
+ */
687
+ onSubtitleCompleted?: IviSubtitleCompletedHandler;
663
688
  }
664
689
  /**
665
690
  * 监听 runtime 中的 conversation/response 事件,并维护当前应展示的字幕队列。
@@ -689,6 +714,10 @@ interface IVISubtitleOverlayProps {
689
714
  * 当 roles 包含 "model" 时,是否使用 response.output_audio_transcript.* 事件作为 model 字幕来源。
690
715
  */
691
716
  useModelStreamingTranscript?: boolean;
717
+ /** 字幕进入完成态后多久自动移除,默认 3000ms;传 false 可关闭自动移除。 */
718
+ hideCompletedAfterMs?: IviUseSubtitlesOptions["hideCompletedAfterMs"];
719
+ /** 字幕进入完成态时的自定义保留/删除策略,优先于 hideCompletedAfterMs。 */
720
+ onSubtitleCompleted?: IviUseSubtitlesOptions["onSubtitleCompleted"];
692
721
  /** 样式配置 */
693
722
  subtitleStyle?: IVISubtitleOverlayStyle;
694
723
  /** 自定义类名 */
@@ -773,4 +802,4 @@ declare function useManagedIviRuntime(config: IviManagedRuntimeConfig): IviRunti
773
802
 
774
803
  declare function useIviStageView(): IviStageViewContextValue;
775
804
 
776
- export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcAIDenoiserMode, type IviRuntimeTrtcAIDenoiserOptions, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleItem, type IviSubtitleRole, type IviSubtitleSource, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
805
+ export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviCompletedSubtitleDecision, type IviCompletedSubtitleDecisionResult, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcAIDenoiserMode, type IviRuntimeTrtcAIDenoiserOptions, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleCompletedContext, type IviSubtitleCompletedHandler, type IviSubtitleCompletedReason, type IviSubtitleItem, type IviSubtitleRole, type IviSubtitleSource, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
package/dist/index.d.ts CHANGED
@@ -628,6 +628,12 @@ interface IVIVolumeControlProps {
628
628
 
629
629
  type IviSubtitleRole = IviRuntimeConversationItem["role"];
630
630
  type IviSubtitleSource = "conversation" | "response_audio_transcript";
631
+ type IviSubtitleCompletedReason = "conversation_done" | "response_done";
632
+ type IviCompletedSubtitleDecision = "keep" | "remove" | {
633
+ removeAfterMs: number;
634
+ };
635
+ type IviCompletedSubtitleDecisionResult = IviCompletedSubtitleDecision | void | Promise<IviCompletedSubtitleDecision | void>;
636
+ declare const DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = 3000;
631
637
  interface IviSubtitleItem {
632
638
  id: string;
633
639
  role: IviSubtitleRole;
@@ -649,6 +655,15 @@ interface IviSubtitleItem {
649
655
  /** 最近一次字幕内容或状态更新的时间戳。 */
650
656
  updatedAt: number;
651
657
  }
658
+ interface IviSubtitleCompletedContext {
659
+ /** 当前字幕队列快照。 */
660
+ entries: readonly IviSubtitleItem[];
661
+ /** 本次完成态来自 conversation 完成还是 response 完成。 */
662
+ reason: IviSubtitleCompletedReason;
663
+ /** 当字幕被移除、会话结束、配置变化或组件卸载时触发。 */
664
+ signal: AbortSignal;
665
+ }
666
+ type IviSubtitleCompletedHandler = (item: IviSubtitleItem, context: IviSubtitleCompletedContext) => IviCompletedSubtitleDecisionResult;
652
667
  interface IviUseSubtitlesOptions {
653
668
  /** 要收集的发言人角色,默认 ["user"]。传单个字符串或数组均可。 */
654
669
  roles?: IviSubtitleRole | IviSubtitleRole[];
@@ -660,6 +675,16 @@ interface IviUseSubtitlesOptions {
660
675
  * 只有 response.done 才会把这一轮 model 字幕标记为 done。
661
676
  */
662
677
  useModelStreamingTranscript?: boolean;
678
+ /**
679
+ * 字幕进入完成态后多久自动移除,默认 3000ms;传 false 可恢复为一直保留直到被 maxItems 裁剪。
680
+ * 自定义 onSubtitleCompleted 时,此配置仅作为未提供自定义策略时的默认策略。
681
+ */
682
+ hideCompletedAfterMs?: number | false;
683
+ /**
684
+ * 字幕进入完成态时调用一次。返回 "remove" 立即删除,返回 { removeAfterMs } 延迟删除,
685
+ * 返回 "keep" 或 undefined 则保留。异步策略可通过 context.signal 响应取消。
686
+ */
687
+ onSubtitleCompleted?: IviSubtitleCompletedHandler;
663
688
  }
664
689
  /**
665
690
  * 监听 runtime 中的 conversation/response 事件,并维护当前应展示的字幕队列。
@@ -689,6 +714,10 @@ interface IVISubtitleOverlayProps {
689
714
  * 当 roles 包含 "model" 时,是否使用 response.output_audio_transcript.* 事件作为 model 字幕来源。
690
715
  */
691
716
  useModelStreamingTranscript?: boolean;
717
+ /** 字幕进入完成态后多久自动移除,默认 3000ms;传 false 可关闭自动移除。 */
718
+ hideCompletedAfterMs?: IviUseSubtitlesOptions["hideCompletedAfterMs"];
719
+ /** 字幕进入完成态时的自定义保留/删除策略,优先于 hideCompletedAfterMs。 */
720
+ onSubtitleCompleted?: IviUseSubtitlesOptions["onSubtitleCompleted"];
692
721
  /** 样式配置 */
693
722
  subtitleStyle?: IVISubtitleOverlayStyle;
694
723
  /** 自定义类名 */
@@ -773,4 +802,4 @@ declare function useManagedIviRuntime(config: IviManagedRuntimeConfig): IviRunti
773
802
 
774
803
  declare function useIviStageView(): IviStageViewContextValue;
775
804
 
776
- export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcAIDenoiserMode, type IviRuntimeTrtcAIDenoiserOptions, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleItem, type IviSubtitleRole, type IviSubtitleSource, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
805
+ export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviCompletedSubtitleDecision, type IviCompletedSubtitleDecisionResult, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcAIDenoiserMode, type IviRuntimeTrtcAIDenoiserOptions, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleCompletedContext, type IviSubtitleCompletedHandler, type IviSubtitleCompletedReason, type IviSubtitleItem, type IviSubtitleRole, type IviSubtitleSource, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
package/dist/index.js CHANGED
@@ -3078,9 +3078,12 @@ function useApplyVolumeToSlot(containerRef, volume, enabled, activeSourceId) {
3078
3078
  return () => observer.disconnect();
3079
3079
  }, [containerRef, volume, enabled, activeSourceId]);
3080
3080
  }
3081
+ var DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = 3e3;
3081
3082
  function useIviSubtitles(runtime, options = {}) {
3082
3083
  const roles = options.roles ?? "user";
3083
3084
  const maxItems = normalizeMaxItems(options.maxItems);
3085
+ const hideCompletedAfterMs = normalizeHideCompletedAfterMs(options.hideCompletedAfterMs);
3086
+ const onSubtitleCompleted = options.onSubtitleCompleted;
3084
3087
  const roleKey = Array.isArray(roles) ? roles.join("\0") : roles;
3085
3088
  const roleSet = useMemo(
3086
3089
  () => new Set(roleKey.split("\0")),
@@ -3090,9 +3093,14 @@ function useIviSubtitles(runtime, options = {}) {
3090
3093
  const [subtitles, setSubtitles] = useState([]);
3091
3094
  const seenIdsRef = useRef(/* @__PURE__ */ new Set());
3092
3095
  const initializedRef = useRef(false);
3096
+ const completedSubtitleIdsRef = useRef(/* @__PURE__ */ new Set());
3097
+ const completionControllersRef = useRef(/* @__PURE__ */ new Map());
3098
+ const completionPolicyRef = useRef(null);
3093
3099
  useEffect(() => {
3094
3100
  seenIdsRef.current = /* @__PURE__ */ new Set();
3095
3101
  initializedRef.current = false;
3102
+ completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
3103
+ abortCompletedSubtitleTasks(completionControllersRef.current);
3096
3104
  setSubtitles([]);
3097
3105
  if (!runtime) {
3098
3106
  return;
@@ -3148,6 +3156,8 @@ function useIviSubtitles(runtime, options = {}) {
3148
3156
  if (event.type === "session.ended") {
3149
3157
  seenIdsRef.current = /* @__PURE__ */ new Set();
3150
3158
  initializedRef.current = false;
3159
+ completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
3160
+ abortCompletedSubtitleTasks(completionControllersRef.current);
3151
3161
  setSubtitles([]);
3152
3162
  return;
3153
3163
  }
@@ -3165,6 +3175,78 @@ function useIviSubtitles(runtime, options = {}) {
3165
3175
  syncConversations(state.conversations);
3166
3176
  });
3167
3177
  }, [runtime, roleSet, maxItems, useModelStreamingTranscript]);
3178
+ useEffect(() => {
3179
+ return () => {
3180
+ completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
3181
+ abortCompletedSubtitleTasks(completionControllersRef.current);
3182
+ };
3183
+ }, []);
3184
+ useEffect(() => {
3185
+ const previousPolicy = completionPolicyRef.current;
3186
+ const policyChanged = Boolean(
3187
+ previousPolicy && (previousPolicy.hideCompletedAfterMs !== hideCompletedAfterMs || previousPolicy.onSubtitleCompleted !== onSubtitleCompleted)
3188
+ );
3189
+ completionPolicyRef.current = { hideCompletedAfterMs, onSubtitleCompleted };
3190
+ if (policyChanged) {
3191
+ completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
3192
+ abortCompletedSubtitleTasks(completionControllersRef.current);
3193
+ }
3194
+ const subtitleById = new Map(subtitles.map((item) => [item.id, item]));
3195
+ const completedIds = completedSubtitleIdsRef.current;
3196
+ const controllers = completionControllersRef.current;
3197
+ for (const [id, controller] of Array.from(controllers)) {
3198
+ const item = subtitleById.get(id);
3199
+ if (!item || !isCompletedSubtitle(item)) {
3200
+ controller.abort();
3201
+ controllers.delete(id);
3202
+ completedIds.delete(id);
3203
+ }
3204
+ }
3205
+ for (const id of Array.from(completedIds)) {
3206
+ const item = subtitleById.get(id);
3207
+ if (!item || !isCompletedSubtitle(item)) {
3208
+ completedIds.delete(id);
3209
+ }
3210
+ }
3211
+ for (const item of subtitles) {
3212
+ if (!isCompletedSubtitle(item) || completedIds.has(item.id)) {
3213
+ continue;
3214
+ }
3215
+ completedIds.add(item.id);
3216
+ const controller = new AbortController();
3217
+ controllers.set(item.id, controller);
3218
+ const context = {
3219
+ entries: subtitles,
3220
+ reason: getSubtitleCompletedReason(item),
3221
+ signal: controller.signal
3222
+ };
3223
+ let decisionResult;
3224
+ try {
3225
+ decisionResult = onSubtitleCompleted ? onSubtitleCompleted(item, context) : getDefaultCompletedSubtitleDecision(hideCompletedAfterMs);
3226
+ } catch {
3227
+ decisionResult = "keep";
3228
+ }
3229
+ void Promise.resolve(decisionResult).then((decision) => {
3230
+ applyCompletedSubtitleDecision(
3231
+ item.id,
3232
+ decision,
3233
+ controller,
3234
+ controllers,
3235
+ completedIds,
3236
+ (id) => setSubtitles((previous) => removeSubtitleById(previous, id))
3237
+ );
3238
+ }).catch(() => {
3239
+ applyCompletedSubtitleDecision(
3240
+ item.id,
3241
+ "keep",
3242
+ controller,
3243
+ controllers,
3244
+ completedIds,
3245
+ (id) => setSubtitles((previous) => removeSubtitleById(previous, id))
3246
+ );
3247
+ });
3248
+ }
3249
+ }, [subtitles, hideCompletedAfterMs, onSubtitleCompleted]);
3168
3250
  return subtitles;
3169
3251
  }
3170
3252
  function normalizeMaxItems(maxItems) {
@@ -3176,6 +3258,35 @@ function normalizeMaxItems(maxItems) {
3176
3258
  }
3177
3259
  return Math.max(0, Math.floor(maxItems));
3178
3260
  }
3261
+ function normalizeHideCompletedAfterMs(hideCompletedAfterMs) {
3262
+ if (hideCompletedAfterMs === false) {
3263
+ return false;
3264
+ }
3265
+ if (hideCompletedAfterMs === void 0 || !Number.isFinite(hideCompletedAfterMs)) {
3266
+ return DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS;
3267
+ }
3268
+ return Math.max(0, Math.floor(hideCompletedAfterMs));
3269
+ }
3270
+ function getDefaultCompletedSubtitleDecision(hideCompletedAfterMs) {
3271
+ if (hideCompletedAfterMs === false) {
3272
+ return "keep";
3273
+ }
3274
+ return hideCompletedAfterMs === 0 ? "remove" : { removeAfterMs: hideCompletedAfterMs };
3275
+ }
3276
+ function normalizeCompletedSubtitleDecision(decision) {
3277
+ if (decision === "keep" || decision === "remove") {
3278
+ return decision;
3279
+ }
3280
+ if (decision && typeof decision === "object" && "removeAfterMs" in decision) {
3281
+ const removeAfterMs = Number(decision.removeAfterMs);
3282
+ if (!Number.isFinite(removeAfterMs)) {
3283
+ return "keep";
3284
+ }
3285
+ const normalized = Math.max(0, Math.floor(removeAfterMs));
3286
+ return normalized === 0 ? "remove" : { removeAfterMs: normalized };
3287
+ }
3288
+ return "keep";
3289
+ }
3179
3290
  function shouldUseConversationRole(role, roleSet, useModelStreamingTranscript) {
3180
3291
  if (!roleSet.has(role)) {
3181
3292
  return false;
@@ -3189,6 +3300,54 @@ function trimSubtitleItems(items, maxItems) {
3189
3300
  const sorted = [...items].sort((a, b) => a.timestamp - b.timestamp);
3190
3301
  return sorted.length > maxItems ? sorted.slice(sorted.length - maxItems) : sorted;
3191
3302
  }
3303
+ function isCompletedSubtitle(item) {
3304
+ return item.lifecycle === "done" || item.status === "completed" || item.status === "incomplete";
3305
+ }
3306
+ function getSubtitleCompletedReason(item) {
3307
+ return item.source === "response_audio_transcript" || item.role === "model" ? "response_done" : "conversation_done";
3308
+ }
3309
+ function abortCompletedSubtitleTasks(controllers) {
3310
+ for (const controller of controllers.values()) {
3311
+ controller.abort();
3312
+ }
3313
+ controllers.clear();
3314
+ }
3315
+ function applyCompletedSubtitleDecision(itemId, decision, controller, controllers, completedIds, removeSubtitle) {
3316
+ if (controller.signal.aborted) {
3317
+ return;
3318
+ }
3319
+ const normalizedDecision = normalizeCompletedSubtitleDecision(decision);
3320
+ if (normalizedDecision === "keep") {
3321
+ controllers.delete(itemId);
3322
+ return;
3323
+ }
3324
+ const remove = () => {
3325
+ if (controller.signal.aborted) {
3326
+ return;
3327
+ }
3328
+ controllers.delete(itemId);
3329
+ completedIds.delete(itemId);
3330
+ removeSubtitle(itemId);
3331
+ };
3332
+ if (normalizedDecision === "remove") {
3333
+ remove();
3334
+ return;
3335
+ }
3336
+ const timeout = setTimeout(remove, normalizedDecision.removeAfterMs);
3337
+ controller.signal.addEventListener(
3338
+ "abort",
3339
+ () => {
3340
+ clearTimeout(timeout);
3341
+ },
3342
+ { once: true }
3343
+ );
3344
+ }
3345
+ function removeSubtitleById(items, itemId) {
3346
+ if (!items.some((item) => item.id === itemId)) {
3347
+ return items;
3348
+ }
3349
+ return items.filter((item) => item.id !== itemId);
3350
+ }
3192
3351
  function getDisplayText(item) {
3193
3352
  return item.text || item.transcript;
3194
3353
  }
@@ -3314,6 +3473,8 @@ function IVISubtitleOverlay(props) {
3314
3473
  maxItems,
3315
3474
  maxVisible,
3316
3475
  useModelStreamingTranscript,
3476
+ hideCompletedAfterMs,
3477
+ onSubtitleCompleted,
3317
3478
  subtitleStyle,
3318
3479
  className,
3319
3480
  style
@@ -3321,7 +3482,9 @@ function IVISubtitleOverlay(props) {
3321
3482
  const entries = useIviSubtitles(runtime, {
3322
3483
  roles,
3323
3484
  maxItems: maxItems ?? maxVisible,
3324
- useModelStreamingTranscript
3485
+ useModelStreamingTranscript,
3486
+ hideCompletedAfterMs,
3487
+ onSubtitleCompleted
3325
3488
  });
3326
3489
  if (entries.length === 0) return null;
3327
3490
  const fontFamily = subtitleStyle?.fontFamily ?? "system-ui, -apple-system, sans-serif";
@@ -4412,6 +4575,6 @@ function getClientLogTag(category) {
4412
4575
  return "[IVI-CLIENT]";
4413
4576
  }
4414
4577
 
4415
- export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
4578
+ export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
4416
4579
  //# sourceMappingURL=index.js.map
4417
4580
  //# sourceMappingURL=index.js.map