@things-factory/ai-assistant 10.1.208 → 10.1.210
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/client/components/assistant-chat.ts +238 -9
- package/client/components/assistant-dock.ts +9 -4
- package/client/components/assistant-invite.ts +138 -0
- package/client/components/assistant-session-manager.ts +53 -12
- package/client/components/proposal-request.test.ts +10 -0
- package/client/components/roster-identity.test.ts +2 -12
- package/client/components/roster-identity.ts +0 -6
- package/client/utils/assistant-session-actions.ts +3 -1
- package/client/utils/assistant-session-admin.ts +9 -2
- package/client/utils/assistant-session-controller.ts +1 -1
- package/client/utils/assistant-typing.test.ts +33 -0
- package/client/utils/assistant-typing.ts +53 -0
- package/dist-client/components/assistant-chat.d.ts +28 -0
- package/dist-client/components/assistant-chat.js +259 -8
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/assistant-dock.js +12 -4
- package/dist-client/components/assistant-dock.js.map +1 -1
- package/dist-client/components/assistant-invite.d.ts +39 -0
- package/dist-client/components/assistant-invite.js +150 -0
- package/dist-client/components/assistant-invite.js.map +1 -0
- package/dist-client/components/assistant-session-manager.d.ts +5 -1
- package/dist-client/components/assistant-session-manager.js +40 -10
- package/dist-client/components/assistant-session-manager.js.map +1 -1
- package/dist-client/components/proposal-request.test.js +8 -0
- package/dist-client/components/proposal-request.test.js.map +1 -1
- package/dist-client/components/roster-identity.d.ts +0 -2
- package/dist-client/components/roster-identity.js +0 -7
- package/dist-client/components/roster-identity.js.map +1 -1
- package/dist-client/components/roster-identity.test.js +2 -13
- package/dist-client/components/roster-identity.test.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/assistant-session-actions.d.ts +1 -0
- package/dist-client/utils/assistant-session-actions.js +3 -1
- package/dist-client/utils/assistant-session-actions.js.map +1 -1
- package/dist-client/utils/assistant-session-admin.d.ts +5 -2
- package/dist-client/utils/assistant-session-admin.js +3 -0
- package/dist-client/utils/assistant-session-admin.js.map +1 -1
- package/dist-client/utils/assistant-session-controller.d.ts +1 -1
- package/dist-client/utils/assistant-session-controller.js.map +1 -1
- package/dist-client/utils/assistant-typing.d.ts +20 -0
- package/dist-client/utils/assistant-typing.js +48 -0
- package/dist-client/utils/assistant-typing.js.map +1 -0
- package/dist-client/utils/assistant-typing.test.d.ts +1 -0
- package/dist-client/utils/assistant-typing.test.js +30 -0
- package/dist-client/utils/assistant-typing.test.js.map +1 -0
- package/dist-server/service/assistant-chat-resolver.js +5 -12
- package/dist-server/service/assistant-chat-resolver.js.map +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +1 -1
- package/dist-server/service/assistant-mode/index.js +2 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/mode-categories.js +9 -2
- package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
- package/dist-server/service/assistant-mode/screen-rules.d.ts +6 -0
- package/dist-server/service/assistant-mode/screen-rules.js +10 -3
- package/dist-server/service/assistant-mode/screen-rules.js.map +1 -1
- package/dist-server/service/chat-message/chat-message-room.js +3 -1
- package/dist-server/service/chat-message/chat-message-room.js.map +1 -1
- package/dist-server/service/chat-message/chat-typing-subscription.d.ts +10 -0
- package/dist-server/service/chat-message/chat-typing-subscription.js +68 -0
- package/dist-server/service/chat-message/chat-typing-subscription.js.map +1 -0
- package/dist-server/service/chat-message/chat-typing.d.ts +28 -0
- package/dist-server/service/chat-message/chat-typing.js +40 -0
- package/dist-server/service/chat-message/chat-typing.js.map +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.d.ts +1 -0
- package/dist-server/service/chat-session/attachment-tool-result.js +6 -0
- package/dist-server/service/chat-session/attachment-tool-result.js.map +1 -1
- package/dist-server/service/chat-session/attachment-tools.js +2 -2
- package/dist-server/service/chat-session/attachment-tools.js.map +1 -1
- package/dist-server/service/chat-session/chat-session.d.ts +2 -1
- package/dist-server/service/chat-session/chat-session.js +1 -1
- package/dist-server/service/chat-session/chat-session.js.map +1 -1
- package/dist-server/service/chat-session/session-activity-broadcast.d.ts +12 -0
- package/dist-server/service/chat-session/session-activity-broadcast.js +15 -0
- package/dist-server/service/chat-session/session-activity-broadcast.js.map +1 -1
- package/dist-server/service/chat-session/session-activity-publish.d.ts +2 -0
- package/dist-server/service/chat-session/session-activity-publish.js +5 -2
- package/dist-server/service/chat-session/session-activity-publish.js.map +1 -1
- package/dist-server/service/chat-session/session-visibility.d.ts +17 -2
- package/dist-server/service/chat-session/session-visibility.js +28 -3
- package/dist-server/service/chat-session/session-visibility.js.map +1 -1
- package/dist-server/service/chat-session/session-write-gate.d.ts +1 -1
- package/dist-server/service/chat-session/session-write-gate.js +1 -1
- package/dist-server/service/chat-session/session-write-gate.js.map +1 -1
- package/dist-server/service/chat-session-participant/index.d.ts +1 -0
- package/dist-server/service/chat-session-participant/index.js +4 -1
- package/dist-server/service/chat-session-participant/index.js.map +1 -1
- package/dist-server/service/chat-session-participant/invite-notification.d.ts +16 -1
- package/dist-server/service/chat-session-participant/invite-notification.js +34 -5
- package/dist-server/service/chat-session-participant/invite-notification.js.map +1 -1
- package/dist-server/service/chat-session-participant/mention-invite.d.ts +30 -4
- package/dist-server/service/chat-session-participant/mention-invite.js +78 -31
- package/dist-server/service/chat-session-participant/mention-invite.js.map +1 -1
- package/dist-server/service/chat-session-participant/read-roster.js +5 -2
- package/dist-server/service/chat-session-participant/read-roster.js.map +1 -1
- package/dist-server/service/chat-session-resolver.d.ts +27 -0
- package/dist-server/service/chat-session-resolver.js +226 -25
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/service/data-entry/entry-target.d.ts +6 -0
- package/dist-server/service/data-entry/entry-target.js.map +1 -1
- package/dist-server/service/data-entry/entry-tools.js +2 -2
- package/dist-server/service/data-entry/entry-tools.js.map +1 -1
- package/dist-server/service/data-entry/entry-update.js +1 -1
- package/dist-server/service/data-entry/entry-update.js.map +1 -1
- package/dist-server/service/data-read/list-read.d.ts +10 -0
- package/dist-server/service/data-read/list-read.js +12 -0
- package/dist-server/service/data-read/list-read.js.map +1 -1
- package/dist-server/service/data-read/read-target.d.ts +5 -0
- package/dist-server/service/data-read/read-target.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -2
- package/dist-server/service/index.js +9 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/testing/conversation-script.d.ts +1 -1
- package/dist-server/service/testing/conversation-script.js.map +1 -1
- package/dist-server/service/testing/mode-conformance.js +2 -0
- package/dist-server/service/testing/mode-conformance.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/assistant-chat-resolver.ts +7 -12
- package/server/service/assistant-mode/index.ts +1 -1
- package/server/service/assistant-mode/mode-categories.ts +11 -3
- package/server/service/assistant-mode/mode-front.test.ts +53 -1
- package/server/service/assistant-mode/screen-rules.test.ts +7 -1
- package/server/service/assistant-mode/screen-rules.ts +11 -2
- package/server/service/chat-message/chat-message-room.test.ts +7 -2
- package/server/service/chat-message/chat-message-room.ts +3 -1
- package/server/service/chat-message/chat-typing-subscription.ts +52 -0
- package/server/service/chat-message/chat-typing.test.ts +45 -0
- package/server/service/chat-message/chat-typing.ts +48 -0
- package/server/service/chat-session/attachment-tool-result.test.ts +9 -1
- package/server/service/chat-session/attachment-tool-result.ts +7 -0
- package/server/service/chat-session/attachment-tools.ts +3 -3
- package/server/service/chat-session/chat-session.ts +3 -2
- package/server/service/chat-session/group-room.test.ts +233 -0
- package/server/service/chat-session/session-activity-broadcast.ts +20 -0
- package/server/service/chat-session/session-activity-publish.ts +6 -1
- package/server/service/chat-session/session-activity-receivers.test.ts +26 -0
- package/server/service/chat-session/session-visibility.test.ts +32 -1
- package/server/service/chat-session/session-visibility.ts +33 -4
- package/server/service/chat-session/session-write-gate.test.ts +7 -1
- package/server/service/chat-session/session-write-gate.ts +2 -2
- package/server/service/chat-session-participant/index.ts +2 -0
- package/server/service/chat-session-participant/invite-notification.test.ts +36 -1
- package/server/service/chat-session-participant/invite-notification.ts +43 -5
- package/server/service/chat-session-participant/mention-invite.test.ts +49 -1
- package/server/service/chat-session-participant/mention-invite.ts +82 -28
- package/server/service/chat-session-participant/read-roster.test.ts +13 -4
- package/server/service/chat-session-participant/read-roster.ts +6 -3
- package/server/service/chat-session-resolver.ts +204 -27
- package/server/service/data-entry/data-entry-update.test.ts +8 -0
- package/server/service/data-entry/entry-target.ts +6 -0
- package/server/service/data-entry/entry-tools.ts +2 -2
- package/server/service/data-entry/entry-update.ts +1 -1
- package/server/service/data-read/list-missing.test.ts +49 -0
- package/server/service/data-read/list-read.ts +18 -0
- package/server/service/data-read/read-target.ts +5 -0
- package/server/service/index.ts +6 -1
- package/server/service/testing/conversation-script.ts +1 -1
- package/server/service/testing/mode-conformance.ts +1 -0
- package/test/assistant-dock.test.ts +5 -1
- package/test/assistant-session-manager.test.ts +4 -2
- package/test/open-doors.test.ts +10 -5
- package/translations/en.json +29 -4
- package/translations/es.json +29 -4
- package/translations/ja.json +29 -4
- package/translations/ko.json +29 -4
- package/translations/ms.json +29 -4
- package/translations/zh.json +29 -4
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
var OxAssistantChat_1;
|
|
2
2
|
import { __decorate, __metadata } from "tslib";
|
|
3
3
|
import { replyDestination, quotedReply } from './reply-message.js';
|
|
4
|
-
import {
|
|
4
|
+
import { rosterName } from './roster-identity';
|
|
5
|
+
import { notify } from '@operato/layout';
|
|
6
|
+
import './assistant-invite.js';
|
|
7
|
+
import { TypingRoom } from '../utils/assistant-typing.js';
|
|
5
8
|
/**
|
|
6
9
|
* <ox-assistant-chat> — AI 도우미 채팅 컴포넌트 (Lit).
|
|
7
10
|
*
|
|
@@ -235,6 +238,7 @@ const CHAT_MESSAGE_SUBSCRIPTION = gql `
|
|
|
235
238
|
// C4 presence/roster — 세션 참여자 목록 + 마지막 접속(online 판정용).
|
|
236
239
|
const CHAT_PARTICIPANTS_QUERY = gql `
|
|
237
240
|
query ChatSessionParticipants($sessionId: String!) {
|
|
241
|
+
mayInviteToAISession(sessionId: $sessionId)
|
|
238
242
|
chatSessionParticipants(sessionId: $sessionId) {
|
|
239
243
|
id
|
|
240
244
|
role
|
|
@@ -243,10 +247,38 @@ const CHAT_PARTICIPANTS_QUERY = gql `
|
|
|
243
247
|
id
|
|
244
248
|
name
|
|
245
249
|
username
|
|
250
|
+
email
|
|
246
251
|
}
|
|
247
252
|
}
|
|
248
253
|
}
|
|
249
254
|
`;
|
|
255
|
+
/*
|
|
256
|
+
* A group conversation (ADR-0073 decision 3) - bringing someone in and sending them out, and the room's
|
|
257
|
+
* "typing" line. The doors decide who may; the roster only offers what the server said this person may do.
|
|
258
|
+
*/
|
|
259
|
+
const INVITE_MUTATION = gql `
|
|
260
|
+
mutation InviteToAISession($sessionId: String!, $userIds: [String!]!) {
|
|
261
|
+
inviteToAISession(sessionId: $sessionId, userIds: $userIds)
|
|
262
|
+
}
|
|
263
|
+
`;
|
|
264
|
+
const REMOVE_MUTATION = gql `
|
|
265
|
+
mutation RemoveFromAISession($sessionId: String!, $userId: String!) {
|
|
266
|
+
removeFromAISession(sessionId: $sessionId, userId: $userId)
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
const SAY_TYPING_MUTATION = gql `
|
|
270
|
+
mutation SayTyping($sessionId: String!) {
|
|
271
|
+
sayTyping(sessionId: $sessionId)
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
const CHAT_TYPING_SUBSCRIPTION = gql `
|
|
275
|
+
subscription ChatTyping($sessionId: String!) {
|
|
276
|
+
chatTyping(sessionId: $sessionId) {
|
|
277
|
+
userId
|
|
278
|
+
name
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
250
282
|
const TOUCH_PRESENCE_MUTATION = gql `
|
|
251
283
|
mutation TouchPresence($sessionId: String!) {
|
|
252
284
|
touchPresence(sessionId: $sessionId)
|
|
@@ -314,6 +346,11 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
314
346
|
/** C4 presence/roster — 세션 참여자 목록. lastSeenAt 으로 online 판정. */
|
|
315
347
|
this.participants = [];
|
|
316
348
|
this.rosterLoaded = false;
|
|
349
|
+
/** The name box under the roster is open. */
|
|
350
|
+
this.inviting = false;
|
|
351
|
+
/** Who is typing now, by name - kept by `typingRoom`, redrawn on each notice and as they fade. */
|
|
352
|
+
this.typingNames = [];
|
|
353
|
+
this.typingRoom = new TypingRoom();
|
|
317
354
|
/** roster online 재평가 트리거 — heartbeat tick 마다 갱신(시간 경과 반영). */
|
|
318
355
|
this._presenceTick = 0;
|
|
319
356
|
/** 이미 transcript 에 반영된 서버 메시지 id 집합 — 자기 에코·중복 방송 차단. */
|
|
@@ -850,6 +887,52 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
850
887
|
.roster-chip.online .roster-dot {
|
|
851
888
|
background: #22c55e;
|
|
852
889
|
}
|
|
890
|
+
.roster-remove {
|
|
891
|
+
display: inline-flex;
|
|
892
|
+
border: 0;
|
|
893
|
+
padding: 0;
|
|
894
|
+
margin-left: 1px;
|
|
895
|
+
background: none;
|
|
896
|
+
color: inherit;
|
|
897
|
+
opacity: 0.6;
|
|
898
|
+
cursor: pointer;
|
|
899
|
+
border-radius: 50%;
|
|
900
|
+
}
|
|
901
|
+
.roster-remove:hover {
|
|
902
|
+
opacity: 1;
|
|
903
|
+
}
|
|
904
|
+
.roster-remove md-icon {
|
|
905
|
+
--md-icon-size: 11px;
|
|
906
|
+
}
|
|
907
|
+
.roster-invite {
|
|
908
|
+
display: inline-flex;
|
|
909
|
+
align-items: center;
|
|
910
|
+
gap: 2px;
|
|
911
|
+
padding: 1px 7px 1px 5px;
|
|
912
|
+
border: 1px solid var(--md-sys-color-outline-variant, #cbd5e1);
|
|
913
|
+
border-radius: 9px;
|
|
914
|
+
background: none;
|
|
915
|
+
color: inherit;
|
|
916
|
+
font: inherit;
|
|
917
|
+
font-size: 10px;
|
|
918
|
+
cursor: pointer;
|
|
919
|
+
}
|
|
920
|
+
.roster-invite:hover {
|
|
921
|
+
background: var(--md-sys-color-surface-container-high, #eef2f7);
|
|
922
|
+
}
|
|
923
|
+
.roster-invite md-icon {
|
|
924
|
+
--md-icon-size: 12px;
|
|
925
|
+
}
|
|
926
|
+
.typing-line .typing-dots { gap: 3px; }
|
|
927
|
+
.typing-line .typing-dots > span { width: 4px; height: 4px; }
|
|
928
|
+
.typing-line {
|
|
929
|
+
display: flex;
|
|
930
|
+
align-items: center;
|
|
931
|
+
gap: 6px;
|
|
932
|
+
margin: 0 2px 4px;
|
|
933
|
+
color: var(--md-sys-color-on-surface-variant, #475569);
|
|
934
|
+
font-size: 11.5px;
|
|
935
|
+
}
|
|
853
936
|
|
|
854
937
|
/* ── Session tabs ─────────────────────────────────── */
|
|
855
938
|
.session-tabs {
|
|
@@ -2560,6 +2643,10 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2560
2643
|
this.input = '';
|
|
2561
2644
|
this.participants = [];
|
|
2562
2645
|
this.rosterLoaded = false;
|
|
2646
|
+
this.mayInvite = undefined;
|
|
2647
|
+
this.inviting = false;
|
|
2648
|
+
this.typingRoom.clear();
|
|
2649
|
+
this.typingNames = [];
|
|
2563
2650
|
this.replyingTo = undefined;
|
|
2564
2651
|
this._seenMessageIds.clear(); // 세션 전환 — 에코 dedup 컨텍스트 리셋
|
|
2565
2652
|
if (this.sessionId) {
|
|
@@ -2629,6 +2716,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2629
2716
|
this._stopMessageSubscription();
|
|
2630
2717
|
if (!this.sessionId)
|
|
2631
2718
|
return;
|
|
2719
|
+
this._startTypingSubscription();
|
|
2632
2720
|
try {
|
|
2633
2721
|
this._messageSub = client
|
|
2634
2722
|
.subscribe({ query: CHAT_MESSAGE_SUBSCRIPTION, variables: { sessionId: this.sessionId } })
|
|
@@ -2643,7 +2731,49 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2643
2731
|
/* 구독 미지원 환경 — 조용히 스킵 */
|
|
2644
2732
|
}
|
|
2645
2733
|
}
|
|
2734
|
+
/** The room's "typing" line - notices of the others, which fade by themselves (`TypingRoom`). */
|
|
2735
|
+
_startTypingSubscription() {
|
|
2736
|
+
try {
|
|
2737
|
+
this._typingSub = client
|
|
2738
|
+
.subscribe({ query: CHAT_TYPING_SUBSCRIPTION, variables: { sessionId: this.sessionId } })
|
|
2739
|
+
.subscribe({
|
|
2740
|
+
next: ({ data }) => {
|
|
2741
|
+
if (!data?.chatTyping)
|
|
2742
|
+
return;
|
|
2743
|
+
this.typingRoom.heard(data.chatTyping);
|
|
2744
|
+
this._refreshTyping();
|
|
2745
|
+
},
|
|
2746
|
+
error: () => {
|
|
2747
|
+
/* no typing line - the conversation itself is unaffected */
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
catch {
|
|
2752
|
+
/* subscriptions unavailable */
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
_refreshTyping() {
|
|
2756
|
+
this.typingNames = this.typingRoom.typing();
|
|
2757
|
+
if (this.typingNames.length && !this._typingTimer) {
|
|
2758
|
+
this._typingTimer = setInterval(() => this._refreshTyping(), 1_000);
|
|
2759
|
+
}
|
|
2760
|
+
else if (!this.typingNames.length && this._typingTimer) {
|
|
2761
|
+
clearInterval(this._typingTimer);
|
|
2762
|
+
this._typingTimer = undefined;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2646
2765
|
_stopMessageSubscription() {
|
|
2766
|
+
try {
|
|
2767
|
+
this._typingSub?.unsubscribe();
|
|
2768
|
+
}
|
|
2769
|
+
catch {
|
|
2770
|
+
/* already closed */
|
|
2771
|
+
}
|
|
2772
|
+
this._typingSub = undefined;
|
|
2773
|
+
if (this._typingTimer) {
|
|
2774
|
+
clearInterval(this._typingTimer);
|
|
2775
|
+
this._typingTimer = undefined;
|
|
2776
|
+
}
|
|
2647
2777
|
if (this._messageSub) {
|
|
2648
2778
|
try {
|
|
2649
2779
|
this._messageSub.unsubscribe();
|
|
@@ -2663,6 +2793,9 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2663
2793
|
* 자기 발신 메시지의 id 는 전송 reconcile 에서 이미 _seenMessageIds 에 등록됨.
|
|
2664
2794
|
*/
|
|
2665
2795
|
_onRemoteMessage(m) {
|
|
2796
|
+
/* Their words arrived - they are not typing them any more. */
|
|
2797
|
+
if (this.typingRoom.spoke(m?.creator?.id))
|
|
2798
|
+
this._refreshTyping();
|
|
2666
2799
|
const next = receiveRemoteMessage(this.lines, this._seenMessageIds, m);
|
|
2667
2800
|
if (!next)
|
|
2668
2801
|
return;
|
|
@@ -2687,6 +2820,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2687
2820
|
if (this.sessionId !== sessionId)
|
|
2688
2821
|
return;
|
|
2689
2822
|
this.participants = result.data?.chatSessionParticipants ?? [];
|
|
2823
|
+
this.mayInvite = result.data?.mayInviteToAISession === true;
|
|
2690
2824
|
this.rosterLoaded = true;
|
|
2691
2825
|
}
|
|
2692
2826
|
catch {
|
|
@@ -2967,6 +3101,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
2967
3101
|
<button type="button" aria-label=${i18next.t('ai-assistant.button.cancel-reply')}
|
|
2968
3102
|
@click=${() => this.cancelReply()}><md-icon>close</md-icon></button>
|
|
2969
3103
|
</div>` : nothing}
|
|
3104
|
+
${this.renderTyping()}
|
|
2970
3105
|
${this.renderInviteNotice()}
|
|
2971
3106
|
${this.addressesPeople
|
|
2972
3107
|
? html `<div class="send-destination" aria-live="polite">
|
|
@@ -3148,7 +3283,8 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3148
3283
|
.map(([token]) => token);
|
|
3149
3284
|
if (incoming.length === 0)
|
|
3150
3285
|
return nothing;
|
|
3151
|
-
|
|
3286
|
+
/* What the server said this person may do here - no copy of the rule on the screen (`maySeat`). */
|
|
3287
|
+
const inviteState = !this.rosterLoaded || this.mayInvite === undefined ? 'checking' : this.mayInvite ? 'allowed' : 'denied';
|
|
3152
3288
|
const mayInvite = inviteState === 'allowed';
|
|
3153
3289
|
const names = incoming.map(token => `@${token}`).join(', ');
|
|
3154
3290
|
return html `
|
|
@@ -3160,10 +3296,15 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3160
3296
|
names,
|
|
3161
3297
|
defaultValue: '{names} will join this conversation when you send'
|
|
3162
3298
|
})
|
|
3163
|
-
:
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3299
|
+
: this.visibility === 'private'
|
|
3300
|
+
? i18next.t('ai-assistant.text.cannot-invite-private', {
|
|
3301
|
+
names,
|
|
3302
|
+
defaultValue: '{names} will not join — a private conversation is yours alone. Make it a group to bring people in'
|
|
3303
|
+
})
|
|
3304
|
+
: i18next.t('ai-assistant.text.cannot-invite-group', {
|
|
3305
|
+
names,
|
|
3306
|
+
defaultValue: '{names} will not join — only the person who started this group can bring someone in'
|
|
3307
|
+
})}
|
|
3167
3308
|
</span>
|
|
3168
3309
|
</div>
|
|
3169
3310
|
`;
|
|
@@ -3294,6 +3435,7 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3294
3435
|
onTextareaInput(e) {
|
|
3295
3436
|
const ta = e.target;
|
|
3296
3437
|
this.input = ta.value;
|
|
3438
|
+
this._sayTyping();
|
|
3297
3439
|
if (this.inputDebounceTimer)
|
|
3298
3440
|
clearTimeout(this.inputDebounceTimer);
|
|
3299
3441
|
this.inputDebounceTimer = setTimeout(() => {
|
|
@@ -3597,27 +3739,120 @@ let OxAssistantChat = class OxAssistantChat extends LitElement {
|
|
|
3597
3739
|
`;
|
|
3598
3740
|
}
|
|
3599
3741
|
/** C4 roster — 세션 참여자 + online 표시. 참여자 2명 미만이면 숨김(혼자면 의미 없음). */
|
|
3742
|
+
/**
|
|
3743
|
+
* Who is in the room. A group shows it always - its people are the point of it, and the owner brings them in
|
|
3744
|
+
* here; any other room once someone else has joined. The buttons follow what the server said this person may
|
|
3745
|
+
* do (`mayInviteToAISession`, the owner's role); the doors decide again.
|
|
3746
|
+
*/
|
|
3600
3747
|
renderRoster() {
|
|
3601
|
-
if (!this.sessionId
|
|
3748
|
+
if (!this.sessionId)
|
|
3749
|
+
return nothing;
|
|
3750
|
+
const group = this.visibility === 'group';
|
|
3751
|
+
if (!group && this.participants.length < 2)
|
|
3602
3752
|
return nothing;
|
|
3603
3753
|
const online = this.participants.filter(p => this._isOnline(p.lastSeenAt)).length;
|
|
3754
|
+
/* Who I am is my email here, as everywhere in this chat - the credential carries no user id. */
|
|
3755
|
+
const myEmail = this._myEmail;
|
|
3756
|
+
const iOwn = !!myEmail && this.participants.some(p => p.role === 'owner' && p.user?.email === myEmail);
|
|
3757
|
+
const inviteLabel = i18next.t('ai-assistant.button.invite', { defaultValue: 'Invite' });
|
|
3604
3758
|
return html `
|
|
3605
|
-
<div class="roster" role="group" aria-label
|
|
3759
|
+
<div class="roster" role="group" aria-label=${i18next.t('ai-assistant.label.participants', { defaultValue: 'People in this conversation' })}>
|
|
3606
3760
|
<span class="roster-count" title=${`${online} online / ${this.participants.length} total`}>
|
|
3607
3761
|
<md-icon>group</md-icon>${online}/${this.participants.length}
|
|
3608
3762
|
</span>
|
|
3609
3763
|
${this.participants.map(p => {
|
|
3610
3764
|
const name = rosterName(p, String(i18next.t('ai-assistant.text.participant-unavailable')));
|
|
3611
3765
|
const on = this._isOnline(p.lastSeenAt);
|
|
3766
|
+
const removable = iOwn && p.role === 'member' && !!p.user?.id;
|
|
3612
3767
|
return html `<span
|
|
3613
3768
|
class="roster-chip ${on ? 'online' : 'offline'}"
|
|
3614
3769
|
title=${`${name} · ${p.role}${on ? ' · online' : ''}`}>
|
|
3615
3770
|
<span class="roster-dot"></span><span class="roster-name">${name}</span>
|
|
3771
|
+
${removable
|
|
3772
|
+
? html `<button
|
|
3773
|
+
type="button"
|
|
3774
|
+
class="roster-remove"
|
|
3775
|
+
aria-label=${i18next.t('ai-assistant.button.remove-participant', { name, defaultValue: 'Send {name} out' })}
|
|
3776
|
+
title=${i18next.t('ai-assistant.button.remove-participant', { name, defaultValue: 'Send {name} out' })}
|
|
3777
|
+
@click=${() => this._removeParticipant(p.user.id, name)}><md-icon>close</md-icon></button>`
|
|
3778
|
+
: nothing}
|
|
3616
3779
|
</span>`;
|
|
3617
3780
|
})}
|
|
3781
|
+
${this.mayInvite && !this.inviting
|
|
3782
|
+
? html `<button type="button" class="roster-invite" @click=${() => (this.inviting = true)}>
|
|
3783
|
+
<md-icon>person_add</md-icon>${inviteLabel}
|
|
3784
|
+
</button>`
|
|
3785
|
+
: nothing}
|
|
3618
3786
|
</div>
|
|
3787
|
+
${this.inviting
|
|
3788
|
+
? html `<ox-assistant-invite
|
|
3789
|
+
.search=${(query) => (this.userProvider ? Promise.resolve(this.userProvider(query)) : this.fetchDomainUsers(query))}
|
|
3790
|
+
.exclude=${this.participants.map(p => p.user?.id).filter(Boolean)}
|
|
3791
|
+
@assistant-invite-pick=${(e) => this._invite(e.detail.userId, e.detail.name)}
|
|
3792
|
+
@assistant-invite-close=${() => (this.inviting = false)}></ox-assistant-invite>`
|
|
3793
|
+
: nothing}
|
|
3619
3794
|
`;
|
|
3620
3795
|
}
|
|
3796
|
+
/** Bring someone in - the door seats them and tells them (`inviteToAISession`). */
|
|
3797
|
+
async _invite(userId, name) {
|
|
3798
|
+
const sessionId = this.sessionId;
|
|
3799
|
+
if (!sessionId || !userId)
|
|
3800
|
+
return;
|
|
3801
|
+
try {
|
|
3802
|
+
const result = await client.mutate({ mutation: INVITE_MUTATION, variables: { sessionId, userIds: [userId] } });
|
|
3803
|
+
if (result.errors?.length)
|
|
3804
|
+
throw new Error(result.errors[0].message);
|
|
3805
|
+
if (this.sessionId !== sessionId)
|
|
3806
|
+
return;
|
|
3807
|
+
this.inviting = false;
|
|
3808
|
+
notify({ level: 'info', message: i18next.t('ai-assistant.text.invited', { name, defaultValue: '{name} can now read and write here, and has been told.' }) });
|
|
3809
|
+
await this._loadParticipants();
|
|
3810
|
+
}
|
|
3811
|
+
catch (error) {
|
|
3812
|
+
this.errorMessage = error instanceof Error ? error.message : String(error);
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
/** Send a member out - in a group they no longer read or write here. Bringing them back is inviting them again. */
|
|
3816
|
+
async _removeParticipant(userId, name) {
|
|
3817
|
+
const sessionId = this.sessionId;
|
|
3818
|
+
if (!sessionId)
|
|
3819
|
+
return;
|
|
3820
|
+
try {
|
|
3821
|
+
const result = await client.mutate({ mutation: REMOVE_MUTATION, variables: { sessionId, userId } });
|
|
3822
|
+
if (result.errors?.length)
|
|
3823
|
+
throw new Error(result.errors[0].message);
|
|
3824
|
+
if (this.sessionId !== sessionId)
|
|
3825
|
+
return;
|
|
3826
|
+
notify({ level: 'info', message: i18next.t('ai-assistant.text.removed-participant', { name, defaultValue: '{name} has left this conversation.' }) });
|
|
3827
|
+
await this._loadParticipants();
|
|
3828
|
+
}
|
|
3829
|
+
catch (error) {
|
|
3830
|
+
this.errorMessage = error instanceof Error ? error.message : String(error);
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
/** Tell the others I am typing - only where someone else could hear it, and not on every key (`TypingRoom`). */
|
|
3834
|
+
_sayTyping() {
|
|
3835
|
+
const sessionId = this.sessionId;
|
|
3836
|
+
if (!sessionId || this.visibility === 'private')
|
|
3837
|
+
return;
|
|
3838
|
+
if (this.visibility !== 'open' && this.participants.length < 2)
|
|
3839
|
+
return;
|
|
3840
|
+
if (!this.typingRoom.shouldSay(this.input))
|
|
3841
|
+
return;
|
|
3842
|
+
client.mutate({ mutation: SAY_TYPING_MUTATION, variables: { sessionId } }).catch(() => {
|
|
3843
|
+
/* a missed notice is a line that does not show */
|
|
3844
|
+
});
|
|
3845
|
+
}
|
|
3846
|
+
/** "Kim is typing…" - over the box, while their notices keep coming. */
|
|
3847
|
+
renderTyping() {
|
|
3848
|
+
const names = this.typingNames;
|
|
3849
|
+
if (!names.length)
|
|
3850
|
+
return nothing;
|
|
3851
|
+
const who = names.length > 2 ? i18next.t('ai-assistant.text.typing-many', { count: names.length, defaultValue: '{count} people are typing' }) : i18next.t('ai-assistant.text.typing', { names: names.join(', '), defaultValue: '{names} typing' });
|
|
3852
|
+
return html `<div class="typing-line" role="status" aria-live="polite">
|
|
3853
|
+
<span class="typing-dots"><span></span><span></span><span></span></span><span>${who}</span>
|
|
3854
|
+
</div>`;
|
|
3855
|
+
}
|
|
3621
3856
|
_onSessionTabClick(sessionId) {
|
|
3622
3857
|
if (sessionId === this.sessionId)
|
|
3623
3858
|
return;
|
|
@@ -4655,6 +4890,10 @@ __decorate([
|
|
|
4655
4890
|
property({ attribute: false }),
|
|
4656
4891
|
__metadata("design:type", Function)
|
|
4657
4892
|
], OxAssistantChat.prototype, "prepareSession", void 0);
|
|
4893
|
+
__decorate([
|
|
4894
|
+
property({ type: String }),
|
|
4895
|
+
__metadata("design:type", String)
|
|
4896
|
+
], OxAssistantChat.prototype, "visibility", void 0);
|
|
4658
4897
|
__decorate([
|
|
4659
4898
|
property({ type: Array, attribute: false }),
|
|
4660
4899
|
__metadata("design:type", Array)
|
|
@@ -4839,6 +5078,18 @@ __decorate([
|
|
|
4839
5078
|
state(),
|
|
4840
5079
|
__metadata("design:type", Object)
|
|
4841
5080
|
], OxAssistantChat.prototype, "rosterLoaded", void 0);
|
|
5081
|
+
__decorate([
|
|
5082
|
+
state(),
|
|
5083
|
+
__metadata("design:type", Boolean)
|
|
5084
|
+
], OxAssistantChat.prototype, "mayInvite", void 0);
|
|
5085
|
+
__decorate([
|
|
5086
|
+
state(),
|
|
5087
|
+
__metadata("design:type", Object)
|
|
5088
|
+
], OxAssistantChat.prototype, "inviting", void 0);
|
|
5089
|
+
__decorate([
|
|
5090
|
+
state(),
|
|
5091
|
+
__metadata("design:type", Array)
|
|
5092
|
+
], OxAssistantChat.prototype, "typingNames", void 0);
|
|
4842
5093
|
__decorate([
|
|
4843
5094
|
state(),
|
|
4844
5095
|
__metadata("design:type", Object)
|