@vibexnpm/talkx 2.3.1 → 3.0.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/README.md +3 -4
- package/dist/index.d.ts +57 -10
- package/dist/talkflow-sdk.esm.js +1 -1
- package/dist/talkflow-sdk.esm.js.map +1 -1
- package/dist/talkflow-sdk.standalone.js +1 -1
- package/dist/talkflow-sdk.standalone.js.map +1 -1
- package/dist/talkflow-sdk.umd.js +1 -1
- package/dist/talkflow-sdk.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/chat/ChatClient.js +182 -16
- package/src/constants.js +2 -0
- package/src/talkflow/eventForwarding.js +1 -0
- package/types/index.d.ts +57 -10
package/package.json
CHANGED
package/src/chat/ChatClient.js
CHANGED
|
@@ -55,6 +55,21 @@ class ChatClient extends EventEmitter {
|
|
|
55
55
|
this._assistantTypingUserId = '__assistant__';
|
|
56
56
|
this._assistantTypingUserName = 'AI';
|
|
57
57
|
|
|
58
|
+
// AI 진행 표시(생각중/검색중/작성중) — assistant-stream 채널의 PHASE/DONE 수신 상태.
|
|
59
|
+
// roomId → { streamId, personaId, phase, timer }. typing 과 별개의 가산 레이어(서버가 둘 다 발행).
|
|
60
|
+
// 자동 해제 3경로: (1) DONE 수신 (2) 어시 메시지 도착 (3) TTL(expiresAt) 만료 — typing 타이머와 동형.
|
|
61
|
+
// 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행하므로 방 단위 1개 진행으로 추적한다.
|
|
62
|
+
this._assistantProgress = new Map();
|
|
63
|
+
// phase → 기본 표시 라벨(설계 §2 — 서버는 의미만, 라벨 텍스트는 SDK 매핑). 소비자는 payload.phase 로 커스텀 가능.
|
|
64
|
+
this._assistantPhaseLabels = {
|
|
65
|
+
THINKING: '생각 중…',
|
|
66
|
+
SEARCHING: '검색 중…',
|
|
67
|
+
PLANNING: '기획 중…',
|
|
68
|
+
WRITING: '작성 중…'
|
|
69
|
+
};
|
|
70
|
+
// PHASE 의 expiresAt 누락/이상 시 fallback TTL (ms) — 서버 ttl(최소 30s) 보다 약간 길게.
|
|
71
|
+
this._assistantProgressFallbackTtlMs = 35000;
|
|
72
|
+
|
|
58
73
|
// 메시지 dedup 보호 — 같은 messageId 의 중복 수신 차단.
|
|
59
74
|
// 서버측 멱등 race 복구 / 네트워크 재전송 / WebSocket 재연결 직후 등 다양한 중복 시나리오 커버.
|
|
60
75
|
//
|
|
@@ -423,40 +438,37 @@ class ChatClient extends EventEmitter {
|
|
|
423
438
|
}
|
|
424
439
|
|
|
425
440
|
/**
|
|
426
|
-
*
|
|
441
|
+
* 특정 메시지 온디맨드 번역 (버튼 진입점, 동기).
|
|
427
442
|
*
|
|
428
|
-
* <p
|
|
429
|
-
*
|
|
430
|
-
*
|
|
443
|
+
* <p>{@code sourceMessageId} 메시지(텍스트/이미지/문서)를 {@code targetLang} 으로 번역해 요청자에게만
|
|
444
|
+
* 반환한다. 이미지는 이미지 속 텍스트를, 문서(PDF/DOCX/XLSX/PPTX 등)는 파일 안의 텍스트를 서버가 추출해
|
|
445
|
+
* 번역한다. 방에 메시지를 저장하거나 {@code chatMessage} 이벤트를 broadcast 하지 않는다. 읽을 텍스트가
|
|
446
|
+
* 없거나 문서가 너무 크면 에러를 반환한다.</p>
|
|
431
447
|
*
|
|
432
448
|
* @param {string} roomId - 채팅방 ID
|
|
433
449
|
* @param {Object} options
|
|
434
|
-
* @param {string} options.
|
|
435
|
-
* @param {string} options.
|
|
436
|
-
* @
|
|
437
|
-
* @returns {Promise<{outcome: string, messageId: (string|null), detail: (string|null)}>}
|
|
438
|
-
* {@code outcome}: {@code 'EMITTED'} (생성됨, {@code messageId} 존재) |
|
|
439
|
-
* {@code 'SKIPPED'} / {@code 'FAILED'} ({@code detail} 에 사유).
|
|
450
|
+
* @param {string} options.sourceMessageId - 번역 대상 메시지의 {@code messageId} (텍스트/이미지/문서). 필수.
|
|
451
|
+
* @param {string} options.targetLang - 목표 언어 (예: {@code 'en'}, {@code '日本語'}). 필수.
|
|
452
|
+
* @returns {Promise<{translatedText: string, sourceMessageId: string, targetLang: string}>}
|
|
440
453
|
*
|
|
441
454
|
* @example
|
|
442
455
|
* const result = await chat.translateWithAssistant(roomId, {
|
|
443
|
-
* personaId,
|
|
444
456
|
* sourceMessageId: msg.messageId,
|
|
445
457
|
* targetLang: 'en'
|
|
446
458
|
* });
|
|
447
459
|
*/
|
|
448
460
|
async translateWithAssistant(roomId, options = {}) {
|
|
449
|
-
const {
|
|
450
|
-
if (!personaId) {
|
|
451
|
-
throw new Error('translateWithAssistant requires options.personaId');
|
|
452
|
-
}
|
|
461
|
+
const { targetLang, sourceMessageId } = options;
|
|
453
462
|
if (!sourceMessageId) {
|
|
454
463
|
throw new Error('translateWithAssistant requires options.sourceMessageId');
|
|
455
464
|
}
|
|
465
|
+
if (!targetLang) {
|
|
466
|
+
throw new Error('translateWithAssistant requires options.targetLang');
|
|
467
|
+
}
|
|
456
468
|
|
|
457
469
|
const response = await this.apiClient.post(
|
|
458
470
|
`/api/v1/rooms/${roomId}/assistant/tools/translate`,
|
|
459
|
-
{
|
|
471
|
+
{ targetLang, sourceMessageId }
|
|
460
472
|
);
|
|
461
473
|
return this._unwrapSuccessResponse(response);
|
|
462
474
|
}
|
|
@@ -1580,6 +1592,12 @@ class ChatClient extends EventEmitter {
|
|
|
1580
1592
|
this._handleTypingEvent(roomId, event);
|
|
1581
1593
|
});
|
|
1582
1594
|
|
|
1595
|
+
// AI 진행 표시(생각중/검색중/작성중) 구독 — typing 과 별개 채널(가산 UX). 구버전 SDK 는 미구독 → 기존 typing 만.
|
|
1596
|
+
const assistantStreamDestination = WebSocketPaths.getChatAssistantStreamDestination(roomId);
|
|
1597
|
+
await this.connectionManager.subscribe(assistantStreamDestination, (event) => {
|
|
1598
|
+
this._handleAssistantStreamEvent(roomId, event);
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1583
1601
|
// 멤버 변경 이벤트 — roomList 이벤트를 방 수준으로 변환하여 자동 emit
|
|
1584
1602
|
// 서버가 members 배열(입장/퇴장 사용자들) + participantCount(정확한 인원수) 포함
|
|
1585
1603
|
const memberJoinedHandler = (event) => {
|
|
@@ -1609,6 +1627,7 @@ class ChatClient extends EventEmitter {
|
|
|
1609
1627
|
chatDestination,
|
|
1610
1628
|
readDestination,
|
|
1611
1629
|
typingDestination,
|
|
1630
|
+
assistantStreamDestination,
|
|
1612
1631
|
memberJoinedHandler,
|
|
1613
1632
|
memberLeftHandler,
|
|
1614
1633
|
subscribedAt: new Date()
|
|
@@ -1634,6 +1653,9 @@ class ChatClient extends EventEmitter {
|
|
|
1634
1653
|
if (subscription.typingDestination) {
|
|
1635
1654
|
this.connectionManager.unsubscribe(subscription.typingDestination);
|
|
1636
1655
|
}
|
|
1656
|
+
if (subscription.assistantStreamDestination) {
|
|
1657
|
+
this.connectionManager.unsubscribe(subscription.assistantStreamDestination);
|
|
1658
|
+
}
|
|
1637
1659
|
|
|
1638
1660
|
// 멤버 변경 리스너 해제
|
|
1639
1661
|
if (subscription.memberJoinedHandler) {
|
|
@@ -1646,6 +1668,8 @@ class ChatClient extends EventEmitter {
|
|
|
1646
1668
|
// 타이핑 타이머 정리 (outgoing + incoming assistant 둘 다)
|
|
1647
1669
|
this._clearTypingTimer(roomId);
|
|
1648
1670
|
this._clearAssistantTypingTimer(roomId);
|
|
1671
|
+
// AI 진행 표시 상태/타이머 정리 (emit 없이 — 구독 해제 중)
|
|
1672
|
+
this._clearAssistantProgress(roomId, false);
|
|
1649
1673
|
|
|
1650
1674
|
// 현재 보고 있는 방이면 activeRoom 해제
|
|
1651
1675
|
if (this._activeRoomId === roomId) {
|
|
@@ -1855,6 +1879,9 @@ class ChatClient extends EventEmitter {
|
|
|
1855
1879
|
typing: false,
|
|
1856
1880
|
senderType: 'ASSISTANT'
|
|
1857
1881
|
});
|
|
1882
|
+
// AI 진행 표시도 해제 — 최종 메시지 도착 = 진행 종료(설계 §3.1·§4.1, 메시지=진실).
|
|
1883
|
+
// DONE 이 유실/지연돼도 메시지로 수렴. active:false emit.
|
|
1884
|
+
this._clearAssistantProgress(roomId, true, 'message');
|
|
1858
1885
|
}
|
|
1859
1886
|
|
|
1860
1887
|
// 상대방 메시지일 때만 newMessage + 자동 읽음 처리
|
|
@@ -2161,6 +2188,141 @@ class ChatClient extends EventEmitter {
|
|
|
2161
2188
|
}
|
|
2162
2189
|
}
|
|
2163
2190
|
|
|
2191
|
+
/**
|
|
2192
|
+
* AI 진행 표시(assistant-stream) 이벤트 수신 처리 — PHASE(단계)/DELTA(토큰 청크)/DONE(종료) 분기.
|
|
2193
|
+
*
|
|
2194
|
+
* <p>typing 과 별개의 가산 레이어 — UI 는 {@code active:true} 면 {@code text}(토큰 스트림 누적) 우선,
|
|
2195
|
+
* 없으면 {@code label}(단계) 표시, {@code active:false} 면 해제. PHASE→DELTA 흐름으로 "작성 중" 라벨이
|
|
2196
|
+
* 점진 렌더 텍스트로 자연 전환된다. 서버가 typing 도 이중발행하므로 구버전 SDK 는 본 채널 미구독 → 기존
|
|
2197
|
+
* typing 만으로 정상 동작(graceful).</p>
|
|
2198
|
+
* @private
|
|
2199
|
+
*/
|
|
2200
|
+
_handleAssistantStreamEvent(roomId, event) {
|
|
2201
|
+
if (!event || !event.type) return;
|
|
2202
|
+
|
|
2203
|
+
if (event.type === 'PHASE') {
|
|
2204
|
+
const phase = event.phase || null;
|
|
2205
|
+
const label = (phase && this._assistantPhaseLabels[phase]) || null;
|
|
2206
|
+
// 같은 stream 의 PHASE 재발행(B-3 synthesis heartbeat 등)이면 누적 텍스트 보존, 새 stream 이면 리셋.
|
|
2207
|
+
const prev = this._assistantProgress.get(roomId);
|
|
2208
|
+
const text = (prev && prev.streamId === event.streamId) ? (prev.text || '') : '';
|
|
2209
|
+
// 진행 상태 갱신 + TTL 재무장(heartbeat 재수신마다 연장). 같은 방의 이전 타이머는 reset.
|
|
2210
|
+
this._startAssistantProgressTimer(roomId, event, { phase, text });
|
|
2211
|
+
this.emit('assistantProgress', {
|
|
2212
|
+
roomId,
|
|
2213
|
+
streamId: event.streamId || null,
|
|
2214
|
+
personaId: event.personaId || null,
|
|
2215
|
+
phase,
|
|
2216
|
+
label,
|
|
2217
|
+
active: true,
|
|
2218
|
+
text: text || null
|
|
2219
|
+
});
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
if (event.type === 'DELTA') {
|
|
2224
|
+
// 토큰 청크(B) — 누적 텍스트에 이어붙여 점진 렌더용으로 emit. WRITING 단계에서 흐른다.
|
|
2225
|
+
const prev = this._assistantProgress.get(roomId);
|
|
2226
|
+
// stale DELTA 무시 — 이전 stream 의 늦은 토큰이 새 stream 표시를 오염시키지 않게(DONE 가드와 동형).
|
|
2227
|
+
if (prev?.streamId && event.streamId && prev.streamId !== event.streamId) {
|
|
2228
|
+
this.logger.debug(`Stale assistant DELTA ignored: room=${roomId}, delta=${event.streamId}, active=${prev.streamId}`);
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const phase = (prev && prev.phase) || 'WRITING';
|
|
2232
|
+
const text = (prev ? (prev.text || '') : '') + (event.delta || '');
|
|
2233
|
+
this._startAssistantProgressTimer(roomId, event, { phase, text });
|
|
2234
|
+
this.emit('assistantProgress', {
|
|
2235
|
+
roomId,
|
|
2236
|
+
streamId: event.streamId || null,
|
|
2237
|
+
personaId: event.personaId || null,
|
|
2238
|
+
phase,
|
|
2239
|
+
label: this._assistantPhaseLabels[phase] || null,
|
|
2240
|
+
active: true,
|
|
2241
|
+
text, // 지금까지 누적된 전체 텍스트(버블 렌더용)
|
|
2242
|
+
delta: event.delta || null, // 이번 청크(append 렌더 선호 시)
|
|
2243
|
+
seq: typeof event.seq === 'number' ? event.seq : null
|
|
2244
|
+
});
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
if (event.type === 'DONE') {
|
|
2249
|
+
// 종료(성공·실패 공통) — 상태/타이머 해제 + active:false emit(status/messageId 동봉, 권위 신호).
|
|
2250
|
+
const state = this._assistantProgress.get(roomId);
|
|
2251
|
+
// 늦게 도착한 이전 stream 의 DONE 무시 — 다음 stream 의 PHASE 이후 도착 시 새 진행 표시를 끄는 race 차단.
|
|
2252
|
+
// 메시지/assistant-stream 채널은 ordering 이 묶여 있지 않다. streamId 가 둘 다 있고 다를 때만 stale 판정
|
|
2253
|
+
// (UI 계약이 "active 만 보면 됨"이므로 SDK 가 stale false 를 막는다).
|
|
2254
|
+
if (state?.streamId && event.streamId && state.streamId !== event.streamId) {
|
|
2255
|
+
this.logger.debug(`Stale assistant DONE ignored: room=${roomId}, done=${event.streamId}, active=${state.streamId}`);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
// 메시지 도착으로 먼저 해제됐어도 DONE 은 한 번 더 발행될 수 있으나 active:false 는 idempotent(설계 §4.1).
|
|
2259
|
+
if (state && state.timer) clearTimeout(state.timer);
|
|
2260
|
+
this._assistantProgress.delete(roomId);
|
|
2261
|
+
this.emit('assistantProgress', {
|
|
2262
|
+
roomId,
|
|
2263
|
+
streamId: event.streamId || null,
|
|
2264
|
+
personaId: event.personaId || null,
|
|
2265
|
+
phase: null,
|
|
2266
|
+
label: null,
|
|
2267
|
+
active: false,
|
|
2268
|
+
status: event.status || null,
|
|
2269
|
+
messageId: event.messageId || null
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
// 그 외 미지 type 은 무시(forward-compat).
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
/**
|
|
2276
|
+
* AI 진행 상태 갱신 + TTL 타이머 재무장 — {@code expiresAt}(epochMillis) 기반, 누락/이상 시 fallback.
|
|
2277
|
+
* {@code fields}={phase, text}(PHASE/DELTA 가 해소해 전달 — DELTA 는 직전 phase 유지 + 누적 text).
|
|
2278
|
+
* 만료 = DONE·메시지 유실로 진행이 끊긴 것 → UI 가 영구 표시에 갇히지 않게 {@code active:false} 자동 emit.
|
|
2279
|
+
* @private
|
|
2280
|
+
*/
|
|
2281
|
+
_startAssistantProgressTimer(roomId, event, fields) {
|
|
2282
|
+
const prev = this._assistantProgress.get(roomId);
|
|
2283
|
+
if (prev && prev.timer) clearTimeout(prev.timer);
|
|
2284
|
+
|
|
2285
|
+
let ttl = this._assistantProgressFallbackTtlMs;
|
|
2286
|
+
if (typeof event.expiresAt === 'number') {
|
|
2287
|
+
// 비정상값 방어 — [5s, 120s] 로 clamp(과거 timestamp / 과대 TTL 모두 차단).
|
|
2288
|
+
ttl = Math.min(120000, Math.max(5000, event.expiresAt - Date.now()));
|
|
2289
|
+
}
|
|
2290
|
+
const timer = setTimeout(() => {
|
|
2291
|
+
this._clearAssistantProgress(roomId, true, 'timeout');
|
|
2292
|
+
this.logger.debug(`Assistant progress auto-cleared (timeout): room=${roomId}`);
|
|
2293
|
+
}, ttl);
|
|
2294
|
+
this._assistantProgress.set(roomId, {
|
|
2295
|
+
streamId: event.streamId || null,
|
|
2296
|
+
personaId: event.personaId || null,
|
|
2297
|
+
phase: fields.phase,
|
|
2298
|
+
text: fields.text || '',
|
|
2299
|
+
timer
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
/**
|
|
2304
|
+
* AI 진행 표시 상태/타이머 해제. {@code emit=true} 면 {@code active:false} 를 발행(메시지 도착/타임아웃 해제용).
|
|
2305
|
+
* 구독 해제 시엔 {@code emit=false} 로 조용히 정리.
|
|
2306
|
+
* @private
|
|
2307
|
+
*/
|
|
2308
|
+
_clearAssistantProgress(roomId, emit, reason) {
|
|
2309
|
+
const state = this._assistantProgress.get(roomId);
|
|
2310
|
+
if (!state) return;
|
|
2311
|
+
if (state.timer) clearTimeout(state.timer);
|
|
2312
|
+
this._assistantProgress.delete(roomId);
|
|
2313
|
+
if (emit) {
|
|
2314
|
+
this.emit('assistantProgress', {
|
|
2315
|
+
roomId,
|
|
2316
|
+
streamId: state.streamId || null,
|
|
2317
|
+
personaId: state.personaId || null,
|
|
2318
|
+
phase: null,
|
|
2319
|
+
label: null,
|
|
2320
|
+
active: false,
|
|
2321
|
+
reason: reason || null
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2164
2326
|
// ==================== 유틸리티 ====================
|
|
2165
2327
|
|
|
2166
2328
|
/**
|
|
@@ -2208,6 +2370,10 @@ class ChatClient extends EventEmitter {
|
|
|
2208
2370
|
this._assistantTypingTimers.forEach((timer) => clearTimeout(timer));
|
|
2209
2371
|
this._assistantTypingTimers.clear();
|
|
2210
2372
|
|
|
2373
|
+
// AI 진행 표시 상태/타이머도 전체 cleanup (구독 외 경로로 남은 roomId 방어).
|
|
2374
|
+
this._assistantProgress.forEach((state) => state.timer && clearTimeout(state.timer));
|
|
2375
|
+
this._assistantProgress.clear();
|
|
2376
|
+
|
|
2211
2377
|
// dedup bucket 전체 clear — unsubscribeRoom 이 방별로 정리하지만 구독 외 경로로 누적된 bucket
|
|
2212
2378
|
// (예: 구독 안 한 방의 push/listener) 이 있을 수 있어 안전하게 명시 정리.
|
|
2213
2379
|
this._seenChatMessageIdsByRoom.clear();
|
package/src/constants.js
CHANGED
|
@@ -319,6 +319,8 @@ export const WebSocketPaths = {
|
|
|
319
319
|
getChatDestination: (roomId) => `/topic/chat/${roomId}`,
|
|
320
320
|
getChatReadDestination: (roomId) => `/topic/chat/${roomId}/read`,
|
|
321
321
|
getChatTypingDestination: (roomId) => `/topic/chat/${roomId}/typing`,
|
|
322
|
+
// AI 진행 표시(생각중/검색중/작성중) + 토큰 스트리밍 — typing 과 별개 채널(가산 UX 레이어).
|
|
323
|
+
getChatAssistantStreamDestination: (roomId) => `/topic/chat/${roomId}/assistant-stream`,
|
|
322
324
|
|
|
323
325
|
// Room list (카톡 스타일 리스트 실시간 업데이트)
|
|
324
326
|
// 서버가 convertAndSendToUser(userId, "/queue/rooms", event) 로 전송
|
|
@@ -15,6 +15,7 @@ const CHAT_EVENT_MAP = [
|
|
|
15
15
|
['messageTranslated', 'messageTranslated'],
|
|
16
16
|
['messageRead', 'messageRead'],
|
|
17
17
|
['typing', 'typing'],
|
|
18
|
+
['assistantProgress', 'assistantProgress'],
|
|
18
19
|
['memberJoined', 'memberJoined'],
|
|
19
20
|
['memberLeft', 'memberLeft'],
|
|
20
21
|
['roomSubscribed', 'roomSubscribed'],
|
package/types/index.d.ts
CHANGED
|
@@ -236,7 +236,7 @@ export interface AssistantPersonaResponse {
|
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
238
|
* 어시스턴트 도구 실행 결과 — 서버 {@code AssistantToolResult}.
|
|
239
|
-
* {@link ChatClient.summarizeWithAssistant}
|
|
239
|
+
* {@link ChatClient.summarizeWithAssistant} 반환.
|
|
240
240
|
*/
|
|
241
241
|
export interface AssistantToolResult {
|
|
242
242
|
/**
|
|
@@ -254,6 +254,16 @@ export interface AssistantToolResult {
|
|
|
254
254
|
detail: string | null;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/** {@link ChatClient.translateWithAssistant} 반환 — 요청자 전용 온디맨드 번역 응답. */
|
|
258
|
+
export interface OnDemandTranslationResponse {
|
|
259
|
+
/** 번역된 텍스트. */
|
|
260
|
+
translatedText: string;
|
|
261
|
+
/** 번역 대상 원본 메시지의 {@code messageId}. */
|
|
262
|
+
sourceMessageId: string;
|
|
263
|
+
/** 요청한 목표 언어. */
|
|
264
|
+
targetLang: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
257
267
|
/** {@link ChatClient.summarizeWithAssistant} 옵션 — 서버 {@code SummarizeToolRequest}. */
|
|
258
268
|
export interface SummarizeToolRequest {
|
|
259
269
|
/** 요약을 수행할 어시스턴트 ID ({@link AssistantPersonaResponse.id}). 필수. */
|
|
@@ -266,12 +276,10 @@ export interface SummarizeToolRequest {
|
|
|
266
276
|
|
|
267
277
|
/** {@link ChatClient.translateWithAssistant} 옵션 — 서버 {@code TranslateToolRequest}. */
|
|
268
278
|
export interface TranslateToolRequest {
|
|
269
|
-
/** 번역을 수행할 어시스턴트 ID ({@link AssistantPersonaResponse.id}). 필수. */
|
|
270
|
-
personaId: string;
|
|
271
279
|
/** 번역 대상 메시지의 {@code messageId}. 필수. */
|
|
272
280
|
sourceMessageId: string;
|
|
273
|
-
/** 목표 언어 (예: {@code 'en'}).
|
|
274
|
-
targetLang
|
|
281
|
+
/** 목표 언어 (예: {@code 'en'}). 필수. */
|
|
282
|
+
targetLang: string;
|
|
275
283
|
}
|
|
276
284
|
|
|
277
285
|
/**
|
|
@@ -378,6 +386,7 @@ export const WebSocketPaths: {
|
|
|
378
386
|
getChatDestination(roomId: string): string;
|
|
379
387
|
getChatReadDestination(roomId: string): string;
|
|
380
388
|
getChatTypingDestination(roomId: string): string;
|
|
389
|
+
getChatAssistantStreamDestination(roomId: string): string;
|
|
381
390
|
readonly ROOM_LIST_USER_DESTINATION: string;
|
|
382
391
|
getWebRTCDestination(roomId: string): string;
|
|
383
392
|
getWebRTCUserDestination(): string;
|
|
@@ -735,6 +744,42 @@ export interface TypingEvent {
|
|
|
735
744
|
senderType: 'USER' | 'ASSISTANT';
|
|
736
745
|
}
|
|
737
746
|
|
|
747
|
+
/**
|
|
748
|
+
* AI 진행 표시 이벤트 — `/topic/chat/{roomId}/assistant-stream` 의 PHASE/DONE.
|
|
749
|
+
*
|
|
750
|
+
* typing 과 별개의 가산 UX 레이어(설계 ASSISTANT_PROGRESS_STREAMING_DESIGN.md). 서버가 typing 도 이중발행하므로
|
|
751
|
+
* 구버전 SDK 는 본 이벤트를 못 받아도 기존 typing 으로 정상 동작한다. UI 는 `active:true` 면 `label`(또는 `phase`)
|
|
752
|
+
* 로 단계를 표시하고, `active:false`(DONE·어시 메시지 도착·TTL 만료) 면 해제한다.
|
|
753
|
+
*
|
|
754
|
+
* 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행 — 멘션/버튼/14인 경로는 본 이벤트 없이 typing 만.
|
|
755
|
+
*/
|
|
756
|
+
export interface AssistantProgressEvent {
|
|
757
|
+
/** 채팅방 ID. */
|
|
758
|
+
roomId: string;
|
|
759
|
+
/** 어시 응답 1회분 식별(멀티 응답/중복 dedup). 일부 종료 emit 에선 null. */
|
|
760
|
+
streamId: string | null;
|
|
761
|
+
/** 진행 주체 페르소나 ID. */
|
|
762
|
+
personaId: string | null;
|
|
763
|
+
/** 진행 단계 — `active:true` 일 때. 종료 시 null. */
|
|
764
|
+
phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
|
|
765
|
+
/** phase 의 기본 표시 라벨(예: "생각 중…"). SDK 매핑 — 커스텀하려면 `phase` 사용. 종료 시 null. */
|
|
766
|
+
label: string | null;
|
|
767
|
+
/** true=진행중(PHASE/DELTA) / false=종료(DONE·메시지 도착·TTL). */
|
|
768
|
+
active: boolean;
|
|
769
|
+
/** 토큰 스트림 누적 텍스트(B) — DELTA 수신 시 지금까지 모인 전체. 버블에 그대로 렌더. PHASE-only/종료 시 null. */
|
|
770
|
+
text?: string | null;
|
|
771
|
+
/** 이번 토큰 청크(B) — append 렌더를 선호할 때. DELTA 수신 시만. */
|
|
772
|
+
delta?: string | null;
|
|
773
|
+
/** 토큰 청크 순서/누락 감지(B) — DELTA 수신 시만. */
|
|
774
|
+
seq?: number | null;
|
|
775
|
+
/** 종료(DONE) 결과 — DONE 수신 시만. */
|
|
776
|
+
status?: 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT' | null;
|
|
777
|
+
/** 종료(DONE, SUCCEEDED) 시 영속된 최종 메시지 ID. */
|
|
778
|
+
messageId?: string | null;
|
|
779
|
+
/** 종료 사유 — 'message'(어시 메시지 도착) / 'timeout'(TTL). DONE 수신 해제엔 없음. */
|
|
780
|
+
reason?: 'message' | 'timeout' | null;
|
|
781
|
+
}
|
|
782
|
+
|
|
738
783
|
// ============================================================================
|
|
739
784
|
// Options — constructor & method
|
|
740
785
|
// ============================================================================
|
|
@@ -1056,7 +1101,7 @@ export interface RetentionCleanupPayload {
|
|
|
1056
1101
|
* TalkFlowClient 이벤트 맵 — 런타임 {@code TalkFlowClient.js} 의 {@code this.emit(...)} 호출 기반.
|
|
1057
1102
|
*
|
|
1058
1103
|
* <p><b>Connection 관련</b>: stateChange, connected, disconnected, reconnecting, connectionError, tokenSet, loggedOut</p>
|
|
1059
|
-
* <p><b>Chat 관련 (ChatClient 이벤트를 이 이름으로 re-emit)</b>: chatMessage, newChatMessage, messageUpdated, messageDeleted, reactionChanged, linkPreviewAttached, messageRead, typing, memberJoined, memberLeft, roomSubscribed, roomUnsubscribed, roomListSubscribed, roomListUnsubscribed, roomListUpdate, roomListMessage, roomListCreated, roomListJoined, roomListLeft, roomListSelfLeft, roomListRoomUpdated, retentionCleanup</p>
|
|
1104
|
+
* <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>
|
|
1060
1105
|
* <p><b>Push 관련</b>: pushEnabled, pushFailed, pushNotification</p>
|
|
1061
1106
|
* <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>
|
|
1062
1107
|
*/
|
|
@@ -1085,6 +1130,7 @@ export interface TalkFlowClientEvents {
|
|
|
1085
1130
|
messageTranslated: ChatMessageReceivedPayload;
|
|
1086
1131
|
messageRead: MessageReadEventPayload;
|
|
1087
1132
|
typing: TypingEvent;
|
|
1133
|
+
assistantProgress: AssistantProgressEvent;
|
|
1088
1134
|
memberJoined: MemberChangePayload;
|
|
1089
1135
|
memberLeft: MemberChangePayload;
|
|
1090
1136
|
roomSubscribed: { roomId: string };
|
|
@@ -1198,6 +1244,7 @@ export interface ChatClientEvents {
|
|
|
1198
1244
|
retentionCleanup: RetentionCleanupPayload;
|
|
1199
1245
|
messageRead: MessageReadEventPayload;
|
|
1200
1246
|
typing: TypingEvent;
|
|
1247
|
+
assistantProgress: AssistantProgressEvent;
|
|
1201
1248
|
}
|
|
1202
1249
|
|
|
1203
1250
|
/**
|
|
@@ -1261,8 +1308,8 @@ export class ChatClient implements SdkEventEmitter<ChatClientEvents> {
|
|
|
1261
1308
|
rateAssistantMessage(roomId: string, messageId: string, rating: number, comment?: string): Promise<void>;
|
|
1262
1309
|
/** 어시스턴트로 대화 요약 (버튼 진입점, 동기). */
|
|
1263
1310
|
summarizeWithAssistant(roomId: string, options: SummarizeToolRequest): Promise<AssistantToolResult>;
|
|
1264
|
-
/**
|
|
1265
|
-
translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<
|
|
1311
|
+
/** 특정 메시지 온디맨드 번역 (버튼 진입점, 동기, 요청자 전용 응답). */
|
|
1312
|
+
translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<OnDemandTranslationResponse>;
|
|
1266
1313
|
/** 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용, 참여자 전체. 미등록 시 404. */
|
|
1267
1314
|
getRoomPmPrompt(roomId: string): Promise<PmPromptLayerResponse>;
|
|
1268
1315
|
/** 방 PM 프롬프트 레이어 등록/수정 (upsert, 새 버전 자동 기록) — 방 주인(OWNER) 전용. content 1~2,000자. */
|
|
@@ -1602,8 +1649,8 @@ export default class TalkFlowClient implements SdkEventEmitter<TalkFlowClientEve
|
|
|
1602
1649
|
rateAssistantMessage(roomId: string, messageId: string, rating: number, comment?: string): Promise<void>;
|
|
1603
1650
|
/** 어시스턴트로 대화 요약 (버튼 진입점, 동기). */
|
|
1604
1651
|
summarizeWithAssistant(roomId: string, options: SummarizeToolRequest): Promise<AssistantToolResult>;
|
|
1605
|
-
/**
|
|
1606
|
-
translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<
|
|
1652
|
+
/** 특정 메시지 온디맨드 번역 (버튼 진입점, 동기, 요청자 전용 응답). */
|
|
1653
|
+
translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<OnDemandTranslationResponse>;
|
|
1607
1654
|
/** 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용, 참여자 전체. 미등록 시 404. */
|
|
1608
1655
|
getRoomPmPrompt(roomId: string): Promise<PmPromptLayerResponse>;
|
|
1609
1656
|
/** 방 PM 프롬프트 레이어 등록/수정 (upsert, 새 버전 자동 기록) — 방 주인(OWNER) 전용. content 1~2,000자. */
|