674 lines
21 KiB
TypeScript
674 lines
21 KiB
TypeScript
import Taro from '@tarojs/taro'
|
|
import { getCurrentUserId } from './auth'
|
|
import { ApiError, BASE_URL, getToken, request } from './api'
|
|
import { loadCurrentChildBindingContext } from './binding'
|
|
import { handleUnauthorized } from './session'
|
|
|
|
export type ConversationSource = 'im' | 'ai'
|
|
export type PeerKind = 'child' | 'parent' | 'ai'
|
|
|
|
const PARENT_CHILD_CONVERSATION_TYPE_NAME = 'parent_child'
|
|
|
|
export interface ChatConversation {
|
|
id: number
|
|
key: string
|
|
source: ConversationSource
|
|
peerKind: PeerKind
|
|
name: string
|
|
avatar: string
|
|
typeLabel: string
|
|
description: string
|
|
lastMessage: string
|
|
time: string
|
|
sortAt: string
|
|
roleKey?: string
|
|
conversationTypeName?: string
|
|
peerId?: string
|
|
childId?: number | null
|
|
childName?: string
|
|
parentUserId?: number | null
|
|
deviceId?: string
|
|
channelLabel: string
|
|
canSend: boolean
|
|
isSynthetic?: boolean
|
|
}
|
|
|
|
export interface ChatMessage {
|
|
id: number
|
|
type: 'user' | 'peer'
|
|
content: string
|
|
time: string
|
|
conversationId: number
|
|
contentType?: number
|
|
mediaUrl?: string | null
|
|
mediaDurationMs?: number | null
|
|
mediaMimeType?: string | null
|
|
mediaTranscriptText?: string | null
|
|
senderType?: string
|
|
senderId?: string
|
|
receiverType?: string
|
|
receiverId?: string
|
|
senderName?: string
|
|
receiverName?: string
|
|
channelLabel?: string
|
|
extJson?: Record<string, any> | null
|
|
}
|
|
|
|
export interface Character {
|
|
id: number
|
|
name: string
|
|
description: string
|
|
icon: string
|
|
peerKind: PeerKind
|
|
source: ConversationSource
|
|
conversationTypeName?: string
|
|
childId?: number | null
|
|
childName?: string
|
|
peerId?: string
|
|
parentUserId?: number | null
|
|
deviceId?: string
|
|
channelLabel: string
|
|
canSend: boolean
|
|
}
|
|
|
|
interface DeviceAiMessageItem {
|
|
id: number
|
|
conversation_id: number
|
|
role_key: string
|
|
is_user: boolean
|
|
speaker: 'user' | 'assistant'
|
|
content: string
|
|
timestamp: number
|
|
created_at: string
|
|
}
|
|
|
|
interface DeviceAiMessageListResponse {
|
|
items: DeviceAiMessageItem[]
|
|
total: number
|
|
next_cursor?: number | null
|
|
}
|
|
|
|
interface ChildConversationItem {
|
|
conversation_id: number
|
|
conversation_type: number
|
|
conversation_type_name: string
|
|
peer_type: 'child' | 'parent'
|
|
peer_id: string
|
|
peer_name?: string | null
|
|
last_message_preview?: string | null
|
|
last_message_at?: string | null
|
|
message_count: number
|
|
}
|
|
|
|
interface ChildConversationListResponse {
|
|
items: ChildConversationItem[]
|
|
total: number
|
|
next_cursor?: number | null
|
|
}
|
|
|
|
interface ChildConversationMessageItem {
|
|
id: number
|
|
conversation_id: number
|
|
seq: number
|
|
sender_type: 'child' | 'parent'
|
|
sender_id: string
|
|
receiver_type: 'child' | 'parent'
|
|
receiver_id: string
|
|
content_type: number
|
|
content_text?: string | null
|
|
content_json?: Record<string, any> | null
|
|
media_file_key?: string | null
|
|
media_duration_ms?: number | null
|
|
media_mime_type?: string | null
|
|
media_size_bytes?: number | null
|
|
media_transcript_text?: string | null
|
|
client_msg_id?: string | null
|
|
sender_name_snapshot?: string | null
|
|
receiver_name_snapshot?: string | null
|
|
ext_json?: Record<string, any> | null
|
|
created_at: string
|
|
}
|
|
|
|
interface ChildConversationMessageListResponse {
|
|
conversation_id: number
|
|
has_more: boolean
|
|
next_cursor_seq?: number | null
|
|
items: ChildConversationMessageItem[]
|
|
}
|
|
|
|
interface ConversationMessageCreateResponse {
|
|
idempotent: boolean
|
|
conversation_id: number
|
|
conversation_type: number
|
|
conversation_type_name: string
|
|
message: ChildConversationMessageItem
|
|
}
|
|
|
|
interface BindingContext {
|
|
deviceId: string
|
|
childId: number | null
|
|
childName: string
|
|
currentUserId: number
|
|
}
|
|
|
|
const PEER_META: Record<PeerKind, { icon: string; label: string; description: string }> = {
|
|
child: { icon: '🧒', label: '儿童', description: '孩子正在交流的儿童玩伴' },
|
|
parent: { icon: '👨', label: '家长', description: '家长通过微信小程序和孩子沟通' },
|
|
ai: { icon: '🤖', label: 'AI', description: '孩子与 AI 的历史交流' },
|
|
}
|
|
|
|
const AI_ROLE_META: Record<string, { name: string; icon: string; description: string }> = {
|
|
assistant: { name: 'AI', icon: '🤖', description: '孩子与 AI 的历史交流' },
|
|
}
|
|
|
|
export function isParentChildConversation(conversationTypeName?: string | null): boolean {
|
|
return conversationTypeName === PARENT_CHILD_CONVERSATION_TYPE_NAME
|
|
}
|
|
|
|
function parseTime(value?: string | null): number {
|
|
if (!value) return 0
|
|
const timestamp = new Date(value).getTime()
|
|
return Number.isNaN(timestamp) ? 0 : timestamp
|
|
}
|
|
|
|
function parseNumericId(value?: string | number | null): number | null {
|
|
const normalizedValue = Number(value)
|
|
if (!Number.isFinite(normalizedValue) || normalizedValue <= 0) return null
|
|
return normalizedValue
|
|
}
|
|
|
|
function formatListTime(value?: string | null): string {
|
|
if (!value) return ''
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) return value
|
|
const now = new Date()
|
|
const isSameDay =
|
|
date.getFullYear() === now.getFullYear() &&
|
|
date.getMonth() === now.getMonth() &&
|
|
date.getDate() === now.getDate()
|
|
if (isSameDay) {
|
|
return `${`${date.getHours()}`.padStart(2, '0')}:${`${date.getMinutes()}`.padStart(2, '0')}`
|
|
}
|
|
return `${date.getMonth() + 1}/${date.getDate()}`
|
|
}
|
|
|
|
function formatDetailTime(value?: string | null): string {
|
|
if (!value) return ''
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) return value
|
|
const month = `${date.getMonth() + 1}`.padStart(2, '0')
|
|
const day = `${date.getDate()}`.padStart(2, '0')
|
|
const hour = `${date.getHours()}`.padStart(2, '0')
|
|
const minute = `${date.getMinutes()}`.padStart(2, '0')
|
|
return `${month}-${day} ${hour}:${minute}`
|
|
}
|
|
|
|
function formatImPreview(
|
|
item: Pick<
|
|
ChildConversationMessageItem,
|
|
'content_type' | 'content_text' | 'media_transcript_text' | 'content_json' | 'ext_json'
|
|
>
|
|
): string {
|
|
if (item.content_type === 1) return item.content_text || '文本消息'
|
|
if (item.content_type === 2) {
|
|
if (item.ext_json?.message_kind === 'leave_message') {
|
|
return item.media_transcript_text || '[留言]'
|
|
}
|
|
return item.media_transcript_text || '[语音]'
|
|
}
|
|
if (item.content_type === 3) return '[图片]'
|
|
if (item.content_type === 4) {
|
|
const title = String(item.content_json?.title || item.content_json?.type || '').trim()
|
|
return title ? `[卡片] ${title}` : '[卡片]'
|
|
}
|
|
return '[消息]'
|
|
}
|
|
|
|
function createClientMessageId(): string {
|
|
return `parent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
}
|
|
|
|
function getAiMeta(roleKey?: string): { name: string; icon: string; description: string } {
|
|
if (roleKey && AI_ROLE_META[roleKey]) return AI_ROLE_META[roleKey]
|
|
return {
|
|
name: roleKey || 'AI',
|
|
icon: '🤖',
|
|
description: `孩子与 ${roleKey || 'AI'} 的历史交流`,
|
|
}
|
|
}
|
|
|
|
function getParticipantDisplayName(type: string, id?: string, name?: string | null): string {
|
|
if (name && name.trim()) return name.trim()
|
|
if (type === 'child') return id ? `儿童 ${id}` : '儿童'
|
|
if (type === 'parent') return id ? `家长 ${id}` : '家长'
|
|
return id || 'AI'
|
|
}
|
|
|
|
function getPeerDisplayName(peerKind: PeerKind, peerId?: string, peerName?: string | null, roleKey?: string): string {
|
|
if (peerKind === 'ai') return getAiMeta(roleKey).name
|
|
return getParticipantDisplayName(peerKind, peerId, peerName)
|
|
}
|
|
|
|
function getConversationRank(conversation: ChatConversation): number {
|
|
if (isParentChildConversation(conversation.conversationTypeName)) return 0
|
|
return 1
|
|
}
|
|
|
|
async function getBindingContext(): Promise<BindingContext> {
|
|
const context = await loadCurrentChildBindingContext()
|
|
const childName = String(context.currentChild?.child_name || context.currentBinding?.child_name || '').trim()
|
|
|
|
return {
|
|
deviceId: context.currentBinding?.device_id || '',
|
|
childId: context.currentChild?.child_id || null,
|
|
childName,
|
|
currentUserId: getCurrentUserId(),
|
|
}
|
|
}
|
|
|
|
async function getDeviceMessages(context?: BindingContext): Promise<DeviceAiMessageItem[]> {
|
|
const { deviceId } = context || (await getBindingContext())
|
|
if (!deviceId) return []
|
|
|
|
try {
|
|
const response = await request<DeviceAiMessageListResponse>(`/banban/devices/${deviceId}/messages?limit=100`)
|
|
return response.items || []
|
|
} catch (error: any) {
|
|
if (error?.status === 404) return []
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async function getChildConversations(context?: BindingContext): Promise<ChildConversationItem[]> {
|
|
const { childId } = context || (await getBindingContext())
|
|
if (!childId) return []
|
|
|
|
try {
|
|
const response = await request<ChildConversationListResponse>(`/banban/children/${childId}/conversations?limit=100`)
|
|
return response.items || []
|
|
} catch (error: any) {
|
|
if (error?.status === 404) return []
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async function getChildConversationMessages(
|
|
conversationId: number,
|
|
context?: BindingContext
|
|
): Promise<ChildConversationMessageItem[]> {
|
|
const { childId } = context || (await getBindingContext())
|
|
if (!childId || conversationId <= 0) return []
|
|
|
|
const response = await request<ChildConversationMessageListResponse>(
|
|
`/banban/children/${childId}/conversations/${conversationId}/messages?limit=100`
|
|
)
|
|
return response.items || []
|
|
}
|
|
|
|
function toImConversation(item: ChildConversationItem, context: BindingContext): ChatConversation {
|
|
const isParentConversation = isParentChildConversation(item.conversation_type_name)
|
|
const peerKind: PeerKind = item.peer_type === 'parent' ? 'parent' : 'child'
|
|
const meta = PEER_META[peerKind]
|
|
|
|
return {
|
|
id: item.conversation_id,
|
|
key: `im-${item.conversation_id}`,
|
|
source: 'im',
|
|
peerKind,
|
|
name: isParentConversation ? '家长沟通' : getPeerDisplayName(peerKind, item.peer_id, item.peer_name),
|
|
avatar: meta.icon,
|
|
typeLabel: isParentConversation ? '家长沟通' : meta.label,
|
|
description: isParentConversation ? '家长通过微信小程序和孩子直接沟通' : meta.description,
|
|
lastMessage: item.last_message_preview || (isParentConversation ? '还没有消息,点进去发送第一条' : '暂无消息'),
|
|
time: formatListTime(item.last_message_at),
|
|
sortAt: item.last_message_at || '',
|
|
conversationTypeName: item.conversation_type_name,
|
|
peerId: item.peer_id,
|
|
childId: context.childId,
|
|
childName: context.childName,
|
|
parentUserId: isParentConversation ? parseNumericId(item.peer_id) : null,
|
|
deviceId: context.deviceId,
|
|
channelLabel: isParentConversation ? '微信小程序' : '设备端',
|
|
canSend: isParentConversation,
|
|
}
|
|
}
|
|
|
|
function toAiConversation(item: DeviceAiMessageItem, context: BindingContext): ChatConversation {
|
|
const meta = getAiMeta(item.role_key)
|
|
|
|
return {
|
|
id: item.conversation_id,
|
|
key: `ai-${item.conversation_id}`,
|
|
source: 'ai',
|
|
peerKind: 'ai',
|
|
name: meta.name,
|
|
avatar: meta.icon,
|
|
typeLabel: PEER_META.ai.label,
|
|
description: meta.description,
|
|
lastMessage: item.content || '暂无消息',
|
|
time: formatListTime(item.created_at),
|
|
sortAt: item.created_at,
|
|
roleKey: item.role_key,
|
|
conversationTypeName: 'ai',
|
|
peerId: item.role_key,
|
|
childId: context.childId,
|
|
childName: context.childName,
|
|
parentUserId: null,
|
|
deviceId: context.deviceId,
|
|
channelLabel: 'AI',
|
|
canSend: false,
|
|
}
|
|
}
|
|
|
|
function createSyntheticParentConversation(context: BindingContext): ChatConversation | null {
|
|
if (!context.childId || !context.currentUserId) return null
|
|
|
|
return {
|
|
id: 0,
|
|
key: `im-parent-child-${context.childId}-${context.currentUserId}`,
|
|
source: 'im',
|
|
peerKind: 'parent',
|
|
name: '家长沟通',
|
|
avatar: PEER_META.parent.icon,
|
|
typeLabel: '家长沟通',
|
|
description: '家长通过微信小程序和孩子直接沟通',
|
|
lastMessage: '还没有留言,点进去发送第一条',
|
|
time: '',
|
|
sortAt: '',
|
|
conversationTypeName: PARENT_CHILD_CONVERSATION_TYPE_NAME,
|
|
peerId: String(context.currentUserId),
|
|
childId: context.childId,
|
|
childName: context.childName,
|
|
parentUserId: context.currentUserId,
|
|
deviceId: context.deviceId,
|
|
channelLabel: '微信小程序',
|
|
canSend: true,
|
|
isSynthetic: true,
|
|
}
|
|
}
|
|
|
|
export async function getConversations(): Promise<ChatConversation[]> {
|
|
const context = await getBindingContext()
|
|
const [imConversationItems, aiMessages] = await Promise.all([getChildConversations(context), getDeviceMessages(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 = [
|
|
...imConversations,
|
|
...Array.from(aiConversationMap.values()).map((item) => toAiConversation(item, context)),
|
|
]
|
|
|
|
const hasParentConversation = conversations.some(
|
|
(item) =>
|
|
isParentChildConversation(item.conversationTypeName) &&
|
|
item.parentUserId === context.currentUserId
|
|
)
|
|
|
|
if (!hasParentConversation) {
|
|
const syntheticConversation = createSyntheticParentConversation(context)
|
|
if (syntheticConversation) {
|
|
conversations.push(syntheticConversation)
|
|
}
|
|
}
|
|
|
|
return conversations.sort((left, right) => {
|
|
const rankDiff = getConversationRank(left) - getConversationRank(right)
|
|
if (rankDiff !== 0) return rankDiff
|
|
return parseTime(right.sortAt) - parseTime(left.sortAt)
|
|
})
|
|
}
|
|
|
|
export async function getMessages(
|
|
conversationId: number,
|
|
source: ConversationSource,
|
|
conversationTypeName?: string
|
|
): Promise<ChatMessage[]> {
|
|
const context = await getBindingContext()
|
|
|
|
if (source === 'ai') {
|
|
const items = await getDeviceMessages(context)
|
|
return items
|
|
.filter((item) => item.conversation_id === conversationId)
|
|
.slice()
|
|
.reverse()
|
|
.map((item) => ({
|
|
id: item.id,
|
|
type: item.is_user ? 'user' : 'peer',
|
|
content: item.content || '暂无内容',
|
|
time: formatDetailTime(item.created_at),
|
|
conversationId: item.conversation_id,
|
|
contentType: 1,
|
|
mediaUrl: null,
|
|
mediaDurationMs: null,
|
|
mediaMimeType: null,
|
|
mediaTranscriptText: null,
|
|
senderType: item.is_user ? 'device' : 'ai',
|
|
senderId: item.is_user ? context.deviceId : item.role_key,
|
|
receiverType: item.is_user ? 'ai' : 'device',
|
|
receiverId: item.is_user ? item.role_key : context.deviceId,
|
|
senderName: item.is_user ? context.childName || '孩子设备' : getAiMeta(item.role_key).name,
|
|
receiverName: item.is_user ? getAiMeta(item.role_key).name : context.childName || '孩子设备',
|
|
channelLabel: 'AI',
|
|
}))
|
|
}
|
|
|
|
const items = await getChildConversationMessages(conversationId, context)
|
|
const isParentConversation = isParentChildConversation(conversationTypeName)
|
|
|
|
return items.map((item) => ({
|
|
id: item.id,
|
|
type: isParentConversation
|
|
? item.sender_type === 'parent' && item.sender_id === String(context.currentUserId)
|
|
? 'user'
|
|
: 'peer'
|
|
: item.sender_type === 'child' && item.sender_id === String(context.childId || '')
|
|
? 'user'
|
|
: 'peer',
|
|
content: formatImPreview(item),
|
|
time: formatDetailTime(item.created_at),
|
|
conversationId: item.conversation_id,
|
|
contentType: item.content_type,
|
|
mediaUrl: item.media_file_key || null,
|
|
mediaDurationMs: item.media_duration_ms || null,
|
|
mediaMimeType: item.media_mime_type || null,
|
|
mediaTranscriptText: item.media_transcript_text || null,
|
|
senderType: item.sender_type,
|
|
senderId: item.sender_id,
|
|
receiverType: item.receiver_type,
|
|
receiverId: item.receiver_id,
|
|
extJson: item.ext_json || null,
|
|
senderName:
|
|
item.sender_type === 'parent' && item.sender_id === String(context.currentUserId)
|
|
? '我'
|
|
: item.sender_type === 'child' && item.sender_id === String(context.childId || '')
|
|
? context.childName || item.sender_name_snapshot || '孩子'
|
|
: getParticipantDisplayName(item.sender_type, item.sender_id, item.sender_name_snapshot),
|
|
receiverName:
|
|
item.receiver_type === 'parent' && item.receiver_id === String(context.currentUserId)
|
|
? '我'
|
|
: item.receiver_type === 'child' && item.receiver_id === String(context.childId || '')
|
|
? context.childName || item.receiver_name_snapshot || '孩子'
|
|
: getParticipantDisplayName(item.receiver_type, item.receiver_id, item.receiver_name_snapshot),
|
|
channelLabel:
|
|
isParentConversation && (item.sender_type === 'parent' || item.receiver_type === 'parent')
|
|
? '微信小程序'
|
|
: '设备端',
|
|
}))
|
|
}
|
|
|
|
export async function getCharacter(options: {
|
|
conversationId: number
|
|
source: ConversationSource
|
|
peerKind?: PeerKind
|
|
name?: string
|
|
roleKey?: string
|
|
conversationTypeName?: string
|
|
childId?: number | null
|
|
childName?: string
|
|
peerId?: string
|
|
parentUserId?: number | null
|
|
deviceId?: string
|
|
channelLabel?: string
|
|
canSend?: boolean
|
|
}): Promise<Character | null> {
|
|
const {
|
|
conversationId,
|
|
source,
|
|
peerKind = 'ai',
|
|
name,
|
|
roleKey,
|
|
conversationTypeName,
|
|
childId,
|
|
childName,
|
|
peerId,
|
|
parentUserId,
|
|
deviceId,
|
|
channelLabel,
|
|
canSend = false,
|
|
} = options
|
|
|
|
if (source === 'ai') {
|
|
const meta = getAiMeta(roleKey)
|
|
return {
|
|
id: conversationId,
|
|
name: name || meta.name,
|
|
description: 'AI 对话通道',
|
|
icon: meta.icon,
|
|
peerKind: 'ai',
|
|
source,
|
|
conversationTypeName: 'ai',
|
|
childId,
|
|
childName,
|
|
peerId: peerId || roleKey,
|
|
parentUserId: null,
|
|
deviceId,
|
|
channelLabel: channelLabel || 'AI',
|
|
canSend: false,
|
|
}
|
|
}
|
|
|
|
if (isParentChildConversation(conversationTypeName)) {
|
|
return {
|
|
id: conversationId,
|
|
name: '家长沟通',
|
|
description: '微信小程序 <-> 儿童设备',
|
|
icon: PEER_META.parent.icon,
|
|
peerKind: 'parent',
|
|
source,
|
|
conversationTypeName: PARENT_CHILD_CONVERSATION_TYPE_NAME,
|
|
childId,
|
|
childName,
|
|
peerId,
|
|
parentUserId: parentUserId || parseNumericId(peerId),
|
|
deviceId,
|
|
channelLabel: channelLabel || '微信小程序',
|
|
canSend: canSend || true,
|
|
}
|
|
}
|
|
|
|
const meta = PEER_META[peerKind]
|
|
return {
|
|
id: conversationId,
|
|
name: getPeerDisplayName(peerKind, peerId, name),
|
|
description: peerKind === 'child' ? '儿童设备间的聊天记录' : meta.description,
|
|
icon: meta.icon,
|
|
peerKind,
|
|
source,
|
|
conversationTypeName,
|
|
childId,
|
|
childName,
|
|
peerId,
|
|
parentUserId,
|
|
deviceId,
|
|
channelLabel: channelLabel || '设备端',
|
|
canSend,
|
|
}
|
|
}
|
|
|
|
export async function sendParentMessage(childId: number, content: string): Promise<ConversationMessageCreateResponse> {
|
|
const normalizedContent = content.trim()
|
|
if (!normalizedContent) {
|
|
throw new Error('请输入消息内容')
|
|
}
|
|
|
|
return request<ConversationMessageCreateResponse>(`/banban/children/${childId}/messages`, {
|
|
method: 'POST',
|
|
data: {
|
|
content_type: 1,
|
|
content_text: normalizedContent,
|
|
client_msg_id: createClientMessageId(),
|
|
},
|
|
})
|
|
}
|
|
|
|
function parseUploadResponseData(rawData: any): any {
|
|
if (typeof rawData !== 'string') return rawData
|
|
try {
|
|
return JSON.parse(rawData)
|
|
} catch {
|
|
return { detail: rawData }
|
|
}
|
|
}
|
|
|
|
function getUploadErrorMessage(data: any): string {
|
|
if (!data) return '留言发送失败'
|
|
if (typeof data.detail === 'string') return data.detail
|
|
if (typeof data.message === 'string') return data.message
|
|
if (typeof data.errMsg === 'string') return data.errMsg
|
|
return '留言发送失败'
|
|
}
|
|
|
|
export async function sendParentVoiceMessage(
|
|
childId: number,
|
|
options: { filePath: string; durationMs: number; transcriptText?: string }
|
|
): Promise<ConversationMessageCreateResponse> {
|
|
if (!childId) {
|
|
throw new Error('当前会话不支持留言')
|
|
}
|
|
if (!options.filePath) {
|
|
throw new Error('录音文件不存在')
|
|
}
|
|
|
|
const token = getToken()
|
|
const requestUrl = `${BASE_URL}/banban/children/${childId}/voice-message`
|
|
const uploadResponse: any = await new Promise((resolve, reject) => {
|
|
Taro.uploadFile({
|
|
url: requestUrl,
|
|
filePath: options.filePath,
|
|
name: 'file',
|
|
header: token
|
|
? {
|
|
Authorization: `Bearer ${token}`,
|
|
}
|
|
: {},
|
|
formData: {
|
|
duration_ms: `${Math.max(1, Math.round(options.durationMs || 0))}`,
|
|
client_msg_id: createClientMessageId(),
|
|
transcript_text: options.transcriptText || '',
|
|
},
|
|
success: resolve,
|
|
fail: reject,
|
|
})
|
|
}).catch((error: any) => {
|
|
const message = String(error?.errMsg || error?.message || 'request:fail')
|
|
throw new ApiError(0, message)
|
|
})
|
|
|
|
if (uploadResponse.statusCode === 401) {
|
|
handleUnauthorized({ redirect: true })
|
|
}
|
|
|
|
const data = parseUploadResponseData(uploadResponse.data)
|
|
if (uploadResponse.statusCode >= 400) {
|
|
throw new ApiError(uploadResponse.statusCode, getUploadErrorMessage(data))
|
|
}
|
|
|
|
return data as ConversationMessageCreateResponse
|
|
}
|