@vibexnpm/talkx 8.9.0 → 8.10.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": "8.9.0",
3
+ "version": "8.10.0",
4
4
  "type": "module",
5
5
  "description": "TalkFlow SDK - Chat & WebRTC unified client",
6
6
  "main": "dist/talkflow-sdk.umd.js",
@@ -3068,6 +3068,16 @@ class ChatClient extends EventEmitter {
3068
3068
  unreadVersion: event.unreadVersion
3069
3069
  });
3070
3070
  break;
3071
+ case RoomListEventType.AI_UNAVAILABLE:
3072
+ // PM 을 직접 불렀는데 회사 AI 가 막혀 답하지 않았다 — 부른 사람에게만 온다. 잠깐 알리고 사라지게(토스트).
3073
+ // 리스트·미독은 건드리지 않는다. 막힌 상태를 계속 보여 줄 띠는 getMyProfile().aiBlocked 로 그린다.
3074
+ this.emit('aiUnavailable', {
3075
+ roomId: event.roomId,
3076
+ messageId: event.messageId,
3077
+ aiBlocked: event.aiBlocked,
3078
+ timestamp: event.timestamp
3079
+ });
3080
+ break;
3071
3081
  default:
3072
3082
  this.logger.warn('Unknown room list event type:', event.eventType);
3073
3083
  }
package/src/constants.js CHANGED
@@ -355,7 +355,14 @@ export const RoomListEventType = {
355
355
  * unreadVersion 으로 순서 역전(읽음 0 이 신규 메시지보다 먼저 도착)을 방어한다.
356
356
  * SDK 는 이를 roomListUnreadUpdated 이벤트로 emit 한다.
357
357
  */
358
- ROOM_UNREAD_UPDATED: 'ROOM_UNREAD_UPDATED'
358
+ ROOM_UNREAD_UPDATED: 'ROOM_UNREAD_UPDATED',
359
+ /**
360
+ * PM 을 직접(@PM) 불렀는데 회사 AI 가 막혀(크레딧·결제) 답하지 않았다 → 부른 사람에게만 온다. 방에는 아무것도 남지 않는다.
361
+ * 잠깐 알리고 사라지게(토스트) 쓴다. 리스트·미독은 건드리지 않는다. 사유는 aiBlocked(REST 402 와 같은 코드).
362
+ * 막힌 상태를 계속 보여 줄 띠는 getMyProfile() 의 aiBlocked 로 그린다.
363
+ * SDK 는 이를 aiUnavailable 이벤트로 emit 한다.
364
+ */
365
+ AI_UNAVAILABLE: 'AI_UNAVAILABLE'
359
366
  };
360
367
 
