feat(ai): add local expert library
This commit is contained in:
@@ -111,7 +111,7 @@ const inboxPageSource = readFileSync('src/pages/projects/project-inbox.tsx', 'ut
|
||||
if (!inboxPageSource.includes('确认创建')) failures.push('project inbox must expose the single confirmation action')
|
||||
|
||||
const aiApiSource = readFileSync('src/api/ai.ts', 'utf8')
|
||||
for (const required of ['/api/v1/projects/', '/ai-sessions', 'listAISessions', 'createAISession']) {
|
||||
for (const required of ['/api/v1/projects/', '/ai-sessions', '/api/v1/ai-experts', 'listAIExperts', 'listAISessions', 'createAISession']) {
|
||||
if (!aiApiSource.includes(required)) failures.push(`AI API must include ${required}`)
|
||||
}
|
||||
if (!aiApiSource.includes('signal?: AbortSignal')) failures.push('AI API requests must accept an AbortSignal')
|
||||
@@ -119,7 +119,7 @@ if (!aiApiSource.includes('signal,')) failures.push('AI API requests must pass t
|
||||
if (/\b(?:task|note|source)Id\b/.test(aiApiSource)) failures.push('AI session responses must not expose automatic formal object IDs')
|
||||
|
||||
const aiPageSource = readFileSync('src/pages/projects/project-ai.tsx', 'utf8')
|
||||
for (const required of ['新建会话', 'agent-chat-shell', 'agent-composer', 'IconAttachment', 'agent-send-button', 'onPressEnter', 'loading', 'error']) {
|
||||
for (const required of ['新建会话', '选择专家进行会话', 'agent-expert-picker', 'selectedExpertID', 'expertId', 'agent-chat-shell', 'agent-composer', 'IconAttachment', 'agent-send-button', 'onPressEnter', 'loading', 'error']) {
|
||||
if (!aiPageSource.includes(required)) failures.push(`project AI page must include ${required}`)
|
||||
}
|
||||
for (const required of ['AbortController', 'generationRef', 'projectRef', 'setSessions([])']) {
|
||||
|
||||
@@ -18,6 +18,20 @@ const browser = await chromium.launch({ headless: true })
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1024 }, deviceScaleFactor: 1 })
|
||||
const errors = []
|
||||
const projectId = '019b0000-0000-7000-8000-000000000001'
|
||||
const visualExperts = [
|
||||
{
|
||||
id: '019b0000-0000-7000-8000-000000000020', slug: 'product-manager', category: 'product', categoryName: '产品',
|
||||
name: '产品经理', description: '负责需求分析、路线规划和产品交付。', emoji: '🧭', color: '#165DFF',
|
||||
},
|
||||
{
|
||||
id: '019b0000-0000-7000-8000-000000000021', slug: 'ui-designer', category: 'design', categoryName: '设计',
|
||||
name: 'UI 设计师', description: '负责界面设计、组件规范和视觉一致性。', emoji: '🎨', color: '#722ED1',
|
||||
},
|
||||
{
|
||||
id: '019b0000-0000-7000-8000-000000000022', slug: 'frontend-developer', category: 'engineering', categoryName: '工程',
|
||||
name: '前端开发者', description: '负责现代 Web 应用实现与性能优化。', emoji: '💻', color: '#0FC6C2',
|
||||
},
|
||||
]
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') errors.push(message.text())
|
||||
})
|
||||
@@ -138,6 +152,7 @@ await page.route('http://localhost:9150/api/v1/**', async (route) => {
|
||||
if (url.pathname === `/api/v1/projects/${projectId}/ai-sessions`) {
|
||||
if (route.request().method() === 'POST') {
|
||||
const input = route.request().postDataJSON()
|
||||
const expert = visualExperts.find((item) => item.id === input.expertId)
|
||||
await route.fulfill({
|
||||
json: {
|
||||
id: '019b0000-0000-7000-8000-000000000010',
|
||||
@@ -145,6 +160,7 @@ await page.route('http://localhost:9150/api/v1/**', async (route) => {
|
||||
title: input.title,
|
||||
context: input.context,
|
||||
status: 'ready',
|
||||
expert,
|
||||
createdAt: '2026-07-22T09:00:00Z',
|
||||
updatedAt: '2026-07-22T09:00:00Z',
|
||||
},
|
||||
@@ -158,12 +174,17 @@ await page.route('http://localhost:9150/api/v1/**', async (route) => {
|
||||
title: '报价分析',
|
||||
context: '分析当前方案的报价结构。',
|
||||
status: 'ready',
|
||||
expert: visualExperts[1],
|
||||
createdAt: '2026-07-22T08:30:00Z',
|
||||
updatedAt: '2026-07-22T08:30:00Z',
|
||||
}],
|
||||
})
|
||||
return
|
||||
}
|
||||
if (url.pathname === '/api/v1/ai-experts') {
|
||||
await route.fulfill({ json: visualExperts })
|
||||
return
|
||||
}
|
||||
await route.fulfill({ status: 404, json: { error: { code: 'not_found', message: '视觉检查未配置该接口' } } })
|
||||
})
|
||||
|
||||
@@ -334,6 +355,8 @@ for (const channel of [
|
||||
hasOverviewHead: Boolean(pageNode?.querySelector('.overview-head')),
|
||||
hasSessionList: Boolean(pageNode?.querySelector('.agent-session-list')),
|
||||
hasComposer: Boolean(pageNode?.querySelector('.agent-composer')),
|
||||
hasExpertPicker: Boolean(pageNode?.querySelector('.agent-expert-picker')),
|
||||
expertHeading: pageNode?.querySelector('.agent-expert-heading')?.textContent?.replace(/\s+/g, ' ').trim() ?? '',
|
||||
}
|
||||
}, channel)
|
||||
|
||||
@@ -350,6 +373,8 @@ for (const channel of [
|
||||
}
|
||||
|
||||
if (channel.label === 'AI 会话') {
|
||||
await page.screenshot({ path: 'test-results/project-ai-expert-picker-light.png', fullPage: true })
|
||||
await page.getByRole('button', { name: /产品经理/ }).click()
|
||||
await page.locator('.agent-composer textarea').fill('请分析这个项目的下一步计划')
|
||||
await page.getByRole('button', { name: '发送消息' }).click()
|
||||
await page.waitForSelector('.agent-user-message')
|
||||
@@ -357,6 +382,7 @@ for (const channel of [
|
||||
userMessage: document.querySelector('.agent-user-message')?.textContent?.trim() ?? '',
|
||||
activeSession: document.querySelector('.agent-session.active')?.textContent?.trim() ?? '',
|
||||
composerValue: document.querySelector('.agent-composer textarea')?.value ?? '',
|
||||
expertName: document.querySelector('.agent-assistant-name')?.textContent?.trim() ?? '',
|
||||
}))
|
||||
await page.screenshot({ path: 'test-results/project-ai-session-light.png', fullPage: true })
|
||||
}
|
||||
@@ -552,7 +578,10 @@ for (const check of channelPageChecks) {
|
||||
if (check.label === '新建频道' && (check.inputCount < 4 || !check.hasSaveChannel)) {
|
||||
failures.push(`expected legacy new channel editor, got inputs=${check.inputCount}, save=${check.hasSaveChannel}`)
|
||||
}
|
||||
if (check.label === 'AI 会话' && (check.hasOverviewHead || !check.hasSessionList || !check.hasComposer)) {
|
||||
if (check.label === 'AI 会话' && (
|
||||
check.hasOverviewHead || !check.hasSessionList || !check.hasComposer || !check.hasExpertPicker ||
|
||||
!check.expertHeading.includes('选择专家进行会话')
|
||||
)) {
|
||||
failures.push(`expected headerless AI session chat layout, got ${JSON.stringify(check)}`)
|
||||
}
|
||||
}
|
||||
@@ -570,7 +599,8 @@ if (
|
||||
!aiSessionCheck ||
|
||||
aiSessionCheck.userMessage !== '请分析这个项目的下一步计划' ||
|
||||
!aiSessionCheck.activeSession.includes('请分析这个项目的下一步计划') ||
|
||||
aiSessionCheck.composerValue !== ''
|
||||
aiSessionCheck.composerValue !== '' ||
|
||||
aiSessionCheck.expertName !== '产品经理'
|
||||
) {
|
||||
failures.push(`expected AI composer to create and select a session, got ${JSON.stringify(aiSessionCheck)}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user