@vivix-ai/ivi-frontend-sdk 0.3.6 → 0.3.8
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/README.md +10 -3
- package/dist/index.cjs +206 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +206 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
@@ -1105,6 +1105,8 @@ var ConversationManager = class {
|
|
|
1105
1105
|
|
|
1106
1106
|
// src/runtime/managers/trtc-source-manager.ts
|
|
1107
1107
|
var TAG = "[IVI-TRTC]";
|
|
1108
|
+
var TRTC_VIEW_ATTR = "data-ivi-trtc-view";
|
|
1109
|
+
var TRTC_MEDIA_STYLE_ID = "ivi-trtc-media-style";
|
|
1108
1110
|
var DEFAULT_DENOISER_OPTIONS = {
|
|
1109
1111
|
enabled: true,
|
|
1110
1112
|
mode: "normal"
|
|
@@ -1266,6 +1268,8 @@ var TrtcSourceManager = class {
|
|
|
1266
1268
|
if (!session) {
|
|
1267
1269
|
throw new Error(`TRTC source session not found: ${sourceId}`);
|
|
1268
1270
|
}
|
|
1271
|
+
ensureTrtcMediaStyleSheet();
|
|
1272
|
+
container.setAttribute(TRTC_VIEW_ATTR, "");
|
|
1269
1273
|
session.views.set(viewId, {
|
|
1270
1274
|
sourceId,
|
|
1271
1275
|
container,
|
|
@@ -1288,6 +1292,7 @@ var TrtcSourceManager = class {
|
|
|
1288
1292
|
}
|
|
1289
1293
|
const binding = session.views.get(viewId);
|
|
1290
1294
|
if (binding?.sourceId === sourceId) {
|
|
1295
|
+
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1291
1296
|
session.views.delete(viewId);
|
|
1292
1297
|
}
|
|
1293
1298
|
void this.applyAudioPolicy(session);
|
|
@@ -1309,6 +1314,7 @@ var TrtcSourceManager = class {
|
|
|
1309
1314
|
this.sourceSessionKeys.delete(sourceId);
|
|
1310
1315
|
session.views.forEach((binding, viewId) => {
|
|
1311
1316
|
if (binding.sourceId === sourceId) {
|
|
1317
|
+
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1312
1318
|
session.views.delete(viewId);
|
|
1313
1319
|
}
|
|
1314
1320
|
});
|
|
@@ -1628,6 +1634,38 @@ var TrtcSourceManager = class {
|
|
|
1628
1634
|
this.onTrtcEvent?.(event);
|
|
1629
1635
|
}
|
|
1630
1636
|
};
|
|
1637
|
+
function ensureTrtcMediaStyleSheet() {
|
|
1638
|
+
if (typeof document === "undefined") {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
if (document.getElementById(TRTC_MEDIA_STYLE_ID)) {
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const style = document.createElement("style");
|
|
1645
|
+
style.id = TRTC_MEDIA_STYLE_ID;
|
|
1646
|
+
style.textContent = `
|
|
1647
|
+
[${TRTC_VIEW_ATTR}] [id^="player_"],
|
|
1648
|
+
[${TRTC_VIEW_ATTR}] [id^="video_"],
|
|
1649
|
+
[${TRTC_VIEW_ATTR}] [id^="audio_"],
|
|
1650
|
+
[${TRTC_VIEW_ATTR}] video,
|
|
1651
|
+
[${TRTC_VIEW_ATTR}] canvas {
|
|
1652
|
+
width: 100% !important;
|
|
1653
|
+
height: 100% !important;
|
|
1654
|
+
max-width: 100% !important;
|
|
1655
|
+
max-height: 100% !important;
|
|
1656
|
+
background: transparent !important;
|
|
1657
|
+
background-color: transparent !important;
|
|
1658
|
+
background-image: none !important;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
[${TRTC_VIEW_ATTR}] video,
|
|
1662
|
+
[${TRTC_VIEW_ATTR}] canvas {
|
|
1663
|
+
object-fit: contain !important;
|
|
1664
|
+
display: block !important;
|
|
1665
|
+
}
|
|
1666
|
+
`;
|
|
1667
|
+
document.head.appendChild(style);
|
|
1668
|
+
}
|
|
1631
1669
|
function isRuntimeTrtcSource(source) {
|
|
1632
1670
|
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
1633
1671
|
}
|
|
@@ -1719,6 +1757,9 @@ function enforceContainMedia(container) {
|
|
|
1719
1757
|
element.style.setProperty("max-height", "100%", "important");
|
|
1720
1758
|
element.style.setProperty("object-fit", "contain", "important");
|
|
1721
1759
|
element.style.setProperty("display", "block", "important");
|
|
1760
|
+
element.style.setProperty("background", "transparent", "important");
|
|
1761
|
+
element.style.setProperty("background-color", "transparent", "important");
|
|
1762
|
+
element.style.setProperty("background-image", "none", "important");
|
|
1722
1763
|
});
|
|
1723
1764
|
}
|
|
1724
1765
|
|
|
@@ -3078,9 +3119,12 @@ function useApplyVolumeToSlot(containerRef, volume, enabled, activeSourceId) {
|
|
|
3078
3119
|
return () => observer.disconnect();
|
|
3079
3120
|
}, [containerRef, volume, enabled, activeSourceId]);
|
|
3080
3121
|
}
|
|
3122
|
+
var DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = 3e3;
|
|
3081
3123
|
function useIviSubtitles(runtime, options = {}) {
|
|
3082
3124
|
const roles = options.roles ?? "user";
|
|
3083
3125
|
const maxItems = normalizeMaxItems(options.maxItems);
|
|
3126
|
+
const hideCompletedAfterMs = normalizeHideCompletedAfterMs(options.hideCompletedAfterMs);
|
|
3127
|
+
const onSubtitleCompleted = options.onSubtitleCompleted;
|
|
3084
3128
|
const roleKey = Array.isArray(roles) ? roles.join("\0") : roles;
|
|
3085
3129
|
const roleSet = useMemo(
|
|
3086
3130
|
() => new Set(roleKey.split("\0")),
|
|
@@ -3090,9 +3134,14 @@ function useIviSubtitles(runtime, options = {}) {
|
|
|
3090
3134
|
const [subtitles, setSubtitles] = useState([]);
|
|
3091
3135
|
const seenIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
3092
3136
|
const initializedRef = useRef(false);
|
|
3137
|
+
const completedSubtitleIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
3138
|
+
const completionControllersRef = useRef(/* @__PURE__ */ new Map());
|
|
3139
|
+
const completionPolicyRef = useRef(null);
|
|
3093
3140
|
useEffect(() => {
|
|
3094
3141
|
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
3095
3142
|
initializedRef.current = false;
|
|
3143
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3144
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3096
3145
|
setSubtitles([]);
|
|
3097
3146
|
if (!runtime) {
|
|
3098
3147
|
return;
|
|
@@ -3148,6 +3197,8 @@ function useIviSubtitles(runtime, options = {}) {
|
|
|
3148
3197
|
if (event.type === "session.ended") {
|
|
3149
3198
|
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
3150
3199
|
initializedRef.current = false;
|
|
3200
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3201
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3151
3202
|
setSubtitles([]);
|
|
3152
3203
|
return;
|
|
3153
3204
|
}
|
|
@@ -3165,6 +3216,78 @@ function useIviSubtitles(runtime, options = {}) {
|
|
|
3165
3216
|
syncConversations(state.conversations);
|
|
3166
3217
|
});
|
|
3167
3218
|
}, [runtime, roleSet, maxItems, useModelStreamingTranscript]);
|
|
3219
|
+
useEffect(() => {
|
|
3220
|
+
return () => {
|
|
3221
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3222
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3223
|
+
};
|
|
3224
|
+
}, []);
|
|
3225
|
+
useEffect(() => {
|
|
3226
|
+
const previousPolicy = completionPolicyRef.current;
|
|
3227
|
+
const policyChanged = Boolean(
|
|
3228
|
+
previousPolicy && (previousPolicy.hideCompletedAfterMs !== hideCompletedAfterMs || previousPolicy.onSubtitleCompleted !== onSubtitleCompleted)
|
|
3229
|
+
);
|
|
3230
|
+
completionPolicyRef.current = { hideCompletedAfterMs, onSubtitleCompleted };
|
|
3231
|
+
if (policyChanged) {
|
|
3232
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3233
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3234
|
+
}
|
|
3235
|
+
const subtitleById = new Map(subtitles.map((item) => [item.id, item]));
|
|
3236
|
+
const completedIds = completedSubtitleIdsRef.current;
|
|
3237
|
+
const controllers = completionControllersRef.current;
|
|
3238
|
+
for (const [id, controller] of Array.from(controllers)) {
|
|
3239
|
+
const item = subtitleById.get(id);
|
|
3240
|
+
if (!item || !isCompletedSubtitle(item)) {
|
|
3241
|
+
controller.abort();
|
|
3242
|
+
controllers.delete(id);
|
|
3243
|
+
completedIds.delete(id);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
for (const id of Array.from(completedIds)) {
|
|
3247
|
+
const item = subtitleById.get(id);
|
|
3248
|
+
if (!item || !isCompletedSubtitle(item)) {
|
|
3249
|
+
completedIds.delete(id);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
for (const item of subtitles) {
|
|
3253
|
+
if (!isCompletedSubtitle(item) || completedIds.has(item.id)) {
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
completedIds.add(item.id);
|
|
3257
|
+
const controller = new AbortController();
|
|
3258
|
+
controllers.set(item.id, controller);
|
|
3259
|
+
const context = {
|
|
3260
|
+
entries: subtitles,
|
|
3261
|
+
reason: getSubtitleCompletedReason(item),
|
|
3262
|
+
signal: controller.signal
|
|
3263
|
+
};
|
|
3264
|
+
let decisionResult;
|
|
3265
|
+
try {
|
|
3266
|
+
decisionResult = onSubtitleCompleted ? onSubtitleCompleted(item, context) : getDefaultCompletedSubtitleDecision(hideCompletedAfterMs);
|
|
3267
|
+
} catch {
|
|
3268
|
+
decisionResult = "keep";
|
|
3269
|
+
}
|
|
3270
|
+
void Promise.resolve(decisionResult).then((decision) => {
|
|
3271
|
+
applyCompletedSubtitleDecision(
|
|
3272
|
+
item.id,
|
|
3273
|
+
decision,
|
|
3274
|
+
controller,
|
|
3275
|
+
controllers,
|
|
3276
|
+
completedIds,
|
|
3277
|
+
(id) => setSubtitles((previous) => removeSubtitleById(previous, id))
|
|
3278
|
+
);
|
|
3279
|
+
}).catch(() => {
|
|
3280
|
+
applyCompletedSubtitleDecision(
|
|
3281
|
+
item.id,
|
|
3282
|
+
"keep",
|
|
3283
|
+
controller,
|
|
3284
|
+
controllers,
|
|
3285
|
+
completedIds,
|
|
3286
|
+
(id) => setSubtitles((previous) => removeSubtitleById(previous, id))
|
|
3287
|
+
);
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
}, [subtitles, hideCompletedAfterMs, onSubtitleCompleted]);
|
|
3168
3291
|
return subtitles;
|
|
3169
3292
|
}
|
|
3170
3293
|
function normalizeMaxItems(maxItems) {
|
|
@@ -3176,6 +3299,35 @@ function normalizeMaxItems(maxItems) {
|
|
|
3176
3299
|
}
|
|
3177
3300
|
return Math.max(0, Math.floor(maxItems));
|
|
3178
3301
|
}
|
|
3302
|
+
function normalizeHideCompletedAfterMs(hideCompletedAfterMs) {
|
|
3303
|
+
if (hideCompletedAfterMs === false) {
|
|
3304
|
+
return false;
|
|
3305
|
+
}
|
|
3306
|
+
if (hideCompletedAfterMs === void 0 || !Number.isFinite(hideCompletedAfterMs)) {
|
|
3307
|
+
return DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS;
|
|
3308
|
+
}
|
|
3309
|
+
return Math.max(0, Math.floor(hideCompletedAfterMs));
|
|
3310
|
+
}
|
|
3311
|
+
function getDefaultCompletedSubtitleDecision(hideCompletedAfterMs) {
|
|
3312
|
+
if (hideCompletedAfterMs === false) {
|
|
3313
|
+
return "keep";
|
|
3314
|
+
}
|
|
3315
|
+
return hideCompletedAfterMs === 0 ? "remove" : { removeAfterMs: hideCompletedAfterMs };
|
|
3316
|
+
}
|
|
3317
|
+
function normalizeCompletedSubtitleDecision(decision) {
|
|
3318
|
+
if (decision === "keep" || decision === "remove") {
|
|
3319
|
+
return decision;
|
|
3320
|
+
}
|
|
3321
|
+
if (decision && typeof decision === "object" && "removeAfterMs" in decision) {
|
|
3322
|
+
const removeAfterMs = Number(decision.removeAfterMs);
|
|
3323
|
+
if (!Number.isFinite(removeAfterMs)) {
|
|
3324
|
+
return "keep";
|
|
3325
|
+
}
|
|
3326
|
+
const normalized = Math.max(0, Math.floor(removeAfterMs));
|
|
3327
|
+
return normalized === 0 ? "remove" : { removeAfterMs: normalized };
|
|
3328
|
+
}
|
|
3329
|
+
return "keep";
|
|
3330
|
+
}
|
|
3179
3331
|
function shouldUseConversationRole(role, roleSet, useModelStreamingTranscript) {
|
|
3180
3332
|
if (!roleSet.has(role)) {
|
|
3181
3333
|
return false;
|
|
@@ -3189,6 +3341,54 @@ function trimSubtitleItems(items, maxItems) {
|
|
|
3189
3341
|
const sorted = [...items].sort((a, b) => a.timestamp - b.timestamp);
|
|
3190
3342
|
return sorted.length > maxItems ? sorted.slice(sorted.length - maxItems) : sorted;
|
|
3191
3343
|
}
|
|
3344
|
+
function isCompletedSubtitle(item) {
|
|
3345
|
+
return item.lifecycle === "done" || item.status === "completed" || item.status === "incomplete";
|
|
3346
|
+
}
|
|
3347
|
+
function getSubtitleCompletedReason(item) {
|
|
3348
|
+
return item.source === "response_audio_transcript" || item.role === "model" ? "response_done" : "conversation_done";
|
|
3349
|
+
}
|
|
3350
|
+
function abortCompletedSubtitleTasks(controllers) {
|
|
3351
|
+
for (const controller of controllers.values()) {
|
|
3352
|
+
controller.abort();
|
|
3353
|
+
}
|
|
3354
|
+
controllers.clear();
|
|
3355
|
+
}
|
|
3356
|
+
function applyCompletedSubtitleDecision(itemId, decision, controller, controllers, completedIds, removeSubtitle) {
|
|
3357
|
+
if (controller.signal.aborted) {
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
const normalizedDecision = normalizeCompletedSubtitleDecision(decision);
|
|
3361
|
+
if (normalizedDecision === "keep") {
|
|
3362
|
+
controllers.delete(itemId);
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
const remove = () => {
|
|
3366
|
+
if (controller.signal.aborted) {
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
controllers.delete(itemId);
|
|
3370
|
+
completedIds.delete(itemId);
|
|
3371
|
+
removeSubtitle(itemId);
|
|
3372
|
+
};
|
|
3373
|
+
if (normalizedDecision === "remove") {
|
|
3374
|
+
remove();
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
const timeout = setTimeout(remove, normalizedDecision.removeAfterMs);
|
|
3378
|
+
controller.signal.addEventListener(
|
|
3379
|
+
"abort",
|
|
3380
|
+
() => {
|
|
3381
|
+
clearTimeout(timeout);
|
|
3382
|
+
},
|
|
3383
|
+
{ once: true }
|
|
3384
|
+
);
|
|
3385
|
+
}
|
|
3386
|
+
function removeSubtitleById(items, itemId) {
|
|
3387
|
+
if (!items.some((item) => item.id === itemId)) {
|
|
3388
|
+
return items;
|
|
3389
|
+
}
|
|
3390
|
+
return items.filter((item) => item.id !== itemId);
|
|
3391
|
+
}
|
|
3192
3392
|
function getDisplayText(item) {
|
|
3193
3393
|
return item.text || item.transcript;
|
|
3194
3394
|
}
|
|
@@ -3314,6 +3514,8 @@ function IVISubtitleOverlay(props) {
|
|
|
3314
3514
|
maxItems,
|
|
3315
3515
|
maxVisible,
|
|
3316
3516
|
useModelStreamingTranscript,
|
|
3517
|
+
hideCompletedAfterMs,
|
|
3518
|
+
onSubtitleCompleted,
|
|
3317
3519
|
subtitleStyle,
|
|
3318
3520
|
className,
|
|
3319
3521
|
style
|
|
@@ -3321,7 +3523,9 @@ function IVISubtitleOverlay(props) {
|
|
|
3321
3523
|
const entries = useIviSubtitles(runtime, {
|
|
3322
3524
|
roles,
|
|
3323
3525
|
maxItems: maxItems ?? maxVisible,
|
|
3324
|
-
useModelStreamingTranscript
|
|
3526
|
+
useModelStreamingTranscript,
|
|
3527
|
+
hideCompletedAfterMs,
|
|
3528
|
+
onSubtitleCompleted
|
|
3325
3529
|
});
|
|
3326
3530
|
if (entries.length === 0) return null;
|
|
3327
3531
|
const fontFamily = subtitleStyle?.fontFamily ?? "system-ui, -apple-system, sans-serif";
|
|
@@ -3453,7 +3657,6 @@ function IVITrtcPlayer(props) {
|
|
|
3453
3657
|
height: "100%",
|
|
3454
3658
|
minWidth: 0,
|
|
3455
3659
|
minHeight: 0,
|
|
3456
|
-
backgroundColor: "#000",
|
|
3457
3660
|
position: "relative",
|
|
3458
3661
|
...style
|
|
3459
3662
|
},
|
|
@@ -4412,6 +4615,6 @@ function getClientLogTag(category) {
|
|
|
4412
4615
|
return "[IVI-CLIENT]";
|
|
4413
4616
|
}
|
|
4414
4617
|
|
|
4415
|
-
export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
|
|
4618
|
+
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
4619
|
//# sourceMappingURL=index.js.map
|
|
4417
4620
|
//# sourceMappingURL=index.js.map
|