feat: improve Banban UI and binding diagnostics
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
`Muyao-Headers.ttf` is a subset of `Muyao-Softbrush.ttf` for the login and binding headings: 伴伴、补全绑定、添加设备卡片、扫码贴卡绑定设备. Glyph outlines and advances are unchanged. Only the subset is bundled so the mini-program stays below the main-package size limit.
|
||||
|
||||
When changing those headings, regenerate the subset with FontTools:
|
||||
|
||||
```sh
|
||||
pyftsubset Muyao-Softbrush.ttf --text='伴伴补全绑定添加设备卡片扫码贴卡绑定设备' --output-file=Muyao-Headers.ttf --layout-features='*' --name-IDs='*' --name-legacy --name-languages='*'
|
||||
```
|
||||
@@ -5,6 +5,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
padding: 14px 20px calc(12px + env(safe-area-inset-bottom));
|
||||
background: #fffdf7;
|
||||
border: 2px solid #df9e55;
|
||||
@@ -15,11 +16,20 @@
|
||||
.companion-tab {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #d6b18a;
|
||||
}
|
||||
.companion-tab.active { color: #ad681d; }
|
||||
.companion-tab-icon { width: 62px; height: 52px; }
|
||||
.companion-tab-label { margin-top: 4px; font-size: 24px; font-weight: 600; }
|
||||
.companion-tab-icon { display: block; flex-shrink: 0; width: 62px; height: 52px; }
|
||||
.companion-tab-label { display: block; margin-top: 4px; font-size: 24px; font-weight: 600; line-height: 1.4; white-space: nowrap; }
|
||||
|
||||
.companion-tab-bar::-webkit-scrollbar,
|
||||
.companion-tab::-webkit-scrollbar,
|
||||
.companion-tab-label::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@@ -511,7 +511,10 @@ export default function Bind() {
|
||||
scanType: ['qrCode', 'barCode'],
|
||||
success: (res) => {
|
||||
setIsScanning(false)
|
||||
console.info('[bind] scanCode success response:', res)
|
||||
console.info('[bind] scanCode raw result:', res.result || '')
|
||||
const parsed = parseBindingPayload(res.result || '')
|
||||
console.info('[bind] scanCode parsed payload:', parsed)
|
||||
resetBindSessionState()
|
||||
if (parsed.deviceId) setDeviceId(parsed.deviceId)
|
||||
if (parsed.serialNumber) setSerialNumber(parsed.serialNumber)
|
||||
@@ -523,6 +526,7 @@ export default function Bind() {
|
||||
},
|
||||
fail: (err) => {
|
||||
setIsScanning(false)
|
||||
console.warn('[bind] scanCode failed response:', err)
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) return
|
||||
Taro.showToast({
|
||||
title: '扫码失败,请重试',
|
||||
|
||||
@@ -32,13 +32,15 @@
|
||||
|
||||
.character-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.character-avatar {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: linear-gradient(135deg, #FF8C42 0%, #FFB347 100%);
|
||||
flex-shrink: 0;
|
||||
background: #fff1d4;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -48,10 +50,16 @@
|
||||
}
|
||||
|
||||
.character-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.character-name {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
@@ -61,6 +69,7 @@
|
||||
.character-desc {
|
||||
font-size: 24px;
|
||||
color: #999999;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,8 +133,8 @@
|
||||
.user-avatar {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
background: #FFE4C4;
|
||||
border-radius: 50%;
|
||||
background: #fff1d4;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -139,7 +148,7 @@
|
||||
.peer-avatar {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
background: #FEF3E8;
|
||||
background: #fff1d4;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -253,6 +262,6 @@
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.chat-avatar-art { width: 100%; height: 100%; }
|
||||
.chat-avatar-art { display: block; width: 100%; height: 100%; }
|
||||
.user-avatar { margin-left: 14px; }
|
||||
.message-item.user .message-bubble { margin: 0; }
|
||||
|
||||
@@ -768,6 +768,8 @@ export default function ChatDetail() {
|
||||
<ScrollView
|
||||
className='chat-list'
|
||||
scrollY
|
||||
enhanced
|
||||
showScrollbar={false}
|
||||
scrollWithAnimation={scrollWithAnimation}
|
||||
scrollIntoView={scrollTarget}
|
||||
>
|
||||
|
||||
@@ -6,20 +6,8 @@
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 80px 32px 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 44px;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-list {
|
||||
height: calc(100vh - 200px);
|
||||
height: calc(100vh - var(--bb-navigation-bar-top) - var(--bb-navigation-bar-height) - 144px - env(safe-area-inset-bottom));
|
||||
padding: 0 24px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
@@ -113,7 +101,7 @@
|
||||
height: 52px;
|
||||
padding: 0 18px;
|
||||
border-radius: 999px;
|
||||
background: #FFF2E7;
|
||||
background: #fff1d4;
|
||||
|
||||
&:active {
|
||||
opacity: 0.72;
|
||||
@@ -159,7 +147,7 @@
|
||||
}
|
||||
|
||||
&.parent {
|
||||
background: #E8F8F0;
|
||||
background: #fff1d4;
|
||||
|
||||
text {
|
||||
color: #16A34A;
|
||||
@@ -262,17 +250,23 @@
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-name-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.conversation-name {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
@@ -280,6 +274,7 @@
|
||||
}
|
||||
|
||||
.conversation-tag {
|
||||
flex-shrink: 0;
|
||||
height: 40px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
@@ -301,12 +296,17 @@
|
||||
}
|
||||
|
||||
.conversation-footer {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.last-message {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
font-size: 28px;
|
||||
line-height: 1.5;
|
||||
color: #666666;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
@@ -318,6 +318,9 @@
|
||||
|
||||
.conversation-meta {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-top: 10px;
|
||||
font-size: 24px;
|
||||
color: #A0A0A0;
|
||||
@@ -369,5 +372,5 @@
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
.conversation-avatar-art { width: 100%; height: 100%; }
|
||||
.conversation-avatar-art { display: block; width: 100%; height: 100%; }
|
||||
.conversation-avatar { background: #fff1d4; padding: 8px; box-sizing: border-box; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getParticipantArtwork } from '@/utils/artwork'
|
||||
import { formatConversationPreview } from '@/utils/chat-preview'
|
||||
import PageTitle from '@/components/page-title'
|
||||
import { getPageStyle } from '@/utils/page-style'
|
||||
import { setActiveTabRoute } from '@/utils/tab-bar'
|
||||
@@ -172,7 +173,7 @@ export default function Chat() {
|
||||
<Text className='empty-title'>当前宝贝暂无会话</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView className='conversation-list' scrollY>
|
||||
<ScrollView className='conversation-list' scrollY enhanced showScrollbar={false}>
|
||||
{conversations.map((conversation) => (
|
||||
<View
|
||||
key={conversation.key}
|
||||
@@ -191,9 +192,9 @@ export default function Chat() {
|
||||
<Text className='conversation-time'>{conversation.time}</Text>
|
||||
</View>
|
||||
<View className='conversation-footer'>
|
||||
<Text className='last-message' numberOfLines={1}>
|
||||
{conversation.lastMessage}
|
||||
</Text>
|
||||
<View className='last-message'>
|
||||
{formatConversationPreview(conversation.lastMessage)}
|
||||
</View>
|
||||
</View>
|
||||
{conversation.childName && <Text className='conversation-meta'>{conversation.childName}</Text>}
|
||||
</View>
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 80px 32px 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 44px;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
display: block;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.mode-switch {
|
||||
margin: 0 24px 20px;
|
||||
padding: 8px;
|
||||
|
||||
@@ -6,17 +6,6 @@
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 80px 32px 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 44px;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20px;
|
||||
|
||||
@@ -27,7 +27,13 @@ export default function TrafficBindingPage() {
|
||||
setOperation(current)
|
||||
if (action === 'unbind' && current.status === 'completed') clearSelectedBindingDeviceId()
|
||||
setError('')
|
||||
} catch (e: any) { setError(e.message || '暂时无法确认,请重试') }
|
||||
} catch (e: any) {
|
||||
console.error('[traffic-binding] recover:error', {
|
||||
deviceId, action, operationId: operation?.operation_id || null,
|
||||
code: e?.code || null, message: e?.message || String(e),
|
||||
})
|
||||
setError(e.message || '暂时无法确认,请重试')
|
||||
}
|
||||
finally { lock.current = false; setBusy(false) }
|
||||
}
|
||||
useDidShow(() => { void recover() })
|
||||
@@ -49,7 +55,13 @@ export default function TrafficBindingPage() {
|
||||
}
|
||||
setOperation(current)
|
||||
if (action === 'unbind' && current.status === 'completed') clearSelectedBindingDeviceId()
|
||||
} catch (e: any) { setError(e.message || e.errMsg || '操作未完成,请继续重试') }
|
||||
} catch (e: any) {
|
||||
console.error('[traffic-binding] continue:error', {
|
||||
deviceId, action, operationId: operation?.operation_id || null,
|
||||
code: e?.code || null, message: e?.message || e?.errMsg || String(e),
|
||||
})
|
||||
setError(e.message || e.errMsg || '操作未完成,请继续重试')
|
||||
}
|
||||
finally { lock.current = false; setBusy(false) }
|
||||
}
|
||||
const completed = operation?.status === 'completed'
|
||||
|
||||
@@ -3,6 +3,9 @@ import { request } from './api'
|
||||
import { getCurrentUserId } from './session'
|
||||
|
||||
export const TRAFFIC_APP_ID = 'wx56e1cfc52aab630f'
|
||||
function trafficLog(event: string, details: Record<string, unknown> = {}) {
|
||||
console.info(`[traffic-binding] ${event}`, details)
|
||||
}
|
||||
export type TrafficAction = 'bind' | 'unbind'
|
||||
export interface TrafficOperation {
|
||||
operation_id: string
|
||||
@@ -32,7 +35,11 @@ export function rememberOperation(operation: TrafficOperation) {
|
||||
Taro.setStorageSync(key(), { operation, receipt: previous?.operation.operation_id === operation.operation_id ? previous.receipt : undefined })
|
||||
}
|
||||
export function captureTrafficReturn(options: any): boolean {
|
||||
if (options?.referrerInfo?.appId !== TRAFFIC_APP_ID) return false
|
||||
const sourceAppId = options?.referrerInfo?.appId
|
||||
if (sourceAppId !== TRAFFIC_APP_ID) {
|
||||
trafficLog('return:ignored-app', { sourceAppId: sourceAppId || null, expectedAppId: TRAFFIC_APP_ID })
|
||||
return false
|
||||
}
|
||||
const data = options.referrerInfo.extraData
|
||||
const pending = getPendingTraffic()
|
||||
if (!pending || !data || data.operationId !== pending.operation.operation_id
|
||||
@@ -104,10 +111,17 @@ export async function resumeTrafficOperation(deviceId: string, action: TrafficAc
|
||||
return startTrafficOperation(deviceId, action)
|
||||
}
|
||||
export async function finishTrafficUnbind(operation: TrafficOperation) {
|
||||
trafficLog('finish-unbind:start', {
|
||||
operationId: operation.operation_id, deviceId: operation.device_id,
|
||||
status: operation.status, trafficStatus: operation.traffic_status,
|
||||
})
|
||||
const user = getCurrentUserId()
|
||||
const result = await request<TrafficOperation>(`/banban/joint-bindings/${operation.operation_id}/finish-unbind`, { method: 'POST' })
|
||||
assertSameUser(user)
|
||||
rememberOperation(result)
|
||||
trafficLog('finish-unbind:response', {
|
||||
operationId: result.operation_id, status: result.status, trafficStatus: result.traffic_status,
|
||||
})
|
||||
return result
|
||||
}
|
||||
function targetVersion(): 'trial' | 'release' {
|
||||
@@ -126,11 +140,28 @@ async function navigate(options: any, withinApp = false) {
|
||||
}
|
||||
export async function jumpTraffic(operation: TrafficOperation) {
|
||||
const user = getCurrentUserId()
|
||||
trafficLog('continue:start', {
|
||||
operationId: operation.operation_id, deviceId: operation.device_id,
|
||||
action: operation.action, status: operation.status, trafficStatus: operation.traffic_status,
|
||||
})
|
||||
const data = await request<any>(`/banban/joint-bindings/${operation.operation_id}/prepare`, { method: 'POST' })
|
||||
assertSameUser(user)
|
||||
rememberOperation(data.operation)
|
||||
const targetAppMatches = data.target_app_id === TRAFFIC_APP_ID
|
||||
trafficLog('continue:prepare-response', {
|
||||
operationId: operation.operation_id, action: operation.action,
|
||||
status: data.operation?.status || null, trafficStatus: data.operation?.traffic_status || null,
|
||||
targetAppId: data.target_app_id || null, expectedAppId: TRAFFIC_APP_ID,
|
||||
targetAppMatches, hasHandoffToken: typeof data.handoff_token === 'string' && data.handoff_token.length > 0,
|
||||
hasRequestId: typeof data.request_id === 'string' && data.request_id.length > 0,
|
||||
})
|
||||
if (data.operation.status === 'completed' || data.operation.traffic_status === 'server_confirmed') return data.operation as TrafficOperation
|
||||
if (data.target_app_id !== TRAFFIC_APP_ID) throw new Error('目标小程序配置不一致')
|
||||
if (!targetAppMatches) {
|
||||
trafficLog('continue:target-app-mismatch', {
|
||||
operationId: operation.operation_id, targetAppId: data.target_app_id || null, expectedAppId: TRAFFIC_APP_ID,
|
||||
})
|
||||
throw new Error('目标小程序配置不一致')
|
||||
}
|
||||
await navigate({
|
||||
appId: TRAFFIC_APP_ID, path: 'pages/handoff/index', envVersion: targetVersion(),
|
||||
extraData: { operationId: operation.operation_id, action: operation.action,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: 'MuyaoSoftbrush';
|
||||
src: url('../assets/fonts/Muyao-Softbrush.ttf') format('truetype');
|
||||
src: url('../assets/fonts/Muyao-Headers.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
const roleArtwork: Record<string, string> = {
|
||||
banban: require('../assets/ui/role-banban.png'),
|
||||
banban: require('../assets/ui/role-tuntunzai.png'),
|
||||
baikeboshi: require('../assets/ui/role-baike.png'),
|
||||
zhuli: require('../assets/ui/role-zhuli.png'),
|
||||
tuntunzai: require('../assets/ui/role-tuntunzai.png'),
|
||||
}
|
||||
|
||||
export function getRoleArtwork(roleKey?: string): string {
|
||||
return roleArtwork[String(roleKey || '').toLowerCase()] || roleArtwork.banban
|
||||
return roleArtwork[String(roleKey || '').trim().toLowerCase()] || roleArtwork.banban
|
||||
}
|
||||
|
||||
export function getParticipantArtwork(kind?: string, _roleKey?: string): string {
|
||||
if (kind === 'parent') return require('../assets/ui/avatar-parent.png')
|
||||
export function getParticipantArtwork(kind?: string, roleKey?: string): string {
|
||||
const participantKind = String(kind || '').trim().toLowerCase()
|
||||
if (participantKind === 'parent') return require('../assets/ui/avatar-parent.png')
|
||||
if (participantKind === 'ai') return getRoleArtwork(roleKey)
|
||||
return roleArtwork.tuntunzai
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Keep line breaks in message history; only flatten the conversation-list preview.
|
||||
export function formatConversationPreview(value?: string | null): string {
|
||||
return String(value || '').replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const { test } = require('node:test')
|
||||
const ts = require('typescript')
|
||||
|
||||
function loadTypeScript(file, mocks = {}) {
|
||||
const source = fs.readFileSync(path.join(__dirname, '../src', file), 'utf8')
|
||||
const { outputText } = ts.transpileModule(source, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2017, esModuleInterop: true },
|
||||
})
|
||||
const module = { exports: {} }
|
||||
new Function('require', 'module', 'exports', outputText)((id) => {
|
||||
if (Object.hasOwn(mocks, id)) return mocks[id]
|
||||
if (id.endsWith('.png')) return path.basename(id)
|
||||
return require(id)
|
||||
}, module, module.exports)
|
||||
return module.exports
|
||||
}
|
||||
|
||||
const { getParticipantArtwork, getRoleArtwork } = loadTypeScript('utils/artwork.ts')
|
||||
const { formatConversationPreview } = loadTypeScript('utils/chat-preview.ts')
|
||||
const story = '好呀,我给你讲一个《小水滴的旅行》的故事。\r\n\r\n有一天,小水滴在云妈妈的怀抱里睡着了。\n小水滴落到了一片荷叶上。\u2028小水滴继续向前,来到了大河里。\n这个故事告诉我们,水是不断循环的。💧'
|
||||
const roles = ['Banban', 'Baikeboshi', 'Zhuli', 'Tuntunzai']
|
||||
const { getConversations, getMessages } = loadTypeScript('services/chat.ts', {
|
||||
'@tarojs/taro': {},
|
||||
'./auth': { getCurrentUserId: () => 1 },
|
||||
'./binding': { loadCurrentChildBindingContext: async () => ({
|
||||
currentChild: { child_id: 7, child_name: '拌拌' },
|
||||
currentBinding: { device_id: 'fixture-device' },
|
||||
}) },
|
||||
'./session': {},
|
||||
'./api': { request: async (url) => {
|
||||
if (url.endsWith('/roles')) return roles.map(role_key => ({ role_key, name: role_key }))
|
||||
if (url.includes('/ai-conversations')) return { items: roles.map((role_key, index) => ({
|
||||
conversation_id: 10 + index, role_key, message_count: 1, last_message_preview: story,
|
||||
})) }
|
||||
if (url.includes('/conversations?')) return { items: [
|
||||
{ conversation_id: 1, conversation_type_name: 'parent_child', peer_type: 'parent', peer_id: '1' },
|
||||
{ conversation_id: 2, conversation_type_name: 'parent_child', peer_type: 'parent', peer_id: '2', peer_name: '右右' },
|
||||
{ conversation_id: 3, conversation_type_name: 'child_peer', peer_type: 'child', peer_id: '42', peer_name: '伴伴2号' },
|
||||
] }
|
||||
if (url.includes('/messages?')) return { items: [{
|
||||
id: 100, conversation_id: 11, role_key: 'Baikeboshi', is_user: false, content: story,
|
||||
}] }
|
||||
throw new Error(`Unexpected fixture request: ${url}`)
|
||||
} },
|
||||
})
|
||||
|
||||
test('real conversation mapping gives both parent entries the flower avatar and preserves specialist AI roles', async () => {
|
||||
const conversations = await getConversations()
|
||||
const artworkById = Object.fromEntries(conversations.map(item => [
|
||||
item.id, getParticipantArtwork(item.peerKind, item.roleKey),
|
||||
]))
|
||||
assert.equal(artworkById[1], 'avatar-parent.png')
|
||||
assert.equal(artworkById[2], 'avatar-parent.png')
|
||||
assert.equal(artworkById[3], 'role-tuntunzai.png')
|
||||
assert.equal(artworkById[10], 'role-tuntunzai.png')
|
||||
assert.equal(artworkById[11], 'role-baike.png')
|
||||
assert.equal(artworkById[12], 'role-zhuli.png')
|
||||
})
|
||||
|
||||
test('the role chooser and conversation avatars agree for each AI role', () => {
|
||||
for (const role of roles) {
|
||||
assert.equal(getParticipantArtwork('ai', role), getRoleArtwork(role))
|
||||
assert.equal(getParticipantArtwork('ai', ` ${role.toUpperCase()} `), getRoleArtwork(role))
|
||||
}
|
||||
})
|
||||
|
||||
test('a multiline story becomes a single-line list preview while message history keeps its paragraphs', async () => {
|
||||
const conversation = (await getConversations()).find(item => item.id === 11)
|
||||
const preview = formatConversationPreview(conversation.lastMessage)
|
||||
assert.doesNotMatch(preview, /[\r\n\u2028\u2029]/)
|
||||
assert.match(preview, /故事。 有一天/)
|
||||
assert.ok(preview.endsWith('💧'))
|
||||
assert.equal(conversation.lastMessage, story)
|
||||
const messages = await getMessages(11, 'ai')
|
||||
assert.equal(messages[0].content, story)
|
||||
})
|
||||
Reference in New Issue
Block a user