# 现在需要修复的问题
1. 媒体预览的组件有问题：Uncaught TypeError: $setup.previewMedia is not a function
   at _createElementBlock.onClick._cache.<computed>._cache.<computed> (MessageBubble.vue:27:21)
2. 点击视频聊天或者语音聊天后应该请求弹框（等待对方接听，对方传输接收则开始聊天，对方拒绝则给出消息提示然后存在聊天记录中），对方也要弹出视频/语音通话邀请，可以接收或者拒绝。
3. 根据{前端获取信令示例}和{后端交互代码}来完成整个聊天功能，并且要考虑用户掉线、断网、网络异常等情况，以及用户退出登录、用户被删除等情况。
4. 在好友管理点击聊天后要吧侧边导航变成聊天
5. 聊天记录列表和好友管理中中，点击对方的头像要有对方的信息
6. 完整的基于WebRTC实现语音/视频通话功能




# 前端获取信令示例
```javascript
// 发起通话邀请
function startCall(targetUserId, isVideo) {
    const callId = generateUniqueId();
    const callType = isVideo ? 6 : 7; // 6=视频,7=语音
    
    const callSignal = {
        action: "invite",
        call_type: callType,
        call_id: callId,
        caller_id: currentUserId,
        callee_id: targetUserId,
        data: JSON.stringify(offer) // SDP offer
    };
    
    const payload = {
        sender_user_id: currentUserId,
        receiver_user_id: targetUserId,
        message_type: callType,
        message_content: JSON.stringify(callSignal)
    };
    
    // 调用/send-to-user接口
    fetch('/send-to-user', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    }).then(response => {
        if (!response.ok) {
            throw new Error('Failed to send call invite');
        }
        return response.json();
    }).then(data => {
        console.log('Call invite sent:', data);
    }).catch(error => {
        console.error('Error sending call invite:', error);
    });
}

// 接受通话
function acceptCall(callId, callType) {
    const callSignal = {
        action: "accept",
        call_id: callId,
        data: JSON.stringify(answer) // SDP answer
    };
    
    const payload = {
        sender_user_id: currentUserId,
        receiver_user_id: callerUserId,
        message_type: callType,
        message_content: JSON.stringify(callSignal)
    };
    
    fetch('/send-to-user', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    });
}

// 拒绝通话
function rejectCall(callId, callType, reason) {
    const callSignal = {
        action: "reject",
        call_id: callId,
        data: reason || "用户忙"
    };
    
    const payload = {
        sender_user_id: currentUserId,
        receiver_user_id: callerUserId,
        message_type: callType,
        message_content: JSON.stringify(callSignal)
    };
    
    fetch('/send-to-user', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    });
}

// 结束通话
function endCall(callId, callType) {
    const callSignal = {
        action: "end",
        call_id: callId
    };
    
    const payload = {
        sender_user_id: currentUserId,
        receiver_user_id: otherUserId,
        message_type: callType,
        message_content: JSON.stringify(callSignal)
    };
    
    fetch('/send-to-user', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    });
}

// 发送ICE候选
function sendCandidate(callId, callType, candidate, targetUserId) {
    const callSignal = {
        action: "candidate",
        call_id: callId,
        data: JSON.stringify(candidate)
    };
    
    const payload = {
        sender_user_id: currentUserId,
        receiver_user_id: targetUserId,
        message_type: callType,
        message_content: JSON.stringify(callSignal)
    };
    
    fetch('/send-to-user', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
    });
}
```

## 后端交互代码
1. {websocket_controller.go}

## 消息结构
1. {messages.go}

## 通话流程图
```mermaid
graph TD
    A[用户A点击通话按钮] --> B{选择通话类型}
    B --> |语音通话| C1[设置 message_type=7]
    B --> |视频通话| C2[设置 message_type=6]
    
    C1 --> D[生成唯一CallID]
    D --> E[创建PeerConnection对象]
    E --> F[获取本地媒体流]
    F --> G[创建SDP offer]
    G --> H[构造信令对象]
    
    H --> I[发送邀请信令]
    I --> J[后端转发给用户B]
    
    J --> K[用户B收到来电通知]
    K --> L{用户B选择}
    L --> |接听| M[创建SDP answer]
    L --> |拒绝| N[发送拒绝信令]
    
    M --> O[发送接受信令]
    O --> P[后端转发给用户A]
    
    P --> Q[用户A设置远程描述]
    Q --> R[交换ICE候选]
    
    R --> S[建立P2P连接]
    S --> T[开始语音通话]
    
    N --> U[用户A收到拒绝通知]
    U --> V[结束通话流程]
    
    subgraph 信令交换
    I[发送邀请信令]
    O[发送接受信令]
    R[交换ICE候选]
    end
    
    subgraph WebRTC连接
    E[创建PeerConnection]
    F[获取本地媒体流]
    G[创建SDP offer]
    M[创建SDP answer]
    Q[设置远程描述]
    S[建立P2P连接]
    end
```