361
368
  /**
@@ -53,6 +53,8 @@ const CHAT_EVENT_MAP = [
53
53
  ['roomListDeleted', 'roomListDeleted'],
54
54
  ['roomListRoomUpdated', 'roomListRoomUpdated'],
55
55
  ['roomListUnreadUpdated', 'roomListUnreadUpdated'],
56
+ // @PM 을 불렀는데 회사 AI 가 막힘 — 부른 사람에게만 오는 방 목록 채널 알림(AI_UNAVAILABLE).
57
+ ['aiUnavailable', 'aiUnavailable'],
56
58
  // Gmail 메일함 — mailChanged 는 받은 그대로. 새 메일 알림(newMail)은 아래에서 푸시와 합쳐 따로 낸다.
57
59
  ['mailSubscribed', 'mailSubscribed'],
58
60
  ['mailUnsubscribed', 'mailUnsubscribed'],
@@ -0,0 +1,48 @@
1
+ /**
2
+ * «AI 사용 불가» 알림·회사 AI 막힘 띠(8.10.0) — FE 가 타입 단언 없이 쓰는지 tsc 로 고정한다.
3
+ *
4
+ * <p>jest 는 `.d.ts` 를 보지 않는다. 최상위 client 와 chat 양쪽에서 `aiUnavailable` 를 구독할 수 있는지, 사유가 유니온인지,
5
+ * `getMyProfile()` 의 `aiBlocked` 에 구독 없음(`NOT_SUBSCRIBED`)이 들어갈 수 없는지(서버가 `/me` 에는 싣지 않는다)를 본다.</p>
6
+ *
7
+ * <p>실행: {@code npm run typecheck}</p>
8
+ */
9
+ import TalkFlowClient, { RoomListEventType } from '../index';
10
+ import type { AiBlockedReason, AiUnavailableEvent, ChatClient, MyProfile, RoomListUpdateEvent } from '../index';
11
+
12
+ declare const talkflow: TalkFlowClient;
13
+ /** {@code talkflow.chat} 은 연결 전 null 이라 선언이 nullable 이다 — 연결된 chat 을 직접 받는 모양으로 본다. */
14
+ declare const chat: ChatClient;
15
+
16
+ function toast(_reason: AiBlockedReason, _roomId: string): void {}
17
+
18
+ // 전용 이벤트 — 최상위·chat 양쪽
19
+ talkflow.on('aiUnavailable', (e: AiUnavailableEvent) => toast(e.aiBlocked, e.roomId));
20
+ chat.on('aiUnavailable', (e) => {
21
+ const reason: AiBlockedReason = e.aiBlocked;
22
+ const messageId: string | undefined = e.messageId;
23
+ toast(reason, e.roomId);
24
+ return messageId;
25
+ });
26
+
27
+ // 통합 이벤트로 받던 코드도 단언 없이 좁혀진다
28
+ talkflow.on('roomListUpdate', (e: RoomListUpdateEvent) => {
29
+ if (e.eventType === RoomListEventType.AI_UNAVAILABLE && e.aiBlocked) {
30
+ toast(e.aiBlocked, e.roomId);
31
+ }
32
+ });
33
+
34
+ // 사유 유니온 — 서버 REST 402 코드와 같다
35
+ const reasons: AiBlockedReason[] = ['CREDIT_EXHAUSTED', 'CREDIT_INSUFFICIENT', 'BILLING_SUSPENDED', 'NOT_SUBSCRIBED'];
36
+ // @ts-expect-error — 모르는 사유는 막힌다
37
+ const unknownReason: AiBlockedReason = 'SUSPENDED';
38
+
39
+ // 채팅방 상단 띠 — /me 의 aiBlocked
40
+ async function banner(): Promise<string | null> {
41
+ const me: MyProfile = await talkflow.getMyProfile();
42
+ return me.aiBlocked ?? null;
43
+ }
44
+ const profileReason: NonNullable<MyProfile['aiBlocked']> = 'CREDIT_EXHAUSTED';
45
+ // @ts-expect-error — /me 에는 구독 없음이 오지 않는다(그 회사는 구독 필요 화면이 맡는다)
46
+ const notOnProfile: NonNullable<MyProfile['aiBlocked']> = 'NOT_SUBSCRIBED';
47
+
48
+ export { reasons, unknownReason, banner, profileReason, notOnProfile };
package/types/index.d.ts CHANGED
@@ -534,6 +534,7 @@ export const RoomListEventType: {
534
534
  readonly ROOM_DELETED: 'ROOM_DELETED';
535
535
  readonly MESSAGE_RETENTION_CLEANUP: 'MESSAGE_RETENTION_CLEANUP';
536
536
  readonly ROOM_UNREAD_UPDATED: 'ROOM_UNREAD_UPDATED';
537
+ readonly AI_UNAVAILABLE: 'AI_UNAVAILABLE';
537
538
  };
538
539
  export type RoomListEventTypeValue = typeof RoomListEventType[keyof typeof RoomListEventType];
539
540
 
