@things-factory/ai-assistant 10.1.125 → 10.1.129
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 +3 -3
- 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 +10 -2
- package/translations/ja.json +1 -1
- package/translations/ko.json +10 -2
- package/translations/ms.json +1 -1
- package/translations/zh.json +1 -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.129",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"things-factory": true,
|
|
6
6
|
"author": "heartyoh",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@operato/popup": "^10.16.0",
|
|
34
34
|
"@operato/shell": "^10.0.0",
|
|
35
35
|
"@operato/styles": "^10.0.0",
|
|
36
|
-
"@things-factory/ai-client-base": "^10.1.
|
|
36
|
+
"@things-factory/ai-client-base": "^10.1.129",
|
|
37
37
|
"@things-factory/apptool-base": "^10.1.123",
|
|
38
38
|
"@things-factory/attachment-base": "^10.1.123",
|
|
39
39
|
"@things-factory/auth-base": "^10.1.123",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"copyfiles": "^2.4.1",
|
|
53
53
|
"rimraf": "^5.0.0"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "4d1ef242d9b7e6669a34c27b6d31aed778c07a90"
|
|
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
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"ai-assistant.notice.stopped": "the request stopped partway, so only part of it was done.",
|
|
92
92
|
"ai-assistant.notice.provider-busy": "the ai service did not answer just now. try again in a moment.",
|
|
93
93
|
"ai-assistant.notice.provider-error": "something went wrong while handling this. tell an administrator if it keeps happening.",
|
|
94
|
-
"ai-assistant.notice.too-long": "this
|
|
94
|
+
"ai-assistant.notice.too-long": "this stopped after {iter} lookups without an answer.",
|
|
95
95
|
"ai-assistant.notice.tool-kept-failing": "{tool} failed {count} times in a row, so it stopped. try saying it differently.",
|
|
96
96
|
"ai-assistant.text.no-match": "no match",
|
|
97
97
|
"ai-assistant.text.messages-unreadable": "could not read this conversation.",
|
|
@@ -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/ja.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"ai-assistant.notice.stopped": "リクエストが途中で止まり、一部だけ処理しました。",
|
|
92
92
|
"ai-assistant.notice.provider-busy": "AI サービスが今応答していません。少し待ってからもう一度お試しください。",
|
|
93
93
|
"ai-assistant.notice.provider-error": "処理中に問題が発生しました。同じことが続く場合は管理者にお知らせください。",
|
|
94
|
-
"ai-assistant.notice.too-long": "
|
|
94
|
+
"ai-assistant.notice.too-long": "{iter} 回の照会で答えを出せずに止まりました。",
|
|
95
95
|
"ai-assistant.notice.tool-kept-failing": "{tool} の呼び出しが {count} 回続けて失敗したため止まりました。言い方を変えてみてください。",
|
|
96
96
|
"ai-assistant.text.no-match": "一致するものがありません",
|
|
97
97
|
"ai-assistant.text.messages-unreadable": "この会話を読み取れませんでした。",
|
package/translations/ko.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"ai-assistant.notice.stopped": "요청이 중간에 멈춰서 일부만 처리했습니다.",
|
|
92
92
|
"ai-assistant.notice.provider-busy": "AI 서비스가 지금 응답하지 않습니다. 잠시 뒤에 다시 해 보세요.",
|
|
93
93
|
"ai-assistant.notice.provider-error": "처리 중에 문제가 생겼습니다. 같은 문제가 계속되면 관리자에게 알려 주세요.",
|
|
94
|
-
"ai-assistant.notice.too-long": "
|
|
94
|
+
"ai-assistant.notice.too-long": "조회 {iter}번 안에 답을 만들지 못하고 멈췄습니다.",
|
|
95
95
|
"ai-assistant.notice.tool-kept-failing": "{tool} 호출이 {count}번 잇따라 실패해서 멈췄습니다. 다르게 말해 보세요.",
|
|
96
96
|
"ai-assistant.text.no-match": "맞는 것이 없습니다",
|
|
97
97
|
"ai-assistant.text.messages-unreadable": "이 대화를 읽지 못했습니다.",
|
|
@@ -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
|
}
|
package/translations/ms.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"ai-assistant.notice.stopped": "permintaan terhenti separuh jalan, jadi sebahagian sahaja selesai.",
|
|
92
92
|
"ai-assistant.notice.provider-busy": "perkhidmatan ai tidak menjawab sekarang. cuba lagi sebentar lagi.",
|
|
93
93
|
"ai-assistant.notice.provider-error": "ada masalah semasa mengendalikan ini. beritahu pentadbir jika ia berulang.",
|
|
94
|
-
"ai-assistant.notice.too-long": "ini
|
|
94
|
+
"ai-assistant.notice.too-long": "ini berhenti selepas {iter} carian tanpa jawapan.",
|
|
95
95
|
"ai-assistant.notice.tool-kept-failing": "{tool} gagal {count} kali berturut-turut, jadi ia berhenti. cuba nyatakan dengan cara lain.",
|
|
96
96
|
"ai-assistant.text.no-match": "tiada padanan",
|
|
97
97
|
"ai-assistant.text.messages-unreadable": "tidak dapat membaca perbualan ini.",
|
package/translations/zh.json
CHANGED
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"ai-assistant.notice.stopped": "请求中途停止,只处理了一部分。",
|
|
92
92
|
"ai-assistant.notice.provider-busy": "AI 服务暂时没有回应,请稍后再试。",
|
|
93
93
|
"ai-assistant.notice.provider-error": "处理时出了问题。如果一直这样,请告知管理员。",
|
|
94
|
-
"ai-assistant.notice.too-long": "
|
|
94
|
+
"ai-assistant.notice.too-long": "查询 {iter} 次后仍未得出答案,已停止。",
|
|
95
95
|
"ai-assistant.notice.tool-kept-failing": "{tool} 连续失败 {count} 次,已停止。请换一种说法。",
|
|
96
96
|
"ai-assistant.text.no-match": "没有匹配项",
|
|
97
97
|
"ai-assistant.text.messages-unreadable": "无法读取这个对话。",
|