@things-factory/ai-assistant 10.1.125 → 10.1.127
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 +122 -68
- package/client/components/chat-echo-dedup.ts +2 -1
- package/client/components/reply-message.test.ts +15 -0
- package/client/components/reply-message.ts +11 -0
- package/client/components/roster-identity.test.ts +18 -0
- package/client/components/roster-identity.ts +10 -0
- package/dist-client/components/assistant-chat.d.ts +7 -0
- package/dist-client/components/assistant-chat.js +127 -68
- package/dist-client/components/assistant-chat.js.map +1 -1
- package/dist-client/components/chat-echo-dedup.d.ts +1 -0
- package/dist-client/components/chat-echo-dedup.js +1 -0
- package/dist-client/components/chat-echo-dedup.js.map +1 -1
- package/dist-client/components/reply-message.d.ts +5 -0
- package/dist-client/components/reply-message.js +14 -0
- package/dist-client/components/reply-message.js.map +1 -0
- package/dist-client/components/reply-message.test.d.ts +1 -0
- package/dist-client/components/reply-message.test.js +16 -0
- package/dist-client/components/reply-message.test.js.map +1 -0
- package/dist-client/components/roster-identity.d.ts +11 -0
- package/dist-client/components/roster-identity.js +11 -0
- package/dist-client/components/roster-identity.js.map +1 -0
- package/dist-client/components/roster-identity.test.d.ts +1 -0
- package/dist-client/components/roster-identity.test.js +20 -0
- package/dist-client/components/roster-identity.test.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/chat-session-participant/read-roster.d.ts +9 -0
- package/dist-server/service/chat-session-participant/read-roster.js +15 -0
- package/dist-server/service/chat-session-participant/read-roster.js.map +1 -0
- package/dist-server/service/chat-session-resolver.d.ts +1 -1
- package/dist-server/service/chat-session-resolver.js +15 -11
- package/dist-server/service/chat-session-resolver.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/service/chat-session-participant/read-roster.test.ts +18 -0
- package/server/service/chat-session-participant/read-roster.ts +14 -0
- package/server/service/chat-session-resolver.ts +11 -8
- package/translations/en.json +9 -1
- package/translations/ko.json +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/ai-assistant",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.127",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"things-factory": true,
|
|
6
6
|
"author": "heartyoh",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"copyfiles": "^2.4.1",
|
|
53
53
|
"rimraf": "^5.0.0"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "1a224d9f1cbf56697a739e505b97333dd090d473"
|
|
56
56
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readRoster } from './read-roster'
|
|
2
|
+
|
|
3
|
+
describe('participant roster identities', () => {
|
|
4
|
+
it('loads the participating user relation for public conversations', async () => {
|
|
5
|
+
const find = jest.fn(async options => options.relations.includes('user') ? [{id:'p',user:{id:'u',name:'김희태'}}] : [{id:'p'}])
|
|
6
|
+
const isParticipant=jest.fn(async()=>false)
|
|
7
|
+
const rows=await readRoster({id:'room',domainId:'domain',visibility:'open'},{find,isParticipant})
|
|
8
|
+
expect(rows).toEqual([{id:'p',user:{id:'u',name:'김희태'}}])
|
|
9
|
+
expect(find).toHaveBeenCalledWith(expect.objectContaining({where:{session:{id:'room'},domain:{id:'domain'}},relations:['user']}))
|
|
10
|
+
expect(isParticipant).not.toHaveBeenCalled()
|
|
11
|
+
})
|
|
12
|
+
it('does not expose identities from a private room to an outsider', async () => {
|
|
13
|
+
const find=jest.fn(async()=>[{id:'p',user:{name:'Private'}}])
|
|
14
|
+
expect(await readRoster({id:'r',domainId:'d',visibility:'private'},{find,isParticipant:async()=>false})).toEqual([])
|
|
15
|
+
expect(find).not.toHaveBeenCalled()
|
|
16
|
+
expect(await readRoster({id:'r',domainId:'d',visibility:'private'},{find,isParticipant:async()=>true})).toEqual([{id:'p',user:{name:'Private'}}])
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { mayReadSession } from '../chat-session/session-visibility.js'
|
|
2
|
+
|
|
3
|
+
/** Read the room roster with user identities hydrated, after applying the room's visibility gate. */
|
|
4
|
+
export async function readRoster<T>(
|
|
5
|
+
room: { id: string; domainId: string; visibility?: string },
|
|
6
|
+
port: { isParticipant(): Promise<boolean>; find(options: any): Promise<T[]> }
|
|
7
|
+
): Promise<T[]> {
|
|
8
|
+
if (!mayReadSession(room.visibility, room.visibility === 'private' ? await port.isParticipant() : false)) return []
|
|
9
|
+
return port.find({
|
|
10
|
+
where: { session: { id: room.id }, domain: { id: room.domainId } },
|
|
11
|
+
relations: ['user'],
|
|
12
|
+
order: { createdAt: 'ASC' }
|
|
13
|
+
})
|
|
14
|
+
}
|
|
@@ -21,6 +21,7 @@ import { readerGate } from './chat-message/reader-gate.js'
|
|
|
21
21
|
import { initialVisibility, mayReadSession, SESSION_VISIBILITIES } from './chat-session/session-visibility.js'
|
|
22
22
|
import { inviteMentionedUsers } from './chat-session-participant/mention-invite.js'
|
|
23
23
|
import { buildInviteNotification } from './chat-session-participant/invite-notification.js'
|
|
24
|
+
import { readRoster } from './chat-session-participant/read-roster.js'
|
|
24
25
|
import { isSessionParticipant } from './chat-session-participant/participant-lookup.js'
|
|
25
26
|
import { assertMayWriteSession } from './chat-session/session-write-gate.js'
|
|
26
27
|
|
|
@@ -570,15 +571,15 @@ export class ChatSessionResolver {
|
|
|
570
571
|
@Arg('sessionId') sessionId: string,
|
|
571
572
|
@Ctx() context: ResolverContext
|
|
572
573
|
): Promise<ChatSessionParticipant[]> {
|
|
573
|
-
const { domain } = context.state
|
|
574
|
+
const { domain, user } = context.state
|
|
574
575
|
const session = await getRepository(ChatSession).findOneBy({
|
|
575
576
|
id: sessionId,
|
|
576
577
|
domain: { id: domain.id }
|
|
577
578
|
})
|
|
578
579
|
if (!session) return []
|
|
579
|
-
return
|
|
580
|
-
|
|
581
|
-
|
|
580
|
+
return readRoster({id:sessionId,domainId:domain.id,visibility:(session as any).visibility}, {
|
|
581
|
+
isParticipant: () => isSessionParticipant(domain.id,sessionId,user?.id),
|
|
582
|
+
find: options => getRepository(ChatSessionParticipant).find(options)
|
|
582
583
|
})
|
|
583
584
|
}
|
|
584
585
|
|
|
@@ -872,6 +873,7 @@ export class ChatSessionResolver {
|
|
|
872
873
|
async sayToRoom(
|
|
873
874
|
@Arg('sessionId') sessionId: string,
|
|
874
875
|
@Arg('content') content: string,
|
|
876
|
+
@Arg('replyToId', { nullable: true }) replyToId: string | undefined,
|
|
875
877
|
@Ctx() context: ResolverContext
|
|
876
878
|
): Promise<string | null> {
|
|
877
879
|
const { domain, user, tx } = context.state
|
|
@@ -882,10 +884,11 @@ export class ChatSessionResolver {
|
|
|
882
884
|
const body = roomMessageBody(content)
|
|
883
885
|
if (!body) return null
|
|
884
886
|
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
887
|
+
const messages = getRepository(ChatMessage, tx)
|
|
888
|
+
const previousLast = replyToId
|
|
889
|
+
? await messages.findOneBy({ id: replyToId, session: { id: session.id } as any })
|
|
890
|
+
: await messages.findOne({ where: { session: { id: session.id } as any }, order: { createdAt: 'DESC' } })
|
|
891
|
+
if (replyToId && !previousLast) throw new Error('The message to reply to is not in this conversation')
|
|
889
892
|
const saved = await getRepository(ChatMessage, tx).save({
|
|
890
893
|
session: { id: session.id } as any,
|
|
891
894
|
role: 'user',
|
package/translations/en.json
CHANGED
|
@@ -309,5 +309,13 @@
|
|
|
309
309
|
"ai-assistant.text.send-shortcut": "{key} to send",
|
|
310
310
|
"ai-assistant.text.send-tooltip-wide": "Send ({key})",
|
|
311
311
|
"ai-assistant.text.table-shape": "{rows} rows × {columns} columns",
|
|
312
|
-
"ai-assistant.button.table-to-words": "Put in as words"
|
|
312
|
+
"ai-assistant.button.table-to-words": "Put in as words",
|
|
313
|
+
"ai-assistant.text.invite-checking": "Checking participant identities before showing invitation eligibility.",
|
|
314
|
+
"ai-assistant.text.participant-unavailable": "User unavailable",
|
|
315
|
+
"ai-assistant.text.send-to-people": "To people",
|
|
316
|
+
"ai-assistant.text.send-to-ai": "To AI",
|
|
317
|
+
"ai-assistant.text.room-attachments-unavailable": "Attachments to participants are not supported yet. Remove the attachments before sending.",
|
|
318
|
+
"ai-assistant.button.reply": "Reply",
|
|
319
|
+
"ai-assistant.button.cancel-reply": "Cancel reply",
|
|
320
|
+
"ai-assistant.text.reply-to": "Reply to {name}"
|
|
313
321
|
}
|
package/translations/ko.json
CHANGED
|
@@ -309,5 +309,13 @@
|
|
|
309
309
|
"ai-assistant.text.send-shortcut": "{key} 보내기",
|
|
310
310
|
"ai-assistant.text.send-tooltip-wide": "전송 ({key})",
|
|
311
311
|
"ai-assistant.text.table-shape": "{rows}행 × {columns}열",
|
|
312
|
-
"ai-assistant.button.table-to-words": "글로 넣기"
|
|
312
|
+
"ai-assistant.button.table-to-words": "글로 넣기",
|
|
313
|
+
"ai-assistant.text.invite-checking": "참여자 정보를 확인 중입니다. 확인되면 초대 가능 여부를 표시합니다.",
|
|
314
|
+
"ai-assistant.text.participant-unavailable": "사용자 정보 없음",
|
|
315
|
+
"ai-assistant.text.send-to-people": "참여자에게",
|
|
316
|
+
"ai-assistant.text.send-to-ai": "AI에게",
|
|
317
|
+
"ai-assistant.text.room-attachments-unavailable": "참여자에게 첨부파일을 보내는 기능은 아직 지원하지 않습니다. 첨부파일을 제거한 뒤 보내주세요.",
|
|
318
|
+
"ai-assistant.button.reply": "답장",
|
|
319
|
+
"ai-assistant.button.cancel-reply": "답장 취소",
|
|
320
|
+
"ai-assistant.text.reply-to": "{name}에게 답장"
|
|
313
321
|
}
|