@@ -1173,6 +1174,14 @@ export interface MyProfile {
1173
1174
  mustChangePassword: boolean;
1174
1175
  accessToken?: string;
1175
1176
  expiresIn?: number;
1177
+ /**
1178
+ * 회사 AI 가 지금 막혔는가 — 채팅방 상단 띠용. 막혔으면 사유, 열려 있으면 키가 없다.
1179
+ *
1180
+ * PM 입구와 같은 판정이라 값이 있는 동안 PM 은 불러도 답하지 않는다. 최대 30초 늦게 바뀔 수 있고, 서버가 상태 변화를 따로
1181
+ * 알려 주지 않으므로 앱 시작·방 입장·탭 복귀·{@link AiUnavailableEvent} 수신 때 다시 읽는다. 게스트에게는 없다.
1182
+ * 구독 없음(`NOT_SUBSCRIBED`)은 여기 오지 않는다 — 그 회사는 구독 필요 화면이 맡는다.
1183
+ */
1184
+ aiBlocked?: Exclude<AiBlockedReason, 'NOT_SUBSCRIBED'>;
1176
1185
  }
1177
1186
 
1178
1187
  /** 채팅방 참가자. */
@@ -1356,9 +1365,36 @@ export interface RoomListUpdateEvent {
1356
1365
  * {@code ROOM_UNREAD_UPDATED} 에 실려, 순서 역전을 {@link RoomUnreadReconciler} 가 판별한다.
1357
1366
  */
1358
1367
  unreadVersion?: number;
1368
+ /** {@code AI_UNAVAILABLE} 전용 — 회사 AI 가 막힌 사유. 다른 eventType 에서는 undefined. 전용 이벤트 {@link AiUnavailableEvent} 로도 받을 수 있다. */
1369
+ aiBlocked?: AiBlockedReason;
1359
1370
  [extra: string]: unknown;
1360
1371
  }
1361
1372
 
1373
+ /**
1374
+ * 회사 AI 가 막힌 사유 — AI 버튼(요약·번역 등)이 받는 REST 402 오류 코드와 같다.
1375
+ * <ul>
1376
+ * <li>{@code CREDIT_EXHAUSTED} — 이번 결제 기간 AI 크레딧을 다 썼다(다음 갱신·충전 때 풀림).</li>
1377
+ * <li>{@code CREDIT_INSUFFICIENT} — 남은 크레딧을 진행 중인 AI 작업이 잡고 있다(작업이 끝나면 풀림, 드묾).</li>
1378
+ * <li>{@code BILLING_SUSPENDED} — 결제가 확인되지 않았다.</li>
1379
+ * <li>{@code NOT_SUBSCRIBED} — 회사 구독이 없다. {@link AiUnavailableEvent} 에만 드물게 오고 {@link MyProfile.aiBlocked} 에는 오지 않는다.</li>
1380
+ * </ul>
1381
+ */
1382
+ export type AiBlockedReason = 'CREDIT_EXHAUSTED' | 'CREDIT_INSUFFICIENT' | 'BILLING_SUSPENDED' | 'NOT_SUBSCRIBED';
1383
+
1384
+ /**
1385
+ * `aiUnavailable` 이벤트 payload — PM 을 직접(@PM) 불렀는데 회사 AI 가 막혀 답하지 않았다. <b>부른 사람에게만</b> 오고, 방에는
1386
+ * 아무것도 남지 않는다. 잠깐 알리고 사라지게(토스트) 쓴다 — 리스트·미독은 바꾸지 않는다. 막힌 상태를 계속 보여 줄 띠는
1387
+ * {@link MyProfile.aiBlocked} 로 그린다.
1388
+ */
1389
+ export interface AiUnavailableEvent {
1390
+ roomId: string;
1391
+ /** 부른 메시지. */
1392
+ messageId?: string;
1393
+ aiBlocked: AiBlockedReason;
1394
+ /** 서버 발행 시각(epoch millis). */
1395
+ timestamp?: number;
1396
+ }
1397
+
1362
1398
  /**
1363
1399
  * `roomListUnreadUpdated` 이벤트 payload — 내가 어느 기기에서 읽든 같은 사용자의 다른 세션에 전달된다.
1364
1400
  *
@@ -2000,7 +2036,7 @@ export interface RetentionCleanupPayload {
2000
2036
  * TalkFlowClient 이벤트 맵 — 런타임 {@code TalkFlowClient.js} 의 {@code this.emit(...)} 호출 기반.
2001
2037
  *
2002
2038
  * <p><b>Connection 관련</b>: stateChange, connected, disconnected, reconnecting, reconnected, resumed, subscriptionRequired, connectionError, tokenSet, loggedOut</p>
2003
- * <p><b>Chat 관련 (ChatClient 이벤트를 이 이름으로 re-emit)</b>: chatMessage, newChatMessage, messageUpdated, messageDeleted, reactionChanged, linkPreviewAttached, messageRead, typing, assistantProgress, memberJoined, memberLeft, roomSubscribed, roomUnsubscribed, roomListSubscribed, roomListUnsubscribed, roomListUpdate, roomListMessage, roomListCreated, roomListJoined, roomListLeft, roomListSelfLeft, roomListRoomUpdated, retentionCleanup</p>
2039
+ * <p><b>Chat 관련 (ChatClient 이벤트를 이 이름으로 re-emit)</b>: chatMessage, newChatMessage, messageUpdated, messageDeleted, reactionChanged, linkPreviewAttached, messageRead, typing, assistantProgress, memberJoined, memberLeft, roomSubscribed, roomUnsubscribed, roomListSubscribed, roomListUnsubscribed, roomListUpdate, roomListMessage, roomListCreated, roomListJoined, roomListLeft, roomListSelfLeft, roomListRoomUpdated, aiUnavailable, retentionCleanup</p>
2004
2040
  * <p><b>Push 관련</b>: pushEnabled, pushFailed, pushNotification</p>
2005
2041
  * <p><b>WebRTC 관련</b>: localStreamStarted, localStreamStopped, remoteTrack, screenShareStarted, screenShareEnded, deviceChange, mediaStateChanged, callStarted, callEnded, callRequested, callAccepted, callRejected, callCancelled, callInvitation, callBusy, incomingCall, incomingCallWhileBusy, userJoined, userLeft, participantLeft, participantMediaState, peerConnected, peerDisconnected, peerClosed, webrtcError</p>
2006
2042
  */
