|
|
|
|
@ -182,6 +182,33 @@ function formatDateTime(ms) {
|
|
|
|
|
} catch (e) { return '' }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查找下一个“休闲”类活动的开始时间(返回 Date 或 null)
|
|
|
|
|
function findNextRelaxStart(oc) {
|
|
|
|
|
try {
|
|
|
|
|
if (!oc || !Array.isArray(oc.schedule)) return null
|
|
|
|
|
const now = new Date()
|
|
|
|
|
const todayStr = now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0') + '-' + String(now.getDate()).padStart(2,'0')
|
|
|
|
|
// 收集未来或当日尚未开始的活动
|
|
|
|
|
const candidates = []
|
|
|
|
|
for (const s of oc.schedule) {
|
|
|
|
|
const dateStr = s.date || todayStr
|
|
|
|
|
const timeStr = (s.time || '00:00').slice(0,5)
|
|
|
|
|
const start = new Date(`${dateStr}T${timeStr}`)
|
|
|
|
|
if (start.getTime() > Date.now()) {
|
|
|
|
|
const eventText = (s.event || '').toLowerCase()
|
|
|
|
|
if (/休息|放松|休闲|看电影|娱乐|咖啡|喝茶/ig.test(eventText)) {
|
|
|
|
|
candidates.push(start)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!candidates.length) return null
|
|
|
|
|
candidates.sort((a,b) => a.getTime() - b.getTime())
|
|
|
|
|
return candidates[0]
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 当 activeContact 或其日程变更时,如果有待处理的占位回复,重新计算并重设定时器
|
|
|
|
|
watch(() => [props.activeContact && props.activeContact.schedule ? props.activeContact.schedule.length : 0, currentScheduleEnd.value], () => {
|
|
|
|
|
// 当 schedule 或 currentScheduleEnd 变化时,如果存在一个待处理的占位回复,重新安排定时器
|
|
|
|
|
@ -265,12 +292,23 @@ const performAIReply = async (batchId, userText) => {
|
|
|
|
|
if (Array.isArray(reply)) reply = reply.join('\n')
|
|
|
|
|
reply = String(reply).trim()
|
|
|
|
|
if (!reply) reply = '(刚在忙,你说啥?)'
|
|
|
|
|
|
|
|
|
|
const hadPlaceholder = messages.value.some(m => m.batchId === batchId && m.isPlaceholder)
|
|
|
|
|
messages.value = messages.value.map(m =>
|
|
|
|
|
m.batchId === batchId && m.isPlaceholder
|
|
|
|
|
? { ...m, text: reply, timestamp: Date.now(), isPlaceholder: false }
|
|
|
|
|
: m
|
|
|
|
|
)
|
|
|
|
|
if (!hadPlaceholder) {
|
|
|
|
|
// 如果没有占位消息,则直接追加 AI 回复
|
|
|
|
|
const replyMsg = {
|
|
|
|
|
senderId: props.activeContact?.id || 'ai',
|
|
|
|
|
text: reply,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
batchId: batchId,
|
|
|
|
|
isPlaceholder: false
|
|
|
|
|
}
|
|
|
|
|
messages.value = [...messages.value, replyMsg]
|
|
|
|
|
}
|
|
|
|
|
emit('update:modelValue', messages.value)
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('AI回复错误:', err)
|
|
|
|
|
@ -282,11 +320,22 @@ const performAIReply = async (batchId, userText) => {
|
|
|
|
|
console.error('服务器无响应:', err.request)
|
|
|
|
|
errMsg = '服务失联,请检查网络'
|
|
|
|
|
}
|
|
|
|
|
const hadPlaceholder = messages.value.some(m => m.batchId === batchId && m.isPlaceholder)
|
|
|
|
|
messages.value = messages.value.map(m =>
|
|
|
|
|
m.batchId === batchId && m.isPlaceholder
|
|
|
|
|
? { ...m, text: errMsg, timestamp: Date.now(), isPlaceholder: false }
|
|
|
|
|
: m
|
|
|
|
|
)
|
|
|
|
|
if (!hadPlaceholder) {
|
|
|
|
|
const errMsgObj = {
|
|
|
|
|
senderId: props.activeContact?.id || 'ai',
|
|
|
|
|
text: errMsg,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
batchId: batchId,
|
|
|
|
|
isPlaceholder: false
|
|
|
|
|
}
|
|
|
|
|
messages.value = [...messages.value, errMsgObj]
|
|
|
|
|
}
|
|
|
|
|
emit('update:modelValue', messages.value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -331,18 +380,7 @@ const handleSend = async () => {
|
|
|
|
|
|
|
|
|
|
const API_KEY = import.meta.env.VITE_ZHIPU_API_KEY
|
|
|
|
|
|
|
|
|
|
// 创建AI占位消息 - 添加相同的batchId
|
|
|
|
|
const aiPlaceholderMsg = {
|
|
|
|
|
senderId: props.activeContact?.id || 'ai',
|
|
|
|
|
text: '对方正在输入中...',
|
|
|
|
|
timestamp: Date.now() + 1,
|
|
|
|
|
batchId: messageBatchId,
|
|
|
|
|
isPlaceholder: true // 明确标记这是占位消息
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nextMessages = [...messages.value, aiPlaceholderMsg]
|
|
|
|
|
messages.value = nextMessages
|
|
|
|
|
emit('update:modelValue', nextMessages)
|
|
|
|
|
// 不再插入“对方正在输入中...”占位提示,直接等待 AI 回复(或发送忙碌提示)
|
|
|
|
|
|
|
|
|
|
const isBusy = statusClass.value === 'busy' || statusText.value.startsWith('正在')
|
|
|
|
|
|
|
|
|
|
@ -351,6 +389,29 @@ const handleSend = async () => {
|
|
|
|
|
pendingPlaceholderTs = messageBatchId
|
|
|
|
|
|
|
|
|
|
if (isBusy) {
|
|
|
|
|
// 立即发送一条忙碌提示(包含下一个休闲活动开始时间),并保留占位消息,随后仍然触发 AI 回复
|
|
|
|
|
const nextRelax = findNextRelaxStart(props.activeContact || {})
|
|
|
|
|
let timeHint = ''
|
|
|
|
|
if (nextRelax) {
|
|
|
|
|
timeHint = formatDateTime(nextRelax.getTime())
|
|
|
|
|
} else if (currentScheduleEnd.value) {
|
|
|
|
|
timeHint = formatDateTime(currentScheduleEnd.value)
|
|
|
|
|
} else {
|
|
|
|
|
timeHint = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const busyText = `我正在${currentActivity.value || '忙碌'},稍后回复哦` + (timeHint ? `(下次空闲:${timeHint})` : '')
|
|
|
|
|
const busyNoticeMsg = {
|
|
|
|
|
senderId: props.activeContact?.id || 'oc',
|
|
|
|
|
text: busyText,
|
|
|
|
|
timestamp: Date.now() + 1,
|
|
|
|
|
system: true
|
|
|
|
|
}
|
|
|
|
|
// 插入忙碌提示到消息中并通知父组件
|
|
|
|
|
nextMessages = [...messages.value, busyNoticeMsg]
|
|
|
|
|
messages.value = nextMessages
|
|
|
|
|
emit('update:modelValue', nextMessages)
|
|
|
|
|
|
|
|
|
|
// 计算等待时间(若有日程结束时间则等待至结束,否则默认 5 分钟)
|
|
|
|
|
let waitMs = 0
|
|
|
|
|
if (currentScheduleEnd.value) {
|
|
|
|
|
|