@open-mercato/ai-assistant 0.6.3-develop.3901.1.ddad60693a → 0.6.3
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/ai_assistant/__integration__/TC-AI-sharing-06-deep-link.spec.js +87 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-sharing-06-deep-link.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-sharing-07-owner-label.spec.js +119 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-sharing-07-owner-label.spec.js.map +7 -0
- package/dist/modules/ai_assistant/acl.js +1 -0
- package/dist/modules/ai_assistant/acl.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +3 -0
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/participants/[userId]/route.js +128 -0
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/participants/[userId]/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/participants/route.js +271 -0
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/participants/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/route.js +9 -1
- package/dist/modules/ai_assistant/api/ai/conversations/[conversationId]/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/conversations/route.js +4 -1
- package/dist/modules/ai_assistant/api/ai/conversations/route.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +5 -1
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/components/ConversationShareButton.js +5 -0
- package/dist/modules/ai_assistant/components/ConversationShareButton.js.map +7 -0
- package/dist/modules/ai_assistant/components/ConversationShareDialog.js +5 -0
- package/dist/modules/ai_assistant/components/ConversationShareDialog.js.map +7 -0
- package/dist/modules/ai_assistant/data/entities.js +3 -0
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiChatConversationRepository.js +235 -5
- package/dist/modules/ai_assistant/data/repositories/AiChatConversationRepository.js.map +2 -2
- package/dist/modules/ai_assistant/events.js +14 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +17 -0
- package/dist/modules/ai_assistant/i18n/en.json +17 -0
- package/dist/modules/ai_assistant/i18n/es.json +17 -0
- package/dist/modules/ai_assistant/i18n/pl.json +17 -0
- package/dist/modules/ai_assistant/lib/conversation-storage.js +12 -3
- package/dist/modules/ai_assistant/lib/conversation-storage.js.map +2 -2
- package/dist/modules/ai_assistant/migrations/Migration20260522120000_ai_assistant.js +15 -0
- package/dist/modules/ai_assistant/migrations/Migration20260522120000_ai_assistant.js.map +7 -0
- package/dist/modules/ai_assistant/notifications.client.js +30 -0
- package/dist/modules/ai_assistant/notifications.client.js.map +7 -0
- package/dist/modules/ai_assistant/notifications.js +27 -0
- package/dist/modules/ai_assistant/notifications.js.map +7 -0
- package/dist/modules/ai_assistant/setup.js +2 -1
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/subscribers/conversation-shared-notify.js +59 -0
- package/dist/modules/ai_assistant/subscribers/conversation-shared-notify.js.map +7 -0
- package/dist/modules/ai_assistant/widgets/notifications/ConversationSharedRenderer.js +123 -0
- package/dist/modules/ai_assistant/widgets/notifications/ConversationSharedRenderer.js.map +7 -0
- package/generated/entities/ai_chat_conversation_participant/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +7 -8
- package/src/modules/ai_assistant/__integration__/TC-AI-sharing-06-deep-link.spec.ts +117 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-sharing-07-owner-label.spec.ts +159 -0
- package/src/modules/ai_assistant/__tests__/integration/ai-chat-sharing.test.ts +406 -0
- package/src/modules/ai_assistant/acl.ts +1 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +3 -0
- package/src/modules/ai_assistant/api/ai/conversations/[conversationId]/participants/[userId]/route.ts +149 -0
- package/src/modules/ai_assistant/api/ai/conversations/[conversationId]/participants/route.ts +314 -0
- package/src/modules/ai_assistant/api/ai/conversations/[conversationId]/route.ts +9 -1
- package/src/modules/ai_assistant/api/ai/conversations/route.ts +4 -1
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +4 -0
- package/src/modules/ai_assistant/components/ConversationShareButton.tsx +1 -0
- package/src/modules/ai_assistant/components/ConversationShareDialog.tsx +1 -0
- package/src/modules/ai_assistant/data/entities.ts +4 -0
- package/src/modules/ai_assistant/data/repositories/AiChatConversationRepository.ts +270 -7
- package/src/modules/ai_assistant/data/repositories/__tests__/AiChatConversationRepository.test.ts +297 -3
- package/src/modules/ai_assistant/events.ts +31 -0
- package/src/modules/ai_assistant/i18n/__tests__/conversation-share-translations.test.ts +59 -0
- package/src/modules/ai_assistant/i18n/de.json +17 -0
- package/src/modules/ai_assistant/i18n/en.json +17 -0
- package/src/modules/ai_assistant/i18n/es.json +17 -0
- package/src/modules/ai_assistant/i18n/pl.json +17 -0
- package/src/modules/ai_assistant/lib/conversation-storage.ts +22 -1
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +25 -0
- package/src/modules/ai_assistant/migrations/Migration20260522120000_ai_assistant.ts +15 -0
- package/src/modules/ai_assistant/notifications.client.ts +29 -0
- package/src/modules/ai_assistant/notifications.ts +25 -0
- package/src/modules/ai_assistant/setup.ts +2 -1
- package/src/modules/ai_assistant/subscribers/__tests__/conversation-shared-notify.test.ts +116 -0
- package/src/modules/ai_assistant/subscribers/conversation-shared-notify.ts +78 -0
- package/src/modules/ai_assistant/widgets/notifications/ConversationSharedRenderer.tsx +121 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
|
|
2
|
+
import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'
|
|
3
|
+
import { defaultLocale } from '@open-mercato/shared/lib/i18n/config'
|
|
4
|
+
import { loadDictionary } from '@open-mercato/shared/lib/i18n/server'
|
|
5
|
+
import { createTranslator } from '@open-mercato/shared/lib/i18n/translate'
|
|
6
|
+
import { notificationTypes } from '../notifications'
|
|
7
|
+
import type { AiConversationSharedPayload } from '../events'
|
|
8
|
+
|
|
9
|
+
export const metadata = {
|
|
10
|
+
event: 'ai_assistant.conversation.shared',
|
|
11
|
+
persistent: true,
|
|
12
|
+
id: 'ai_assistant:conversation-shared-notify',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type ResolverContext = {
|
|
16
|
+
resolve: <T = unknown>(name: string) => T
|
|
17
|
+
container?: { resolve<T = unknown>(name: string): T }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function resolveDefaultLocaleStrings(
|
|
21
|
+
titleKey: string | undefined,
|
|
22
|
+
bodyKey: string | undefined,
|
|
23
|
+
): Promise<{ title: string | undefined; body: string | undefined }> {
|
|
24
|
+
if (!titleKey && !bodyKey) return { title: titleKey, body: bodyKey }
|
|
25
|
+
try {
|
|
26
|
+
const dict = await loadDictionary(defaultLocale)
|
|
27
|
+
const t = createTranslator(dict)
|
|
28
|
+
return {
|
|
29
|
+
title: titleKey ? t(titleKey) : titleKey,
|
|
30
|
+
body: bodyKey ? t(bodyKey) : bodyKey,
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
return { title: titleKey, body: bodyKey }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default async function handleConversationShared(
|
|
38
|
+
payload: AiConversationSharedPayload,
|
|
39
|
+
ctx: ResolverContext,
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
if (!payload?.participantUserId || !payload.tenantId) return
|
|
42
|
+
|
|
43
|
+
const typeDef = notificationTypes.find((t) => t.type === 'ai_assistant.conversation_shared')
|
|
44
|
+
if (!typeDef) return
|
|
45
|
+
|
|
46
|
+
const container = ctx.container ?? { resolve: ctx.resolve }
|
|
47
|
+
let notificationService: ReturnType<typeof resolveNotificationService> | null
|
|
48
|
+
try {
|
|
49
|
+
notificationService = resolveNotificationService(container)
|
|
50
|
+
} catch {
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const notificationInput = buildNotificationFromType(typeDef, {
|
|
55
|
+
recipientUserId: payload.participantUserId,
|
|
56
|
+
bodyVariables: {},
|
|
57
|
+
sourceEntityType: 'ai_assistant:ai_chat_conversation',
|
|
58
|
+
sourceEntityId: payload.conversationId,
|
|
59
|
+
linkHref: `/backend?openAiConversation=${payload.conversationId}`,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// Persist a human-readable fallback alongside the i18n keys so consumers
|
|
63
|
+
// that do not run the client renderer (email digests, exports) display a
|
|
64
|
+
// resolved string instead of the raw key. The client renderer continues
|
|
65
|
+
// to re-translate via titleKey/bodyKey for the viewer's locale.
|
|
66
|
+
const resolved = await resolveDefaultLocaleStrings(typeDef.titleKey, typeDef.bodyKey)
|
|
67
|
+
if (resolved.title !== undefined) notificationInput.title = resolved.title
|
|
68
|
+
if (resolved.body !== undefined) notificationInput.body = resolved.body
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await notificationService.create(notificationInput, {
|
|
72
|
+
tenantId: payload.tenantId,
|
|
73
|
+
organizationId: payload.organizationId ?? null,
|
|
74
|
+
})
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn('[ai_assistant.conversationSharedNotify] create failed', err)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Share2, ExternalLink, X } from 'lucide-react'
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
7
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
8
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
9
|
+
import { formatRelativeTime } from '@open-mercato/shared/lib/time'
|
|
10
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
11
|
+
import type { NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'
|
|
12
|
+
|
|
13
|
+
export function ConversationSharedRenderer({
|
|
14
|
+
notification,
|
|
15
|
+
onAction,
|
|
16
|
+
onDismiss,
|
|
17
|
+
actions = [],
|
|
18
|
+
}: NotificationRendererProps) {
|
|
19
|
+
const t = useT()
|
|
20
|
+
const router = useRouter()
|
|
21
|
+
const [executing, setExecuting] = React.useState(false)
|
|
22
|
+
const isUnread = notification.status === 'unread'
|
|
23
|
+
const viewAction = actions.find((a) => a.id === 'view') ?? actions[0] ?? null
|
|
24
|
+
|
|
25
|
+
const handleView = async () => {
|
|
26
|
+
setExecuting(true)
|
|
27
|
+
try {
|
|
28
|
+
if (viewAction) {
|
|
29
|
+
await onAction(viewAction.id)
|
|
30
|
+
}
|
|
31
|
+
if (notification.linkHref) router.push(notification.linkHref)
|
|
32
|
+
} finally {
|
|
33
|
+
setExecuting(false)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const timeAgo = formatRelativeTime(notification.createdAt, { translate: t }) ?? ''
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
className={cn(
|
|
42
|
+
'group relative flex gap-4 items-start rounded-xl p-3 transition-colors hover:bg-muted/40 cursor-pointer',
|
|
43
|
+
isUnread && 'bg-muted/20',
|
|
44
|
+
)}
|
|
45
|
+
onClick={executing ? undefined : handleView}
|
|
46
|
+
onKeyDown={(e) => {
|
|
47
|
+
if (executing) return
|
|
48
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
handleView()
|
|
51
|
+
}
|
|
52
|
+
}}
|
|
53
|
+
role="button"
|
|
54
|
+
tabIndex={0}
|
|
55
|
+
>
|
|
56
|
+
<div className="relative shrink-0 flex size-10 items-center justify-center rounded-full bg-status-info-bg">
|
|
57
|
+
<Share2 className="size-5 text-status-info-icon" aria-hidden="true" />
|
|
58
|
+
{isUnread ? (
|
|
59
|
+
<span
|
|
60
|
+
className="absolute -right-1 -top-1 size-3 rounded-full bg-accent-indigo ring-2 ring-background"
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
/>
|
|
63
|
+
) : null}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
67
|
+
<p className="truncate text-sm font-medium leading-5 tracking-tight text-foreground">
|
|
68
|
+
{notification.title}
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<div className="text-xs leading-4 text-muted-foreground">
|
|
72
|
+
{timeAgo ? <span className="whitespace-nowrap">{timeAgo}</span> : null}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="mt-2 flex items-center gap-2">
|
|
76
|
+
<Button
|
|
77
|
+
type="button"
|
|
78
|
+
size="sm"
|
|
79
|
+
className="h-8 rounded-md px-2.5 bg-accent-indigo text-accent-indigo-foreground hover:bg-accent-indigo/90"
|
|
80
|
+
onClick={(e) => {
|
|
81
|
+
e.stopPropagation()
|
|
82
|
+
handleView()
|
|
83
|
+
}}
|
|
84
|
+
disabled={executing || (!viewAction && !notification.linkHref)}
|
|
85
|
+
>
|
|
86
|
+
<ExternalLink className="size-3.5" aria-hidden="true" />
|
|
87
|
+
{t('ai_assistant.notifications.conversation_shared.view_button', 'View Conversation')}
|
|
88
|
+
</Button>
|
|
89
|
+
<Button
|
|
90
|
+
type="button"
|
|
91
|
+
variant="outline"
|
|
92
|
+
size="sm"
|
|
93
|
+
className="h-8 rounded-md px-2.5"
|
|
94
|
+
onClick={(e) => {
|
|
95
|
+
e.stopPropagation()
|
|
96
|
+
onDismiss()
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
{t('notifications.actions.dismiss', 'Dismiss')}
|
|
100
|
+
</Button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<IconButton
|
|
105
|
+
type="button"
|
|
106
|
+
variant="ghost"
|
|
107
|
+
size="xs"
|
|
108
|
+
className="absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100"
|
|
109
|
+
onClick={(e) => {
|
|
110
|
+
e.stopPropagation()
|
|
111
|
+
onDismiss()
|
|
112
|
+
}}
|
|
113
|
+
aria-label={t('notifications.actions.dismiss', 'Dismiss')}
|
|
114
|
+
>
|
|
115
|
+
<X className="size-3.5" />
|
|
116
|
+
</IconButton>
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default ConversationSharedRenderer
|