@@ -2088,6 +2124,8 @@ export interface TalkFlowClientEvents {
2088
2124
  roomListRoomUpdated: RoomListUpdateEvent;
2089
2125
  /** 내가 어느 기기에서 읽든 방별 미독 절대값(+version)을 통지 — 크로스-디바이스 badge 동기화. */
2090
2126
  roomListUnreadUpdated: RoomUnreadUpdatedEvent;
2127
+ /** @PM 을 불렀는데 회사 AI 가 막혀 PM 이 답하지 않음 (AI_UNAVAILABLE) — 부른 사람에게만 온다. 잠깐 알리고 사라지게. */
2128
+ aiUnavailable: AiUnavailableEvent;
2091
2129
  mailSubscribed: Record<string, unknown>;
2092
2130
  mailUnsubscribed: Record<string, unknown>;
2093
2131
  mailChanged: MailChangedEvent;
@@ -2204,6 +2242,8 @@ export interface ChatClientEvents {
2204
2242
  roomListRoomUpdated: RoomListUpdateEvent;
2205
2243
  /** 내가 어느 기기에서 읽든 방별 미독 절대값(+version)을 통지 — 크로스-디바이스 badge 동기화. */
2206
2244
  roomListUnreadUpdated: RoomUnreadUpdatedEvent;
2245
+ /** @PM 을 불렀는데 회사 AI 가 막혀 PM 이 답하지 않음 (AI_UNAVAILABLE) — 부른 사람에게만 온다. 잠깐 알리고 사라지게. */
2246
+ aiUnavailable: AiUnavailableEvent;
2207
2247
  mailSubscribed: Record<string, unknown>;
2208
2248
  mailUnsubscribed: Record<string, unknown>;
2209
2249
  mailChanged: MailChangedEvent;