add 小程序支持角色切换
This commit is contained in:
@@ -380,6 +380,10 @@
|
|||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role-switch-card {
|
||||||
|
max-height: 74vh;
|
||||||
|
}
|
||||||
|
|
||||||
.device-switch-empty {
|
.device-switch-empty {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
@@ -392,6 +396,11 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role-switch-list {
|
||||||
|
max-height: 56vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.device-switch-footer {
|
.device-switch-footer {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
@@ -424,6 +433,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role-switch-item {
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: #F7F8FA;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #FFF2E7;
|
||||||
|
border: 2px solid #FFBE94;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.58;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-switch-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-switch-name {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 30px;
|
||||||
|
color: #1A1A1A;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-switch-desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 25px;
|
||||||
|
line-height: 1.55;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
.device-switch-head {
|
.device-switch-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -10,7 +10,16 @@ import {
|
|||||||
unbindDevice,
|
unbindDevice,
|
||||||
} from '@/services/binding'
|
} from '@/services/binding'
|
||||||
import { Child, clearSelectedChildId, createChild, setSelectedChildId, updateChild } from '@/services/child'
|
import { Child, clearSelectedChildId, createChild, setSelectedChildId, updateChild } from '@/services/child'
|
||||||
import { DeviceFirmwareStatus, getDeviceFirmwareStatus, startDeviceFirmwareUpdate } from '@/services/device'
|
import {
|
||||||
|
DeviceCurrentRole,
|
||||||
|
DeviceFirmwareStatus,
|
||||||
|
DeviceRoleSummary,
|
||||||
|
getDeviceFirmwareStatus,
|
||||||
|
getDeviceRole,
|
||||||
|
getDeviceRoles,
|
||||||
|
startDeviceFirmwareUpdate,
|
||||||
|
updateDeviceRole,
|
||||||
|
} from '@/services/device'
|
||||||
import { useSystemBanner } from '@/components/system-banner/use-system-banner'
|
import { useSystemBanner } from '@/components/system-banner/use-system-banner'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
@@ -31,10 +40,15 @@ export default function Sleep() {
|
|||||||
const [binding, setBinding] = useState<BindingListItem | null>(null)
|
const [binding, setBinding] = useState<BindingListItem | null>(null)
|
||||||
const [currentChild, setCurrentChild] = useState<Child | null>(null)
|
const [currentChild, setCurrentChild] = useState<Child | null>(null)
|
||||||
const [firmwareStatus, setFirmwareStatus] = useState<DeviceFirmwareStatus | null>(null)
|
const [firmwareStatus, setFirmwareStatus] = useState<DeviceFirmwareStatus | null>(null)
|
||||||
|
const [deviceRole, setDeviceRole] = useState<DeviceCurrentRole | null>(null)
|
||||||
|
const [roles, setRoles] = useState<DeviceRoleSummary[]>([])
|
||||||
const [isLoadingFirmware, setIsLoadingFirmware] = useState(false)
|
const [isLoadingFirmware, setIsLoadingFirmware] = useState(false)
|
||||||
const [isUpdatingFirmware, setIsUpdatingFirmware] = useState(false)
|
const [isUpdatingFirmware, setIsUpdatingFirmware] = useState(false)
|
||||||
|
const [isLoadingRoles, setIsLoadingRoles] = useState(false)
|
||||||
|
const [isUpdatingRole, setIsUpdatingRole] = useState(false)
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [showChildModal, setShowChildModal] = useState(false)
|
const [showChildModal, setShowChildModal] = useState(false)
|
||||||
|
const [showRoleModal, setShowRoleModal] = useState(false)
|
||||||
const [modalType, setModalType] = useState<'add' | 'edit'>('add')
|
const [modalType, setModalType] = useState<'add' | 'edit'>('add')
|
||||||
const [childName, setChildName] = useState('')
|
const [childName, setChildName] = useState('')
|
||||||
const [editingChildId, setEditingChildId] = useState<number | null>(null)
|
const [editingChildId, setEditingChildId] = useState<number | null>(null)
|
||||||
@@ -60,6 +74,7 @@ export default function Sleep() {
|
|||||||
setBinding(nextBinding)
|
setBinding(nextBinding)
|
||||||
setParentInfo(Taro.getStorageSync('userInfo') || {})
|
setParentInfo(Taro.getStorageSync('userInfo') || {})
|
||||||
void loadFirmwareStatus(nextBinding?.device_id)
|
void loadFirmwareStatus(nextBinding?.device_id)
|
||||||
|
void loadRoleData(nextBinding?.device_id)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[manage] load failed:', error)
|
console.error('[manage] load failed:', error)
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
@@ -97,6 +112,36 @@ export default function Sleep() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadRoleData = async (deviceId?: string | null) => {
|
||||||
|
const normalizedDeviceId = String(deviceId || '').trim()
|
||||||
|
if (!normalizedDeviceId) {
|
||||||
|
setDeviceRole(null)
|
||||||
|
setRoles([])
|
||||||
|
setIsLoadingRoles(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingRoles(true)
|
||||||
|
try {
|
||||||
|
const [nextRoles, nextDeviceRole] = await Promise.all([
|
||||||
|
getDeviceRoles(),
|
||||||
|
getDeviceRole(normalizedDeviceId),
|
||||||
|
])
|
||||||
|
setRoles(nextRoles)
|
||||||
|
setDeviceRole(nextDeviceRole)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[manage] role load failed:', error)
|
||||||
|
setRoles([])
|
||||||
|
setDeviceRole(null)
|
||||||
|
Taro.showToast({
|
||||||
|
title: error?.message || '角色信息加载失败',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setIsLoadingRoles(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleOpenModal = (type: 'add' | 'edit', child?: Child) => {
|
const handleOpenModal = (type: 'add' | 'edit', child?: Child) => {
|
||||||
setModalType(type)
|
setModalType(type)
|
||||||
setChildName(child?.child_name || '')
|
setChildName(child?.child_name || '')
|
||||||
@@ -121,6 +166,7 @@ export default function Sleep() {
|
|||||||
setCurrentChild(child)
|
setCurrentChild(child)
|
||||||
setBinding(nextBinding)
|
setBinding(nextBinding)
|
||||||
void loadFirmwareStatus(nextBinding?.device_id)
|
void loadFirmwareStatus(nextBinding?.device_id)
|
||||||
|
void loadRoleData(nextBinding?.device_id)
|
||||||
setShowChildModal(false)
|
setShowChildModal(false)
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '已切换当前孩子',
|
title: '已切换当前孩子',
|
||||||
@@ -192,6 +238,37 @@ export default function Sleep() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenRoleModal = () => {
|
||||||
|
if (!binding?.device_id) {
|
||||||
|
Taro.showToast({ title: '当前没有可用设备', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setShowRoleModal(true)
|
||||||
|
if (roles.length === 0) {
|
||||||
|
void loadRoleData(binding.device_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectRole = async (role: DeviceRoleSummary) => {
|
||||||
|
if (!binding?.device_id || isUpdatingRole) return
|
||||||
|
|
||||||
|
setIsUpdatingRole(true)
|
||||||
|
try {
|
||||||
|
const language = deviceRole?.preferred_language || role.default_language || role.languages?.[0] || null
|
||||||
|
const nextRole = await updateDeviceRole(role.role_key, language, binding.device_id)
|
||||||
|
setDeviceRole(nextRole)
|
||||||
|
setShowRoleModal(false)
|
||||||
|
Taro.showToast({ title: 'AI 角色已切换', icon: 'success' })
|
||||||
|
} catch (error: any) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: error?.message || '角色切换失败',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setIsUpdatingRole(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleUnbind = () => {
|
const handleUnbind = () => {
|
||||||
if (!binding) return
|
if (!binding) return
|
||||||
|
|
||||||
@@ -268,6 +345,11 @@ export default function Sleep() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.name === 'AI 角色') {
|
||||||
|
handleOpenRoleModal()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (item.name === '解除设备绑定') {
|
if (item.name === '解除设备绑定') {
|
||||||
handleUnbind()
|
handleUnbind()
|
||||||
}
|
}
|
||||||
@@ -289,6 +371,11 @@ export default function Sleep() {
|
|||||||
const latestFirmwareLabel = firmwareStatus?.latest_version || '--'
|
const latestFirmwareLabel = firmwareStatus?.latest_version || '--'
|
||||||
const canUpdateFirmware = Boolean(binding?.device_id && firmwareStatus?.can_update && !isLoadingFirmware && !isUpdatingFirmware)
|
const canUpdateFirmware = Boolean(binding?.device_id && firmwareStatus?.can_update && !isLoadingFirmware && !isUpdatingFirmware)
|
||||||
const firmwareActionText = isLoadingFirmware ? '查询中' : isUpdatingFirmware ? '发送中' : '更新'
|
const firmwareActionText = isLoadingFirmware ? '查询中' : isUpdatingFirmware ? '发送中' : '更新'
|
||||||
|
const roleValue = !binding?.device_id
|
||||||
|
? '未绑定'
|
||||||
|
: isLoadingRoles
|
||||||
|
? '查询中'
|
||||||
|
: deviceRole?.name || '未设置'
|
||||||
const firmwareSubtitle = !binding?.device_id
|
const firmwareSubtitle = !binding?.device_id
|
||||||
? '绑定设备后可查看系统版本'
|
? '绑定设备后可查看系统版本'
|
||||||
: isLoadingFirmware
|
: isLoadingFirmware
|
||||||
@@ -325,6 +412,14 @@ export default function Sleep() {
|
|||||||
value: binding?.device_id ? `当前: ${binding.device_id}` : '未绑定',
|
value: binding?.device_id ? `当前: ${binding.device_id}` : '未绑定',
|
||||||
arrow: true,
|
arrow: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||||
|
iconBgClass: 'green',
|
||||||
|
name: 'AI 角色',
|
||||||
|
value: roleValue,
|
||||||
|
arrow: true,
|
||||||
|
disabled: !binding,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: require('../../assets/tab-icons/broken-rings.png'),
|
icon: require('../../assets/tab-icons/broken-rings.png'),
|
||||||
iconBgClass: 'red',
|
iconBgClass: 'red',
|
||||||
@@ -501,6 +596,49 @@ export default function Sleep() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{showRoleModal && (
|
||||||
|
<View className='modal-mask' onClick={() => setShowRoleModal(false)}>
|
||||||
|
<View
|
||||||
|
className='modal-card role-switch-card'
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text className='modal-title'>切换 AI 角色</Text>
|
||||||
|
{isLoadingRoles ? (
|
||||||
|
<Text className='device-switch-empty'>角色加载中...</Text>
|
||||||
|
) : roles.length === 0 ? (
|
||||||
|
<Text className='device-switch-empty'>当前没有可用角色</Text>
|
||||||
|
) : (
|
||||||
|
<View className='role-switch-list'>
|
||||||
|
{roles.map((item) => {
|
||||||
|
const isActive = deviceRole?.role_key === item.role_key
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={item.role_key}
|
||||||
|
className={`role-switch-item ${isActive ? 'active' : ''} ${isUpdatingRole ? 'disabled' : ''}`}
|
||||||
|
onClick={isUpdatingRole ? undefined : () => handleSelectRole(item)}
|
||||||
|
>
|
||||||
|
<View className='role-switch-head'>
|
||||||
|
<Text className='role-switch-name'>{item.name || item.role_key}</Text>
|
||||||
|
{isActive && <Text className='device-switch-tag'>当前</Text>}
|
||||||
|
</View>
|
||||||
|
<Text className='role-switch-desc'>
|
||||||
|
{item.description || `角色标识:${item.role_key}`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<View className='modal-actions'>
|
||||||
|
<Text className='modal-action cancel' onClick={() => setShowRoleModal(false)}>
|
||||||
|
关闭
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
{systemBanner}
|
{systemBanner}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -88,6 +88,30 @@ interface DeviceAiMessageListResponse {
|
|||||||
next_cursor?: number | null
|
next_cursor?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeviceAiConversationItem {
|
||||||
|
conversation_id: number
|
||||||
|
role_key: string
|
||||||
|
role_name: string
|
||||||
|
role_description?: string | null
|
||||||
|
message_count: number
|
||||||
|
last_message_preview?: string | null
|
||||||
|
last_message_at?: string | null
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeviceAiConversationListResponse {
|
||||||
|
items: DeviceAiConversationItem[]
|
||||||
|
total: number
|
||||||
|
next_cursor?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeviceRoleSummary {
|
||||||
|
role_key: string
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
interface ChildConversationItem {
|
interface ChildConversationItem {
|
||||||
conversation_id: number
|
conversation_id: number
|
||||||
conversation_type: number
|
conversation_type: number
|
||||||
@@ -161,6 +185,8 @@ const AI_ROLE_META: Record<string, { name: string; icon: string; description: st
|
|||||||
assistant: { name: 'AI', icon: '🤖', description: '孩子与 AI 的历史交流' },
|
assistant: { name: 'AI', icon: '🤖', description: '孩子与 AI 的历史交流' },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AiMeta = { name: string; icon: string; description: string }
|
||||||
|
|
||||||
export function isParentChildConversation(conversationTypeName?: string | null): boolean {
|
export function isParentChildConversation(conversationTypeName?: string | null): boolean {
|
||||||
return conversationTypeName === PARENT_CHILD_CONVERSATION_TYPE_NAME
|
return conversationTypeName === PARENT_CHILD_CONVERSATION_TYPE_NAME
|
||||||
}
|
}
|
||||||
@@ -225,7 +251,16 @@ function createClientMessageId(): string {
|
|||||||
return `parent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
return `parent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAiMeta(roleKey?: string): { name: string; icon: string; description: string } {
|
function getAiMeta(roleKey?: string, roleName?: string | null, roleDescription?: string | null): AiMeta {
|
||||||
|
const normalizedRoleName = String(roleName || '').trim()
|
||||||
|
const normalizedRoleDescription = String(roleDescription || '').trim()
|
||||||
|
if (normalizedRoleName) {
|
||||||
|
return {
|
||||||
|
name: normalizedRoleName,
|
||||||
|
icon: '🤖',
|
||||||
|
description: normalizedRoleDescription || `孩子与 ${normalizedRoleName} 的历史交流`,
|
||||||
|
}
|
||||||
|
}
|
||||||
if (roleKey && AI_ROLE_META[roleKey]) return AI_ROLE_META[roleKey]
|
if (roleKey && AI_ROLE_META[roleKey]) return AI_ROLE_META[roleKey]
|
||||||
return {
|
return {
|
||||||
name: roleKey || 'AI',
|
name: roleKey || 'AI',
|
||||||
@@ -234,6 +269,21 @@ function getAiMeta(roleKey?: string): { name: string; icon: string; description:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getRoleMetaMap(): Promise<Record<string, AiMeta>> {
|
||||||
|
try {
|
||||||
|
const roles = await request<DeviceRoleSummary[]>('/banban/roles')
|
||||||
|
return roles.reduce<Record<string, AiMeta>>((result, role) => {
|
||||||
|
const roleKey = String(role.role_key || '').trim()
|
||||||
|
if (!roleKey) return result
|
||||||
|
result[roleKey] = getAiMeta(roleKey, role.name, role.description)
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.status === 404) return {}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getParticipantDisplayName(type: string, id?: string, name?: string | null): string {
|
function getParticipantDisplayName(type: string, id?: string, name?: string | null): string {
|
||||||
if (name && name.trim()) return name.trim()
|
if (name && name.trim()) return name.trim()
|
||||||
if (type === 'child') return id ? `儿童 ${id}` : '儿童'
|
if (type === 'child') return id ? `儿童 ${id}` : '儿童'
|
||||||
@@ -276,6 +326,21 @@ async function getDeviceMessages(context?: BindingContext): Promise<DeviceAiMess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getDeviceAiConversations(context?: BindingContext): Promise<DeviceAiConversationItem[]> {
|
||||||
|
const { deviceId } = context || (await getBindingContext())
|
||||||
|
if (!deviceId) return []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request<DeviceAiConversationListResponse>(
|
||||||
|
`/banban/devices/${deviceId}/ai-conversations?limit=100`
|
||||||
|
)
|
||||||
|
return response.items || []
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.status === 404) return []
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getChildConversations(context?: BindingContext): Promise<ChildConversationItem[]> {
|
async function getChildConversations(context?: BindingContext): Promise<ChildConversationItem[]> {
|
||||||
const { childId } = context || (await getBindingContext())
|
const { childId } = context || (await getBindingContext())
|
||||||
if (!childId) return []
|
if (!childId) return []
|
||||||
@@ -330,8 +395,10 @@ function toImConversation(item: ChildConversationItem, context: BindingContext):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toAiConversation(item: DeviceAiMessageItem, context: BindingContext): ChatConversation {
|
function toAiConversation(item: DeviceAiConversationItem, context: BindingContext, roleMetaMap: Record<string, AiMeta>): ChatConversation {
|
||||||
const meta = getAiMeta(item.role_key)
|
const meta = roleMetaMap[item.role_key] || getAiMeta(item.role_key, item.role_name, item.role_description)
|
||||||
|
const lastMessage = item.last_message_preview || (item.message_count > 0 ? '暂无内容' : '这个会话还没有消息')
|
||||||
|
const sortAt = item.last_message_at || item.updated_at || item.created_at
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.conversation_id,
|
id: item.conversation_id,
|
||||||
@@ -342,9 +409,9 @@ function toAiConversation(item: DeviceAiMessageItem, context: BindingContext): C
|
|||||||
avatar: meta.icon,
|
avatar: meta.icon,
|
||||||
typeLabel: PEER_META.ai.label,
|
typeLabel: PEER_META.ai.label,
|
||||||
description: meta.description,
|
description: meta.description,
|
||||||
lastMessage: item.content || '暂无消息',
|
lastMessage,
|
||||||
time: formatListTime(item.created_at),
|
time: formatListTime(sortAt),
|
||||||
sortAt: item.created_at,
|
sortAt,
|
||||||
roleKey: item.role_key,
|
roleKey: item.role_key,
|
||||||
conversationTypeName: 'ai',
|
conversationTypeName: 'ai',
|
||||||
peerId: item.role_key,
|
peerId: item.role_key,
|
||||||
@@ -386,19 +453,16 @@ function createSyntheticParentConversation(context: BindingContext): ChatConvers
|
|||||||
|
|
||||||
export async function getConversations(): Promise<ChatConversation[]> {
|
export async function getConversations(): Promise<ChatConversation[]> {
|
||||||
const context = await getBindingContext()
|
const context = await getBindingContext()
|
||||||
const [imConversationItems, aiMessages] = await Promise.all([getChildConversations(context), getDeviceMessages(context)])
|
const [imConversationItems, aiConversations, roleMetaMap] = await Promise.all([
|
||||||
|
getChildConversations(context),
|
||||||
|
getDeviceAiConversations(context),
|
||||||
|
getRoleMetaMap(),
|
||||||
|
])
|
||||||
const imConversations = imConversationItems.map((item) => toImConversation(item, context))
|
const imConversations = imConversationItems.map((item) => toImConversation(item, context))
|
||||||
const aiConversationMap = new Map<number, DeviceAiMessageItem>()
|
|
||||||
|
|
||||||
for (const item of aiMessages) {
|
|
||||||
if (!aiConversationMap.has(item.conversation_id)) {
|
|
||||||
aiConversationMap.set(item.conversation_id, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const conversations = [
|
const conversations = [
|
||||||
...imConversations,
|
...imConversations,
|
||||||
...Array.from(aiConversationMap.values()).map((item) => toAiConversation(item, context)),
|
...aiConversations.map((item) => toAiConversation(item, context, roleMetaMap)),
|
||||||
]
|
]
|
||||||
|
|
||||||
const hasParentConversation = conversations.some(
|
const hasParentConversation = conversations.some(
|
||||||
@@ -429,12 +493,14 @@ export async function getMessages(
|
|||||||
const context = await getBindingContext()
|
const context = await getBindingContext()
|
||||||
|
|
||||||
if (source === 'ai') {
|
if (source === 'ai') {
|
||||||
const items = await getDeviceMessages(context)
|
const [items, roleMetaMap] = await Promise.all([getDeviceMessages(context), getRoleMetaMap()])
|
||||||
return items
|
return items
|
||||||
.filter((item) => item.conversation_id === conversationId)
|
.filter((item) => item.conversation_id === conversationId)
|
||||||
.slice()
|
.slice()
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((item) => ({
|
.map((item) => {
|
||||||
|
const meta = roleMetaMap[item.role_key] || getAiMeta(item.role_key)
|
||||||
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
type: item.is_user ? 'user' : 'peer',
|
type: item.is_user ? 'user' : 'peer',
|
||||||
content: item.content || '暂无内容',
|
content: item.content || '暂无内容',
|
||||||
@@ -449,10 +515,11 @@ export async function getMessages(
|
|||||||
senderId: item.is_user ? context.deviceId : item.role_key,
|
senderId: item.is_user ? context.deviceId : item.role_key,
|
||||||
receiverType: item.is_user ? 'ai' : 'device',
|
receiverType: item.is_user ? 'ai' : 'device',
|
||||||
receiverId: item.is_user ? item.role_key : context.deviceId,
|
receiverId: item.is_user ? item.role_key : context.deviceId,
|
||||||
senderName: item.is_user ? context.childName || '孩子设备' : getAiMeta(item.role_key).name,
|
senderName: item.is_user ? context.childName || '孩子设备' : meta.name,
|
||||||
receiverName: item.is_user ? getAiMeta(item.role_key).name : context.childName || '孩子设备',
|
receiverName: item.is_user ? meta.name : context.childName || '孩子设备',
|
||||||
channelLabel: 'AI',
|
channelLabel: 'AI',
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = await getChildConversationMessages(conversationId, context)
|
const items = await getChildConversationMessages(conversationId, context)
|
||||||
|
|||||||
@@ -79,6 +79,23 @@ export interface DeviceFirmwareUpdateResponse extends DeviceFirmwareStatus {
|
|||||||
msg_id: string
|
msg_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeviceRoleSummary {
|
||||||
|
role_key: string
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
default_language?: string | null
|
||||||
|
languages: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceCurrentRole {
|
||||||
|
device_id: string
|
||||||
|
role_key: string
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
preferred_language?: string | null
|
||||||
|
languages: string[]
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeTimeValue(value?: string | null): string | null {
|
function normalizeTimeValue(value?: string | null): string | null {
|
||||||
if (value === null || value === undefined) return null
|
if (value === null || value === undefined) return null
|
||||||
const trimmed = String(value).trim()
|
const trimmed = String(value).trim()
|
||||||
@@ -203,3 +220,40 @@ export async function getDeviceAlarms(
|
|||||||
|
|
||||||
return request<DeviceAlarmListResponse>(`/banban/devices/${resolvedDeviceId}/alarms?limit=${limit}`)
|
return request<DeviceAlarmListResponse>(`/banban/devices/${resolvedDeviceId}/alarms?limit=${limit}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getDeviceRoles(): Promise<DeviceRoleSummary[]> {
|
||||||
|
return request<DeviceRoleSummary[]>('/banban/roles')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDeviceRole(deviceId?: string): Promise<DeviceCurrentRole | null> {
|
||||||
|
const resolvedDeviceId = await resolveDeviceId(deviceId)
|
||||||
|
if (!resolvedDeviceId) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await request<DeviceCurrentRole>(`/banban/roles/devices/${resolvedDeviceId}`)
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error?.status === 404) return null
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateDeviceRole(
|
||||||
|
roleKey: string,
|
||||||
|
language?: string | null,
|
||||||
|
deviceId?: string,
|
||||||
|
playWelcome = false
|
||||||
|
): Promise<DeviceCurrentRole> {
|
||||||
|
const resolvedDeviceId = await resolveDeviceId(deviceId)
|
||||||
|
if (!resolvedDeviceId) {
|
||||||
|
throw new Error('当前没有可用设备')
|
||||||
|
}
|
||||||
|
|
||||||
|
return request<DeviceCurrentRole>(`/banban/roles/devices/${resolvedDeviceId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data: {
|
||||||
|
role_key: roleKey,
|
||||||
|
language: language || undefined,
|
||||||
|
play_welcome: playWelcome,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user