@vibexnpm/talkx 5.2.0 → 5.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibexnpm/talkx",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "type": "module",
5
5
  "description": "TalkFlow SDK - Chat & WebRTC unified client",
6
6
  "main": "dist/talkflow-sdk.umd.js",
@@ -320,6 +320,21 @@ class ChatClient extends EventEmitter {
320
320
  // 1) WebSocket 구독을 먼저 — 이후 서버 read 이벤트 유실 방지
321
321
  if (!wasAlreadySubscribed) {
322
322
  await this.subscribeRoom(roomId);
323
+ } else if (this.assistantStreamReplay) {
324
+ // ⭐ <b>이미 구독 중인 방으로 재입장</b> — 구독을 유지한 채 방 화면만 나갔다 들어오는 흐름이다
325
+ // (목록 화면에서 unread 를 받으려면 구독을 끊지 않는 것이 정상 구현이다). 이때 subscribeRoom 이
326
+ // 통째로 건너뛰어져 <b>스냅샷 트리거가 하나도 돌지 않는다</b> — 재개 경로는 여기와 재연결
327
+ // (`resumeAssistantStreams`) 둘뿐이기 때문이다.
328
+ //
329
+ // 진행 표시는 transient 이벤트라 화면이 언마운트되면 프론트에서 사라지는데, SDK 슬롯에만 남은
330
+ // 그 상태를 다시 그려줄 신호가 없다 → 다음 PHASE 가 올 때까지(워커 heartbeat 간격 15초) 배지가
331
+ // 안 뜬다. 스냅샷을 다시 받아 그 공백을 없앤다.
332
+ //
333
+ // await 하지 않는다 — 구독은 살아 있으므로 그 사이 도착분은 보류함에 쌓였다가 병합 후 순서대로
334
+ // 풀린다(§6.1). 실패해도 보류분은 반드시 풀리므로 라이브만으로 계속 동작한다.
335
+ const generation = this._armAssistantSnapshot(roomId);
336
+ this._loadAssistantSnapshot(roomId, generation)
337
+ .catch((error) => this.logger.warn(`Assistant snapshot on re-enter failed: room=${roomId}`, error));
323
338
  }
324
339
  // 2) 자동 읽음 처리 활성 방 지정
325
340
  this.setActiveRoom(roomId);
@@ -3490,17 +3505,29 @@ class ChatClient extends EventEmitter {
3490
3505
  * @private
3491
3506
  */
3492
3507
  _emitAssistantRoomProgress(roomId) {
3508
+ // 이벤트 계약은 종전 그대로 둔다 — stream 목록은 getter 전용(이벤트로 매 틱 배열을 실어 보내면
3509
+ // DELTA 마다 방 전체 스트림을 다시 만들어 흘리는 셈이다).
3510
+ const { streams, ...event } = this._assistantRoomProgressPayload(roomId);
3511
+ this.emit('assistantRoomProgress', event);
3512
+ }
3513
+
3514
+ /**
3515
+ * 방 단위 집계 payload 생성 — {@link #_emitAssistantRoomProgress} 와 {@link #getAssistantProgress} 의
3516
+ * <b>공통 SSOT</b>. 두 곳에서 각자 조립하면 "이벤트로 본 상태"와 "물어봐서 본 상태"가 갈린다.
3517
+ * @private
3518
+ */
3519
+ _assistantRoomProgressPayload(roomId) {
3493
3520
  const slots = this._assistantProgress.get(roomId);
3494
3521
  const alive = slots ? Array.from(slots.entries()) : [];
3495
3522
  if (alive.length === 0) {
3496
- this.emit('assistantRoomProgress', {
3523
+ return {
3497
3524
  roomId, active: false, streamCount: 0,
3498
- streamId: null, personaId: null, phase: null, label: null, text: null, textAvailable: true
3499
- });
3500
- return;
3525
+ streamId: null, personaId: null, phase: null, label: null, text: null, textAvailable: true,
3526
+ streams: []
3527
+ };
3501
3528
  }
3502
3529
  const [streamId, slot] = alive.reduce((a, b) => (b[1].order > a[1].order ? b : a));
3503
- this.emit('assistantRoomProgress', {
3530
+ return {
3504
3531
  roomId,
3505
3532
  active: true,
3506
3533
  streamCount: alive.length,
@@ -3509,8 +3536,43 @@ class ChatClient extends EventEmitter {
3509
3536
  phase: slot.phase,
3510
3537
  label: slot.phase ? (this._assistantPhaseLabels[slot.phase] || null) : null,
3511
3538
  text: slot.textAvailable ? (slot.text || null) : null,
3512
- textAvailable: slot.textAvailable
3513
- });
3539
+ textAvailable: slot.textAvailable,
3540
+ // stream 단위 UI(권장 (B))가 마운트 즉시 버블을 복원할 수 있도록 전부 싣는다.
3541
+ // 도착 순서(order)대로 — 소비자가 "가장 최근"을 다시 계산하지 않아도 되게 한다.
3542
+ streams: alive
3543
+ .sort((a, b) => a[1].order - b[1].order)
3544
+ .map(([id, s]) => ({
3545
+ streamId: id,
3546
+ personaId: s.personaId,
3547
+ phase: s.phase,
3548
+ label: s.phase ? (this._assistantPhaseLabels[s.phase] || null) : null,
3549
+ text: s.textAvailable ? (s.text || null) : null,
3550
+ textAvailable: s.textAvailable,
3551
+ resumed: s.resumed === true
3552
+ }))
3553
+ };
3554
+ }
3555
+
3556
+ /**
3557
+ * 방의 <b>현재</b> AI 진행 상태를 즉시 반환 (네트워크 호출 없음).
3558
+ *
3559
+ * <p>{@code assistantProgress} / {@code assistantRoomProgress} 는 transient 이벤트라, 방 화면을 나갔다
3560
+ * 들어오면 프론트 상태가 비어 있는데 <b>이미 지나간 이벤트는 다시 오지 않는다</b>. 마운트 시점에 이걸
3561
+ * 한 번 읽으면 다음 이벤트를 기다리지 않고 바로 그릴 수 있다 — 재입장 스냅샷(1 RTT)보다도 빠르다.</p>
3562
+ *
3563
+ * <p>반환 형태는 {@code assistantRoomProgress} 이벤트와 동일하고, stream 단위 UI 를 위해 {@code streams}
3564
+ * 배열이 추가된다. 진행 중인 것이 없으면 {@code active:false} + 빈 배열.</p>
3565
+ *
3566
+ * @param {string} roomId - 채팅방 ID
3567
+ * @returns {Object} { roomId, active, streamCount, streamId, personaId, phase, label, text, textAvailable, streams[] }
3568
+ *
3569
+ * @example
3570
+ * // 방 화면 마운트 시 — 이벤트를 기다리지 않고 즉시 복원
3571
+ * const p = client.chat.getAssistantProgress(roomId);
3572
+ * if (p.active) renderBubble(p);
3573
+ */
3574
+ getAssistantProgress(roomId) {
3575
+ return this._assistantRoomProgressPayload(roomId);
3514
3576
  }
3515
3577
 
3516
3578
  /**
@@ -52,6 +52,10 @@ const CHAT_DELEGATE_METHODS = [
52
52
  'isSubscribed',
53
53
  'startTyping',
54
54
  'stopTyping',
55
+ // AI 진행 표시 — 마운트 즉시 복원용 동기 조회 + 수동 재개. 여기 없으면 최상위 클라이언트에서
56
+ // 런타임 undefined 가 된다(타입 선언만 있고 배선이 없으면 타입이 거짓말을 한다).
57
+ 'getAssistantProgress',
58
+ 'resumeAssistantStreams',
55
59
  'getAssistants',
56
60
  'getRoomAiMeta',
57
61
  'rateAssistantMessage',
package/types/index.d.ts CHANGED
@@ -1260,6 +1260,29 @@ export interface AssistantRoomProgressEvent {
1260
1260
  textAvailable?: boolean;
1261
1261
  }
1262
1262
 
1263
+ /** {@link ChatClient.getAssistantProgress} 가 돌려주는 stream 1건. */
1264
+ export interface AssistantProgressStreamSnapshot {
1265
+ streamId: string;
1266
+ personaId: string | null;
1267
+ phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
1268
+ label: string | null;
1269
+ text: string | null;
1270
+ textAvailable: boolean;
1271
+ /** 서버 스냅샷으로 복원된 진행분이면 true. */
1272
+ resumed: boolean;
1273
+ }
1274
+
1275
+ /**
1276
+ * 방의 **현재** AI 진행 상태 (v5.3.0 — {@link ChatClient.getAssistantProgress} 반환).
1277
+ *
1278
+ * <p>{@link AssistantRoomProgressEvent} 와 같은 형태에 stream 단위 UI 를 위한 `streams` 가 더해진다.
1279
+ * 이벤트는 transient 라 화면을 나갔다 들어오면 다시 오지 않는다 — 마운트 시 이걸 한 번 읽는다.</p>
1280
+ */
1281
+ export interface AssistantProgressSnapshot extends AssistantRoomProgressEvent {
1282
+ /** 살아 있는 stream 전체 (도착 순서). 없으면 빈 배열. */
1283
+ streams: AssistantProgressStreamSnapshot[];
1284
+ }
1285
+
1263
1286
  // ============================================================================
1264
1287
  // Options — constructor & method
1265
1288
  // ============================================================================
@@ -2017,6 +2040,17 @@ export class ChatClient implements SdkEventEmitter<ChatClientEvents> {
2017
2040
 
2018
2041
  startTyping(roomId: string): void;
2019
2042
  stopTyping(roomId: string): void;
2043
+
2044
+ /**
2045
+ * 방의 현재 AI 진행 상태를 즉시 반환 (네트워크 호출 없음, v5.3.0).
2046
+ * 진행 표시 이벤트는 transient 라 방 화면 마운트 시 이걸 읽어 버블을 복원한다.
2047
+ */
2048
+ getAssistantProgress(roomId: string): AssistantProgressSnapshot;
2049
+ /**
2050
+ * 구독 중인 모든 방의 진행 스트림 스냅샷을 서버에서 다시 받는다 (v5.1.0).
2051
+ * 재연결·탭 복귀 때 SDK 가 자동 호출하므로 보통은 직접 부를 일이 없다.
2052
+ */
2053
+ resumeAssistantStreams(reason?: string): Promise<void>;
2020
2054
  }
2021
2055
 
2022
2056
  export class WebRTCClient implements SdkEventEmitter<WebRTCClientEvents> {
@@ -2400,6 +2434,17 @@ export default class TalkFlowClient implements SdkEventEmitter<TalkFlowClientEve
2400
2434
  startTyping(roomId: string): void;
2401
2435
  stopTyping(roomId: string): void;
2402
2436
 
2437
+ /**
2438
+ * 방의 현재 AI 진행 상태를 즉시 반환 (네트워크 호출 없음, v5.3.0).
2439
+ * 진행 표시 이벤트는 transient 라 방 화면 마운트 시 이걸 읽어 버블을 복원한다.
2440
+ */
2441
+ getAssistantProgress(roomId: string): AssistantProgressSnapshot;
2442
+ /**
2443
+ * 구독 중인 모든 방의 진행 스트림 스냅샷을 서버에서 다시 받는다 (v5.1.0).
2444
+ * 재연결·탭 복귀 때 SDK 가 자동 호출하므로 보통은 직접 부를 일이 없다.
2445
+ */
2446
+ resumeAssistantStreams(reason?: string): Promise<void>;
2447
+
2403
2448
  // WebRTC delegations
2404
2449
  initializeIceServers(): Promise<RTCIceServer[]>;
2405
2450
  getTurnCredentials(): Promise<{ iceServers: RTCIceServer[] }>;