feat: release contextual omnichannel customer platform

Add Telegram, WhatsApp and email account management, realtime messaging, contextual multilingual AI, customer intelligence, knowledge workflows, enterprise controls, a unified design system and complete repository documentation.
This commit is contained in:
wmq
2026-07-30 02:29:53 +08:00
parent 9804341463
commit af49d22ca7
66 changed files with 3181 additions and 554 deletions
+123
View File
@@ -0,0 +1,123 @@
import { expect, test } from '@playwright/test'
const account = process.env.E2E_ACCOUNT || 'e2e-1785008967@local.test'
const password = process.env.E2E_PASSWORD || 'Lingxi-E2E-2026!'
test('知识库与逐句意图在真实页面可用', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop-chromium', '仅在桌面视口执行')
const loginResponse = await page.request.post('/api/auth/login', {
data: { account, password },
})
expect(loginResponse.ok()).toBeTruthy()
const loginPayload = await loginResponse.json()
const token = loginPayload.data.token as string
const headers = { Authorization: `Bearer ${token}` }
await page.goto('/login')
await page.locator('input[autocomplete="username"]').fill(account)
await page.locator('input[autocomplete="current-password"]').fill(password)
await page.getByRole('button', { name: '登录' }).click()
await expect(page).toHaveURL(/\/dashboard/)
await page.goto('/knowledge')
await expect(page.getByText('已分段索引', { exact: true })).toBeVisible()
await expect(page.getByRole('button', { name: '上传文件' })).toBeVisible()
await page.screenshot({ path: testInfo.outputPath('knowledge-index.png'), fullPage: true })
const conversationsResponse = await page.request.get('/api/conversations', { headers })
const conversations = (await conversationsResponse.json()).data.list as Array<{ id: string }>
let conversationId = ''
let messageId = ''
let messageCount = 0
for (const conversation of conversations.slice(0, 20)) {
const messagesResponse = await page.request.get(
`/api/conversations/${encodeURIComponent(conversation.id)}/messages`,
{ headers },
)
const messages = (await messagesResponse.json()).data as Array<{
id: string
sender: string
type: string
originalText: string
intentAnalysis?: unknown
}>
const candidate = [...messages].reverse().find((message) => (
message.sender === 'customer'
&& message.type === 'text'
&& /[\p{L}]/u.test(message.originalText || '')
))
if (candidate) {
conversationId = conversation.id
messageId = candidate.id
messageCount = messages.length
break
}
}
expect(conversationId).not.toBe('')
const aiStatusResponse = await page.request.get(
`/api/conversations/${encodeURIComponent(conversationId)}/ai-status`,
{ headers },
)
expect(aiStatusResponse.ok()).toBeTruthy()
const aiStatus = (await aiStatusResponse.json()).data
expect(['not-started', 'queued', 'running', 'completed']).toContain(aiStatus.backfill.status)
if (messageCount > 80) {
await expect.poll(async () => {
const response = await page.request.get(
`/api/conversations/${encodeURIComponent(conversationId)}/ai-status`,
{ headers },
)
return (await response.json()).data.context.status
}, { timeout: 30_000 }).toBe('ready')
const readyResponse = await page.request.get(
`/api/conversations/${encodeURIComponent(conversationId)}/ai-status`,
{ headers },
)
const readyStatus = (await readyResponse.json()).data
expect(readyStatus.context.sourceMessageCount).toBeGreaterThan(0)
expect(readyStatus.context.memory.summary).toBeTruthy()
}
const analyzeResponse = await page.request.post(
`/api/conversations/${encodeURIComponent(conversationId)}/messages/${encodeURIComponent(messageId)}/analyze`,
{ headers, data: { force: true } },
)
expect(analyzeResponse.ok()).toBeTruthy()
await page.goto(`/inbox?c=${encodeURIComponent(conversationId)}`)
await expect(page.getByTestId('conversation-insight-bar')).toBeVisible()
await expect(page.getByTestId('conversation-intent-toggle')).toBeVisible()
await expect(
page.getByRole('switch', { name: '当前会话逐句意图识别' }),
).toHaveAttribute('aria-checked', 'true')
await expect.poll(() => page.getByTestId('message-intent').count()).toBeGreaterThan(0)
const intentCount = await page.getByTestId('message-intent').count()
const visibleIntent = page.getByTestId('message-intent').nth(intentCount - 1)
await visibleIntent.scrollIntoViewIfNeeded()
await expect(visibleIntent).toBeVisible()
const intentSwitch = page.getByRole('switch', { name: '当前会话逐句意图识别' })
await intentSwitch.click()
await expect(intentSwitch).toHaveAttribute('aria-checked', 'false')
await expect(page.getByTestId('message-intent')).toHaveCount(0)
await page.reload()
await expect(
page.getByRole('switch', { name: '当前会话逐句意图识别' }),
).toHaveAttribute('aria-checked', 'false')
await page.getByRole('switch', { name: '当前会话逐句意图识别' }).click()
await expect(
page.getByRole('switch', { name: '当前会话逐句意图识别' }),
).toHaveAttribute('aria-checked', 'true')
await expect.poll(() => page.getByTestId('message-intent').count()).toBeGreaterThan(0)
await expect(page.getByText('Unexpected Application Error!', { exact: true })).toHaveCount(0)
await page.screenshot({ path: testInfo.outputPath('message-intent.png'), fullPage: false })
await page.goto('/settings')
await page.getByRole('button', { name: '智能回复' }).click()
const globalIntentSwitch = page.getByRole('switch', { name: '逐条客户消息意图识别' })
await expect(globalIntentSwitch).toBeVisible()
await globalIntentSwitch.click()
await expect(globalIntentSwitch).toHaveAttribute('aria-checked', 'false')
await globalIntentSwitch.click()
await expect(globalIntentSwitch).toHaveAttribute('aria-checked', 'true')
await expect(page.getByText('invalid input syntax for type json')).toHaveCount(0)
})